import { describe, it, expect, beforeEach, vi } from 'vitest';
import { authService } from '../authService';
import { userRepository } from '../../repositories/userRepository';
import { authAdapter } from '../../adapters/auth';

vi.mock('../../repositories/userRepository', () => ({
  userRepository: {
    findById: vi.fn(),
    findByUsername: vi.fn(),
    findMany: vi.fn(),
    create: vi.fn(),
    update: vi.fn(),
    delete: vi.fn(),
    existsByUsername: vi.fn(),
    count: vi.fn(),
  },
}));

vi.mock('../../adapters/auth', () => ({
  authAdapter: {
    verifyCredentials: vi.fn(),
    hashPassword: vi.fn(),
    verifyPassword: vi.fn(),
  },
}));

describe('AuthService', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('verifyCredentials', () => {
    it('debería retornar usuario si las credenciales son válidas', async () => {
      const mockUser = {
        id: 'user1',
        username: 'testuser',
        name: 'Test User',
        email: 'test@local.com',
        role: 'USER' as const,
      };

      vi.mocked(authAdapter.verifyCredentials).mockResolvedValue(mockUser);

      const result = await authService.verifyCredentials('testuser', 'password123');

      expect(result).toBeDefined();
      expect(result?.username).toBe('testuser');
      expect(authAdapter.verifyCredentials).toHaveBeenCalledWith('testuser', 'password123');
    });

    it('debería retornar null si las credenciales son inválidas', async () => {
      vi.mocked(authAdapter.verifyCredentials).mockResolvedValue(null);

      const result = await authService.verifyCredentials('invalid', 'wrongpassword');

      expect(result).toBeNull();
    });
  });

  describe('createUser', () => {
    it('debería crear un usuario exitosamente para ADMIN', async () => {
      const inputData = {
        name: 'Nuevo Usuario',
        username: 'nuevousuario',
        password: 'password123',
        role: 'USER' as const,
      };

      vi.mocked(userRepository.existsByUsername).mockResolvedValue(false);
      vi.mocked(authAdapter.hashPassword).mockResolvedValue('hashed_password');
      vi.mocked(userRepository.create).mockResolvedValue({
        id: 'user1',
        name: 'Nuevo Usuario',
        username: 'nuevousuario',
        role: 'USER',
        createdAt: new Date(),
        updatedAt: new Date(),
      } as any);

      const result = await authService.createUser(inputData, 'ADMIN');

      expect(result.id).toBe('user1');
      expect(authAdapter.hashPassword).toHaveBeenCalledWith('password123');
      expect(userRepository.create).toHaveBeenCalledWith({
        name: 'Nuevo Usuario',
        username: 'nuevousuario',
        passwordHash: 'hashed_password',
        role: 'USER',
      });
    });

    it('debería usar USER como rol por defecto', async () => {
      const inputData = {
        username: 'nuevousuario',
        password: 'password123',
      };

      vi.mocked(userRepository.existsByUsername).mockResolvedValue(false);
      vi.mocked(authAdapter.hashPassword).mockResolvedValue('hashed_password');
      vi.mocked(userRepository.create).mockResolvedValue({
        id: 'user1',
        username: 'nuevousuario',
        role: 'USER',
        createdAt: new Date(),
        updatedAt: new Date(),
      } as any);

      await authService.createUser(inputData as any, 'ADMIN');

      expect(userRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({
          role: 'USER',
        })
      );
    });

    it('debería lanzar error si el usuario no es ADMIN', async () => {
      const inputData = { username: 'test', password: 'pass' };

      await expect(
        authService.createUser(inputData as any, 'USER')
      ).rejects.toThrow('No tienes permisos para crear usuarios');
    });

    it('debería lanzar error si el username ya existe', async () => {
      const inputData = { username: 'existing', password: 'pass' };

      vi.mocked(userRepository.existsByUsername).mockResolvedValue(true);

      await expect(
        authService.createUser(inputData as any, 'ADMIN')
      ).rejects.toThrow('El nombre de usuario ya está en uso');
    });
  });

  describe('getAllUsers', () => {
    it('debería retornar todos los usuarios', async () => {
      const mockUsers = [
        {
          id: '1',
          username: 'user1',
          name: 'User One',
          role: 'USER',
          createdAt: new Date(),
          updatedAt: new Date(),
          _count: { documents: 5 },
        },
        {
          id: '2',
          username: 'admin1',
          name: 'Admin One',
          role: 'ADMIN',
          createdAt: new Date(),
          updatedAt: new Date(),
          _count: { documents: 10 },
        },
      ];

      vi.mocked(userRepository.findMany).mockResolvedValue(mockUsers as any);

      const result = await authService.getAllUsers();

      expect(result).toHaveLength(2);
      expect(result[0].username).toBe('user1');
      expect(result[1].role).toBe('ADMIN');
    });

    it('debería retornar array vacío si no hay usuarios', async () => {
      vi.mocked(userRepository.findMany).mockResolvedValue([]);

      const result = await authService.getAllUsers();

      expect(result).toHaveLength(0);
    });
  });

  describe('getUserById', () => {
    it('debería retornar un usuario por ID', async () => {
      const mockUser = {
        id: '1',
        username: 'testuser',
        name: 'Test User',
        role: 'USER',
        createdAt: new Date(),
        updatedAt: new Date(),
        _count: { documents: 5 },
      };

      vi.mocked(userRepository.findById).mockResolvedValue(mockUser as any);

      const result = await authService.getUserById('1');

      expect(result).toBeDefined();
      expect(result?.username).toBe('testuser');
    });

    it('debería retornar null si el usuario no existe', async () => {
      vi.mocked(userRepository.findById).mockResolvedValue(null);

      const result = await authService.getUserById('nonexistent');

      expect(result).toBeNull();
    });
  });

  describe('hasUsers', () => {
    it('debería retornar true si existen usuarios', async () => {
      vi.mocked(userRepository.count).mockResolvedValue(5);

      const result = await authService.hasUsers();

      expect(result).toBe(true);
    });

    it('debería retornar false si no existen usuarios', async () => {
      vi.mocked(userRepository.count).mockResolvedValue(0);

      const result = await authService.hasUsers();

      expect(result).toBe(false);
    });
  });
});
