import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcryptjs';
import { Request } from 'express';

import { AuthService } from '../auth.service';

describe('AuthService Integration Tests', () => {
  let prisma: PrismaClient;
  let authService: AuthService;

  beforeAll(async () => {
    // Use the existing test database setup from setupTests.ts
    // The environment should already be configured for testing
    prisma = new PrismaClient();

    // Clean up any existing test data
    await prisma.$transaction([
      prisma.userPermission.deleteMany(),
      prisma.userRole.deleteMany(),
      prisma.refreshToken.deleteMany(),
      prisma.session.deleteMany(),
      prisma.mFADevice.deleteMany(),
      prisma.authEvent.deleteMany(),
      prisma.user.deleteMany(),
    ]);

    authService = new AuthService();
  }, 30000);

  afterAll(async () => {
    await prisma.$disconnect();
  });

  beforeEach(async () => {
    // Clean up between tests
    await prisma.$transaction([
      prisma.userPermission.deleteMany(),
      prisma.userRole.deleteMany(),
      prisma.refreshToken.deleteMany(),
      prisma.session.deleteMany(),
      prisma.mFADevice.deleteMany(),
      prisma.authEvent.deleteMany(),
      prisma.user.deleteMany(),
    ]);
  });

  describe('User Registration', () => {
    it('should register a new user successfully', async () => {
      const registerData = {
        email: `test-${Date.now()}@example.com`,
        password: 'ValidTestPass123!',
        username: `user-${Date.now()}`,
      };

      const mockRequest = {
        ip: '127.0.0.1',
        headers: { 'user-agent': 'test-agent' },
      } as Partial<Request>;

      const result = await authService.register(registerData, mockRequest as Request);

      expect(result).toHaveProperty('user');
      expect(result).toHaveProperty('tokens');
      expect(result.user.email).toBe(registerData.email);
      expect(result.user.username).toBe(registerData.username);
      expect(result.tokens).toHaveProperty('accessToken');
      expect(result.tokens).toHaveProperty('refreshToken');

      // Verify user was created in database
      const dbUser = await prisma.user.findUnique({
        where: { email: registerData.email },
      });
      expect(dbUser).toBeTruthy();
      expect(dbUser?.email).toBe(registerData.email);
      expect(dbUser?.username).toBe(registerData.username);
      expect(dbUser?.isActive).toBe(true);
      expect(dbUser?.emailVerified).toBe(false);
      expect(dbUser?.mfaEnabled).toBe(false);
    });

    it('should hash password securely', async () => {
      const registerData = {
        email: `hash-test-${Date.now()}@example.com`,
        password: 'MySecurePassword123!',
        username: `hash-user-${Date.now()}`,
      };

      const mockRequest = {
        ip: '127.0.0.1',
        headers: { 'user-agent': 'test-agent' },
      } as Partial<Request>;

      await authService.register(registerData, mockRequest as Request);

      // Verify password is hashed in database
      const dbUser = await prisma.user.findUnique({
        where: { email: registerData.email },
      });

      expect(dbUser?.passwordHash).not.toBe(registerData.password);
      expect(dbUser?.passwordHash).toMatch(/^\$2[ayb]\$.{56}$/); // bcrypt hash pattern

      // Verify password can be validated
      const isValidPassword = await bcrypt.compare(registerData.password, dbUser!.passwordHash || '');
      expect(isValidPassword).toBe(true);
    });

    it('should reject duplicate email registration', async () => {
      const email = `duplicate-${Date.now()}@example.com`;

      // Register first user
      const registerData1 = {
        email,
        password: 'ValidTestPass123!',
        username: `user1-${Date.now()}`,
      };

      const mockRequest = {
        ip: '127.0.0.1',
        headers: { 'user-agent': 'test-agent' },
      } as Partial<Request>;

      await authService.register(registerData1, mockRequest as Request);

      // Try to register second user with same email
      const registerData2 = {
        email,
        password: 'DifferentPass456!',
        username: `user2-${Date.now()}`,
      };

      await expect(authService.register(registerData2, mockRequest as Request))
        .rejects.toThrow('User already exists');
    });
  });

  describe('User Login', () => {
    it('should login user successfully with correct credentials', async () => {
      // First register a user
      const email = `login-test-${Date.now()}@example.com`;
      const password = 'LoginTestPass123!';
      const username = `login-user-${Date.now()}`;

      const registerData = { email, password, username };
      const mockRequest = {
        ip: '127.0.0.1',
        headers: { 'user-agent': 'test-agent' },
      } as Partial<Request>;

      await authService.register(registerData, mockRequest as Request);

      // Now try to login
      const loginData = { email, password };
      const loginResult = await authService.login(loginData, mockRequest as Request);

      expect(loginResult).toHaveProperty('user');
      expect(loginResult).toHaveProperty('tokens');
      expect(loginResult.user.email).toBe(email);
      expect(loginResult.user.username).toBe(username);
      expect(loginResult.tokens).toHaveProperty('accessToken');
      expect(loginResult.tokens).toHaveProperty('refreshToken');
    });

    it('should reject login with wrong password', async () => {
      // Register a user
      const email = `wrong-pass-${Date.now()}@example.com`;
      const password = 'CorrectPass123!';

      const registerData = { email, password, username: `wrong-pass-user-${Date.now()}` };
      const mockRequest = {
        ip: '127.0.0.1',
        headers: { 'user-agent': 'test-agent' },
      } as Partial<Request>;

      await authService.register(registerData, mockRequest as Request);

      // Try to login with wrong password
      const loginData = { email, password: 'WrongPassword456!' };

      await expect(authService.login(loginData, mockRequest as Request))
        .rejects.toThrow('Invalid credentials');
    });

    it('should reject login for non-existent user', async () => {
      const loginData = {
        email: 'nonexistent@example.com',
        password: 'SomePassword123!',
      };

      const mockRequest = {
        ip: '127.0.0.1',
        headers: { 'user-agent': 'test-agent' },
      } as Partial<Request>;

      await expect(authService.login(loginData, mockRequest as Request))
        .rejects.toThrow('Invalid credentials');
    });
  });

  describe('Token Refresh', () => {
    it('should refresh tokens successfully', async () => {
      // Register and login a user first
      const email = `refresh-test-${Date.now()}@example.com`;
      const password = 'RefreshTestPass123!';

      const registerData = { email, password, username: `refresh-user-${Date.now()}` };
      const mockRequest = {
        ip: '127.0.0.1',
        headers: { 'user-agent': 'test-agent' },
      } as Partial<Request>;

      const registerResult = await authService.register(registerData, mockRequest as Request);
      const originalRefreshToken = registerResult.tokens.refreshToken;

      // Refresh the token
      const refreshResult = await authService.refreshToken(originalRefreshToken, mockRequest as Request);

      expect(refreshResult).toHaveProperty('accessToken');
      expect(refreshResult).toHaveProperty('refreshToken');
      expect(refreshResult.accessToken).not.toBe(registerResult.tokens.accessToken);
      expect(refreshResult.refreshToken).not.toBe(originalRefreshToken);
    });

    it('should reject invalid refresh token', async () => {
      const invalidToken = 'invalid-refresh-token-123';
      const mockRequest = {
        ip: '127.0.0.1',
        headers: { 'user-agent': 'test-agent' },
      } as Partial<Request>;

      await expect(authService.refreshToken(invalidToken, mockRequest as Request))
        .rejects.toThrow();
    });
  });

  describe('Logout', () => {
    it('should logout user successfully', async () => {
      // Register a user
      const email = `logout-test-${Date.now()}@example.com`;
      const password = 'LogoutTestPass123!';

      const registerData = { email, password, username: `logout-user-${Date.now()}` };
      const mockRequest = {
        ip: '127.0.0.1',
        headers: { 'user-agent': 'test-agent' },
      } as Partial<Request>;

      await authService.register(registerData, mockRequest as Request);

      // Logout should not throw
      await expect(authService.logout(email)).resolves.not.toThrow();
    });
  });


});