import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import request from 'supertest';
import * as bcrypt from 'bcryptjs';
import { JwtService } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';
import { AuthController } from '../../src/modules/auth/auth.controller';
import { AuthService } from '../../src/modules/auth/auth.service';
import { TwoFactorService } from '../../src/modules/auth/two-factor.service';
import { PrismaService } from '@chatapp/database';
import { generateTestToken, generateRefreshToken } from '../setup';

// Create a factory for the mock PrismaService
function createMockPrisma() {
  const mockStore: Record<string, any> = {};
  return {
    user: {
      findUnique: jest.fn(({ where }: any) => {
        if (where.email) {
          return mockStore[`user:${where.email}`] || null;
        }
        if (where.id) {
          return mockStore[`user:${where.id}`] || null;
        }
        return null;
      }),
      create: jest.fn(({ data }: any) => {
        const user = {
          id: 'user-' + Date.now(),
          email: data.email,
          displayName: data.displayName,
          passwordHash: data.passwordHash,
          isDeleted: false,
          avatarUrl: null,
          ...data,
        };
        mockStore[`user:${data.email}`] = user;
        mockStore[`user:${user.id}`] = user;
        return user;
      }),
    },
    workspace: {
      create: jest.fn(({ data }: any) => {
        const ws = { id: 'ws-' + Date.now() + '-' + Math.random().toString(36).slice(2, 6), ...data };
        mockStore[`workspace:${ws.id}`] = ws;
        return ws;
      }),
    },
    workspaceMember: {
      findMany: jest.fn(() => []),
      create: jest.fn(),
    },
    session: {
      findFirst: jest.fn(() => null),
      create: jest.fn(),
      update: jest.fn(),
      updateMany: jest.fn(),
    },
    channel: {
      create: jest.fn(({ data }: any) => {
        const ch = { id: 'ch-' + Date.now() + '-' + Math.random().toString(36).slice(2, 6), ...data };
        mockStore[`channel:${ch.id}`] = ch;
        return ch;
      }),
    },
    channelMember: {
      create: jest.fn(({ data }: any) => {
        const m = { id: 'cm-' + Date.now(), ...data };
        return m;
      }),
    },
    twoFactorAuth: {
      findUnique: jest.fn(() => ({ enabled: false })),
    },
    $connect: jest.fn(),
    $disconnect: jest.fn(),
  };
}

