import { jest, describe, it, expect, beforeEach } from '@jest/globals';
import { createMockEmployee } from '../../../__tests__/helpers.js';

// Mock query functions
const mockQuery = jest.fn();
const mockQueryOne = jest.fn();

jest.mock('../../../config/database.js', () => ({
  query: (...args: any[]) => mockQuery(...args),
  queryOne: (...args: any[]) => mockQueryOne(...args),
}));

// Import after mocking
import { employeesService } from '../employees.service.js';
import { NotFoundError, ConflictError, ValidationError } from '../../../shared/errors/index.js';

describe('EmployeesService', () => {
  const tenantId = 'test-tenant-uuid';
  const userId = 'test-user-uuid';

  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('findAll', () => {
    it('should return employees with pagination', async () => {
      const mockEmployees = [
        createMockEmployee({ id: '1', firstName: 'John' }),
        createMockEmployee({ id: '2', firstName: 'Jane' }),
      ];

      mockQueryOne.mockResolvedValue({ count: '2' });
      mockQuery.mockResolvedValue(mockEmployees);

      const result = await employeesService.findAll(tenantId, { page: 1, limit: 20 });

      expect(result.data).toHaveLength(2);
      expect(result.total).toBe(2);
    });

    it('should filter by company_id', async () => {
      mockQueryOne.mockResolvedValue({ count: '0' });
      mockQuery.mockResolvedValue([]);

      await employeesService.findAll(tenantId, { company_id: 'company-uuid' });

      expect(mockQuery).toHaveBeenCalledWith(
        expect.stringContaining('e.company_id = $'),
        expect.arrayContaining([tenantId, 'company-uuid'])
      );
    });

    it('should filter by department_id', async () => {
      mockQueryOne.mockResolvedValue({ count: '0' });
      mockQuery.mockResolvedValue([]);

      await employeesService.findAll(tenantId, { department_id: 'dept-uuid' });

      expect(mockQuery).toHaveBeenCalledWith(
        expect.stringContaining('e.department_id = $'),
        expect.arrayContaining([tenantId, 'dept-uuid'])
      );
    });

    it('should filter by status', async () => {
      mockQueryOne.mockResolvedValue({ count: '0' });
      mockQuery.mockResolvedValue([]);

      await employeesService.findAll(tenantId, { status: 'active' });

      expect(mockQuery).toHaveBeenCalledWith(
        expect.stringContaining('e.status = $'),
        expect.arrayContaining([tenantId, 'active'])
      );
    });

    it('should filter by search term', async () => {
      mockQueryOne.mockResolvedValue({ count: '0' });
      mockQuery.mockResolvedValue([]);

      await employeesService.findAll(tenantId, { search: 'John' });

      expect(mockQuery).toHaveBeenCalledWith(
        expect.stringContaining('e.first_name ILIKE'),
        expect.arrayContaining([tenantId, '%John%'])
      );
    });

    it('should apply pagination correctly', async () => {
      mockQueryOne.mockResolvedValue({ count: '50' });
      mockQuery.mockResolvedValue([]);

      await employeesService.findAll(tenantId, { page: 3, limit: 10 });

      expect(mockQuery).toHaveBeenCalledWith(
        expect.stringContaining('LIMIT'),
        expect.arrayContaining([10, 20]) // limit=10, offset=20 (page 3)
      );
    });
  });

  describe('findById', () => {
    it('should return employee when found', async () => {
      const mockEmployee = createMockEmployee();
      mockQueryOne.mockResolvedValue(mockEmployee);

      const result = await employeesService.findById('employee-uuid-1', tenantId);

      expect(result).toEqual(mockEmployee);
    });

    it('should throw NotFoundError when employee not found', async () => {
      mockQueryOne.mockResolvedValue(null);

      await expect(
        employeesService.findById('nonexistent-id', tenantId)
      ).rejects.toThrow(NotFoundError);
    });
  });

  describe('create', () => {
    const createDto = {
      company_id: 'company-uuid',
      employee_number: 'EMP-001',
      first_name: 'John',
      last_name: 'Doe',
      hire_date: '2024-01-15',
    };

    it('should create employee successfully', async () => {
      // No existing employee with same number
      mockQueryOne.mockResolvedValueOnce(null);
      // INSERT returns new employee
      mockQueryOne.mockResolvedValueOnce({ id: 'new-uuid', ...createDto });
      // findById for return value
      mockQueryOne.mockResolvedValueOnce(createMockEmployee({ ...createDto }));

      const result = await employeesService.create(createDto, tenantId, userId);

      expect(result.first_name).toBe('John');
    });

    it('should throw ConflictError when employee number already exists', async () => {
      mockQueryOne.mockResolvedValue({ id: 'existing-uuid' });

      await expect(
        employeesService.create(createDto, tenantId, userId)
      ).rejects.toThrow(ConflictError);
    });
  });

  describe('update', () => {
    it('should update employee successfully', async () => {
      const existingEmployee = createMockEmployee();
      mockQueryOne.mockResolvedValue(existingEmployee);
      mockQuery.mockResolvedValue([]);

      const result = await employeesService.update(
        'employee-uuid-1',
        { first_name: 'Updated' },
        tenantId,
        userId
      );

      expect(mockQuery).toHaveBeenCalledWith(
        expect.stringContaining('UPDATE hr.employees SET'),
        expect.any(Array)
      );
    });

    it('should throw NotFoundError when employee not found', async () => {
      mockQueryOne.mockResolvedValue(null);

      await expect(
        employeesService.update('nonexistent-id', { first_name: 'Test' }, tenantId, userId)
      ).rejects.toThrow(NotFoundError);
    });

    it('should return unchanged employee when no fields to update', async () => {
      const existingEmployee = createMockEmployee();
      mockQueryOne.mockResolvedValue(existingEmployee);

      const result = await employeesService.update(
        'employee-uuid-1',
        {},
        tenantId,
        userId
      );

      expect(result).toEqual(existingEmployee);
    });
  });

  describe('terminate', () => {
    it('should terminate active employee', async () => {
      const activeEmployee = createMockEmployee({ status: 'active' });
      mockQueryOne.mockResolvedValue(activeEmployee);
      mockQuery.mockResolvedValue([]);

      await employeesService.terminate('employee-uuid-1', '2024-12-31', tenantId, userId);

      expect(mockQuery).toHaveBeenCalledWith(
        expect.stringContaining("status = 'terminated'"),
        expect.any(Array)
      );
    });

    it('should throw ValidationError when employee already terminated', async () => {
      const terminatedEmployee = createMockEmployee({ status: 'terminated' });
      mockQueryOne.mockResolvedValue(terminatedEmployee);

      await expect(
        employeesService.terminate('employee-uuid-1', '2024-12-31', tenantId, userId)
      ).rejects.toThrow(ValidationError);
    });

    it('should also terminate active contracts', async () => {
      const activeEmployee = createMockEmployee({ status: 'active' });
      mockQueryOne.mockResolvedValue(activeEmployee);
      mockQuery.mockResolvedValue([]);

      await employeesService.terminate('employee-uuid-1', '2024-12-31', tenantId, userId);

      expect(mockQuery).toHaveBeenCalledWith(
        expect.stringContaining('UPDATE hr.contracts SET'),
        expect.any(Array)
      );
    });
  });

  describe('reactivate', () => {
    it('should reactivate terminated employee', async () => {
      const terminatedEmployee = createMockEmployee({ status: 'terminated' });
      mockQueryOne.mockResolvedValue(terminatedEmployee);
      mockQuery.mockResolvedValue([]);

      await employeesService.reactivate('employee-uuid-1', tenantId, userId);

      expect(mockQuery).toHaveBeenCalledWith(
        expect.stringContaining("status = 'active'"),
        expect.any(Array)
      );
    });

    it('should reactivate inactive employee', async () => {
      const inactiveEmployee = createMockEmployee({ status: 'inactive' });
      mockQueryOne.mockResolvedValue(inactiveEmployee);
      mockQuery.mockResolvedValue([]);

      await employeesService.reactivate('employee-uuid-1', tenantId, userId);

      expect(mockQuery).toHaveBeenCalledWith(
        expect.stringContaining("status = 'active'"),
        expect.any(Array)
      );
    });

    it('should throw ValidationError when employee is already active', async () => {
      const activeEmployee = createMockEmployee({ status: 'active' });
      mockQueryOne.mockResolvedValue(activeEmployee);

      await expect(
        employeesService.reactivate('employee-uuid-1', tenantId, userId)
      ).rejects.toThrow(ValidationError);
    });
  });

  describe('delete', () => {
    it('should delete employee without contracts or subordinates', async () => {
      const employee = createMockEmployee();
      mockQueryOne
        .mockResolvedValueOnce(employee) // findById
        .mockResolvedValueOnce({ count: '0' }) // hasContracts
        .mockResolvedValueOnce({ count: '0' }); // isManager
      mockQuery.mockResolvedValue([]);

      await employeesService.delete('employee-uuid-1', tenantId);

      expect(mockQuery).toHaveBeenCalledWith(
        expect.stringContaining('DELETE FROM hr.employees'),
        expect.any(Array)
      );
    });

    it('should throw ConflictError when employee has contracts', async () => {
      const employee = createMockEmployee();
      mockQueryOne
        .mockResolvedValueOnce(employee)
        .mockResolvedValueOnce({ count: '5' }); // hasContracts

      await expect(
        employeesService.delete('employee-uuid-1', tenantId)
      ).rejects.toThrow(ConflictError);
    });

    it('should throw ConflictError when employee is a manager', async () => {
      const employee = createMockEmployee();
      mockQueryOne
        .mockResolvedValueOnce(employee)
        .mockResolvedValueOnce({ count: '0' }) // hasContracts
        .mockResolvedValueOnce({ count: '3' }); // isManager

      await expect(
        employeesService.delete('employee-uuid-1', tenantId)
      ).rejects.toThrow(ConflictError);
    });
  });

  describe('getSubordinates', () => {
    it('should return subordinates of a manager', async () => {
      const manager = createMockEmployee();
      const subordinates = [
        createMockEmployee({ id: 'sub-1', firstName: 'Sub 1' }),
        createMockEmployee({ id: 'sub-2', firstName: 'Sub 2' }),
      ];

      mockQueryOne.mockResolvedValue(manager);
      mockQuery.mockResolvedValue(subordinates);

      const result = await employeesService.getSubordinates('manager-uuid', tenantId);

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

    it('should throw NotFoundError when manager not found', async () => {
      mockQueryOne.mockResolvedValue(null);

      await expect(
        employeesService.getSubordinates('nonexistent-id', tenantId)
      ).rejects.toThrow(NotFoundError);
    });
  });
});
