import { type INestApplication, ValidationPipe } from '@nestjs/common';
import { CqrsModule } from '@nestjs/cqrs';
import { Test, type TestingModule } from '@nestjs/testing';
import request from 'supertest';
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { PrismaService } from '@modules/shared/infrastructure/prisma.service';
import { SharedModule } from '@modules/shared/shared.module';
import { AuthModule } from '../auth.module';

describe('Auth Controller (Integration)', () => {
  let app: INestApplication;
  let prisma: PrismaService;

  beforeAll(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [CqrsModule.forRoot(), SharedModule, AuthModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    app.useGlobalPipes(
      new ValidationPipe({
        whitelist: true,
        forbidNonWhitelisted: true,
        transform: true,
        transformOptions: { enableImplicitConversion: true },
      }),
    );
    await app.init();

    prisma = app.get(PrismaService);
  });

  afterAll(async () => {
    // Clean up test data
    await prisma.refreshToken.deleteMany({});
    await prisma.user.deleteMany({
      where: { phone: { in: ['+84912345678', '+84987654321'] } },
    });
    await app.close();
  });

  describe('POST /auth/register', () => {
    it('should register a new user and return tokens', async () => {
      const res = await request(app.getHttpServer())
        .post('/auth/register')
        .send({
          phone: '0912345678',
          password: 'StrongPass123',
          fullName: 'Test User',
          email: 'test@example.com',
        })
        .expect(201);

      expect(res.body.accessToken).toBeDefined();
      expect(res.body.refreshToken).toBeDefined();
      expect(res.body.expiresIn).toBe(900);
    });

    it('should reject duplicate phone', async () => {
      await request(app.getHttpServer())
        .post('/auth/register')
        .send({
          phone: '0912345678',
          password: 'StrongPass123',
          fullName: 'Duplicate User',
        })
        .expect(409);
    });

    it('should reject invalid phone', async () => {
      await request(app.getHttpServer())
        .post('/auth/register')
        .send({
          phone: '12345',
          password: 'StrongPass123',
          fullName: 'Invalid Phone',
        })
        .expect(400);
    });

    it('should reject short password', async () => {
      await request(app.getHttpServer())
        .post('/auth/register')
        .send({
          phone: '0987654321',
          password: 'short',
          fullName: 'Short Pass',
        })
        .expect(400);
    });
  });

  describe('POST /auth/login', () => {
    it('should login with valid credentials', async () => {
      const res = await request(app.getHttpServer())
        .post('/auth/login')
        .send({
          phone: '0912345678',
          password: 'StrongPass123',
        })
        .expect(201);

      expect(res.body.accessToken).toBeDefined();
      expect(res.body.refreshToken).toBeDefined();
    });

    it('should reject invalid password', async () => {
      await request(app.getHttpServer())
        .post('/auth/login')
        .send({
          phone: '0912345678',
          password: 'WrongPassword',
        })
        .expect(401);
    });

    it('should reject non-existent phone', async () => {
      await request(app.getHttpServer())
        .post('/auth/login')
        .send({
          phone: '0999999999',
          password: 'StrongPass123',
        })
        .expect(401);
    });
  });

  describe('POST /auth/refresh', () => {
    let refreshToken: string;

    beforeAll(async () => {
      const res = await request(app.getHttpServer())
        .post('/auth/login')
        .send({
          phone: '0912345678',
          password: 'StrongPass123',
        });
      refreshToken = res.body.refreshToken;
    });

    it('should rotate refresh token', async () => {
      const res = await request(app.getHttpServer())
        .post('/auth/refresh')
        .send({ refreshToken })
        .expect(201);

      expect(res.body.accessToken).toBeDefined();
      expect(res.body.refreshToken).toBeDefined();
      expect(res.body.refreshToken).not.toBe(refreshToken);
    });

    it('should reject reused refresh token', async () => {
      // The old token was already rotated — reuse should fail
      await request(app.getHttpServer())
        .post('/auth/refresh')
        .send({ refreshToken })
        .expect(401);
    });
  });

  describe('GET /auth/profile', () => {
    let accessToken: string;

    beforeAll(async () => {
      const res = await request(app.getHttpServer())
        .post('/auth/login')
        .send({
          phone: '0912345678',
          password: 'StrongPass123',
        });
      accessToken = res.body.accessToken;
    });

    it('should return user profile', async () => {
      const res = await request(app.getHttpServer())
        .get('/auth/profile')
        .set('Authorization', `Bearer ${accessToken}`)
        .expect(200);

      expect(res.body.phone).toBe('+84912345678');
      expect(res.body.fullName).toBe('Test User');
      expect(res.body.role).toBe('BUYER');
      expect(res.body.kycStatus).toBe('NONE');
    });

    it('should reject unauthenticated request', async () => {
      await request(app.getHttpServer()).get('/auth/profile').expect(401);
    });

    it('should reject invalid token', async () => {
      await request(app.getHttpServer())
        .get('/auth/profile')
        .set('Authorization', 'Bearer invalid-token')
        .expect(401);
    });
  });

  describe('POST /auth/exchange-token', () => {
    let validAccessToken: string;
    let validRefreshToken: string;

    beforeAll(async () => {
      const res = await request(app.getHttpServer())
        .post('/auth/login')
        .send({
          phone: '0912345678',
          password: 'StrongPass123',
        });
      validAccessToken = res.body.accessToken as string;
      validRefreshToken = res.body.refreshToken as string;
    });

    it('should set auth cookies for a valid token pair', async () => {
      const res = await request(app.getHttpServer())
        .post('/auth/exchange-token')
        .send({ accessToken: validAccessToken, refreshToken: validRefreshToken })
        .expect(201);

      expect(res.body.message).toBe('Auth cookies set');
      const setCookie = res.headers['set-cookie'] as string[] | string;
      const cookieStr = Array.isArray(setCookie) ? setCookie.join('; ') : (setCookie ?? '');
      expect(cookieStr).toContain('access_token=');
      expect(cookieStr).toContain('refresh_token=');
    });

    it('should return 401 for an invalid access token', async () => {
      await request(app.getHttpServer())
        .post('/auth/exchange-token')
        .send({ accessToken: 'invalid.token.here', refreshToken: validRefreshToken })
        .expect(401);
    });

    it('should return 401 when accessToken is missing', async () => {
      await request(app.getHttpServer())
        .post('/auth/exchange-token')
        .send({ refreshToken: validRefreshToken })
        .expect(401);
    });
  });
});