describe('Auth E2E', () => {
  let app: INestApplication;
  let mockPrisma: any;
  let jwtService: JwtService;
  let configService: ConfigService;

  beforeAll(async () => {
    // Set required env vars for JWT
    process.env.JWT_SECRET = process.env.JWT_SECRET || 'test-jwt-secret-e2e';
    process.env.JWT_ACCESS_EXPIRES = process.env.JWT_ACCESS_EXPIRES || '15m';
    process.env.JWT_REFRESH_EXPIRES = process.env.JWT_REFRESH_EXPIRES || '30d';

    mockPrisma = createMockPrisma();

    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [
        ConfigModule.forRoot({ isGlobal: true }),
        JwtModule.registerAsync({
          imports: [ConfigModule],
          inject: [ConfigService],
          useFactory: (config: ConfigService) => ({
            secret: config.get<string>('JWT_SECRET'),
            signOptions: { expiresIn: '15m' },
          }),
        }),
      ],
      controllers: [AuthController],
      providers: [
        AuthService,
        { provide: PrismaService, useValue: mockPrisma },
        { provide: TwoFactorService, useValue: { verifyToken: jest.fn(), generateSecret: jest.fn(), enable: jest.fn(), disable: jest.fn() } },
      ],
    }).compile();

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

    jwtService = moduleFixture.get<JwtService>(JwtService);
    configService = moduleFixture.get<ConfigService>(ConfigService);
  });

  afterAll(async () => {
    await app.close();
  });

  describe('POST /api/v1/auth/register', () => {
    it('should register a new user successfully', async () => {
      const res = await request(app.getHttpServer())
        .post('/api/v1/auth/register')
        .send({
          email: 'testuser@example.com',
          password: 'ValidPassword123!',
          display_name: 'Test User',
          workspace_name: 'Test Workspace',
        })
        .expect(201);

      expect(res.body).toHaveProperty('access_token');
      expect(res.body).toHaveProperty('refresh_token');
      expect(res.body.user).toHaveProperty('email', 'testuser@example.com');
      expect(res.body.user).toHaveProperty('display_name', 'Test User');
      expect(res.body.workspace).toHaveProperty('name', 'Test Workspace');
      expect(res.body.workspace).toHaveProperty('domain');
    });

    it('should reject duplicate email registration', async () => {
      // First registration
      await request(app.getHttpServer())
        .post('/api/v1/auth/register')
        .send({
          email: 'duplicate@example.com',
          password: 'ValidPassword123!',
          display_name: 'First User',
        })
        .expect(201);

      // Second registration with same email
      const res = await request(app.getHttpServer())
        .post('/api/v1/auth/register')
        .send({
          email: 'duplicate@example.com',
          password: 'ValidPassword123!',
          display_name: 'Second User',
        })
        .expect(409);

      expect(res.body.message).toContain('already registered');
    });

    it('should reject invalid email', async () => {
      const res = await request(app.getHttpServer())
        .post('/api/v1/auth/register')
        .send({
          email: 'not-an-email',
          password: 'ValidPassword123!',
          display_name: 'Test User',
        })
        .expect(400);

      expect(res.body.message).toBeDefined();
    });

    it('should reject weak password', async () => {
      const res = await request(app.getHttpServer())
        .post('/api/v1/auth/register')
        .send({
          email: 'weakpass@example.com',
          password: 'weak',
          display_name: 'Test User',
        })
        .expect(400);

      expect(res.body.message).toBeDefined();
    });
  });

  describe('POST /api/v1/auth/login', () => {
    it('should login a registered user', async () => {
      // First register
      await request(app.getHttpServer())
        .post('/api/v1/auth/register')
        .send({
          email: 'loginuser@example.com',
          password: 'ValidPassword123!',
          display_name: 'Login User',
        })
        .expect(201);

      // Then login
      const res = await request(app.getHttpServer())
        .post('/api/v1/auth/login')
        .send({
          email: 'loginuser@example.com',
          password: 'ValidPassword123!',
        })
        .expect(200);

      expect(res.body).toHaveProperty('access_token');
      expect(res.body).toHaveProperty('refresh_token');
      expect(res.body.user).toHaveProperty('email', 'loginuser@example.com');
    });

    it('should reject wrong password', async () => {
      // Register
      await request(app.getHttpServer())
        .post('/api/v1/auth/register')
        .send({
          email: 'wrongpass@example.com',
          password: 'ValidPassword123!',
          display_name: 'Wrong Pass User',
        })
        .expect(201);

      // Login with wrong password
      const res = await request(app.getHttpServer())
        .post('/api/v1/auth/login')
        .send({
          email: 'wrongpass@example.com',
          password: 'WrongPassword123',
        })
        .expect(401);

      expect(res.body.message).toContain('Invalid credentials');
    });

    it('should reject non-existent user', async () => {
      const res = await request(app.getHttpServer())
        .post('/api/v1/auth/login')
        .send({
          email: 'nonexistent@example.com',
          password: 'SomePassword123',
        })
        .expect(401);

      expect(res.body.message).toContain('Invalid credentials');
    });
  });

  describe('POST /api/v1/auth/refresh', () => {
    it('should reject invalid refresh token', async () => {
      const res = await request(app.getHttpServer())
        .post('/api/v1/auth/refresh')
        .send({
          refresh_token: 'invalid-token-string',
        })
        .expect(401);

      expect(res.body.message).toContain('Invalid refresh token');
    });

    it('should reject valid JWT but no session found', async () => {
      // Create a valid refresh token
      const refreshToken = generateRefreshToken('test-user-id', 'test@example.com');

      // Mock has no session, so should fail
      const res = await request(app.getHttpServer())
        .post('/api/v1/auth/refresh')
        .send({
          refresh_token: refreshToken,
        })
        .expect(401);

      expect(res.body.message).toContain('Invalid refresh token');
    });
  });

  describe('POST /api/v1/auth/logout', () => {
    it('should require authentication', async () => {
      const res = await request(app.getHttpServer())
        .post('/api/v1/auth/logout')
        .expect(401);

      expect(res.body.message).toContain('No token provided');
    });

    it('should logout with valid token', async () => {
      // Register to get a token
      const regRes = await request(app.getHttpServer())
        .post('/api/v1/auth/register')
        .send({
          email: 'logoutuser@example.com',
          password: 'ValidPassword123!',
          display_name: 'Logout User',
        })
        .expect(201);

      const token = regRes.body.access_token;

      const res = await request(app.getHttpServer())
        .post('/api/v1/auth/logout')
        .set('Authorization', `Bearer ${token}`)
        .expect(200);

      expect(res.body.message).toContain('Logged out successfully');
    });
  });

  describe('GET /api/v1/auth/sessions', () => {
    it('should require authentication', async () => {
      await request(app.getHttpServer())
        .get('/api/v1/auth/sessions')
        .expect(401);
    });

    it('should return sessions list with auth', async () => {
      const token = generateTestToken('session-user', 'session@example.com');

      const res = await request(app.getHttpServer())
        .get('/api/v1/auth/sessions')
        .set('Authorization', `Bearer ${token}`)
        .expect(200);

      expect(res.body).toHaveProperty('sessions');
    });
  });
});