/**
 * Employees Service
 * @module HR
 */

import { Repository, FindOptionsWhere, ILike, Between, LessThanOrEqual, MoreThanOrEqual } from 'typeorm';
import { Employee } from '../entities/employee.entity';
import { CreateEmployeeDto, UpdateEmployeeDto } from '../dto';

export interface EmployeeSearchParams {
  tenantId: string;
  search?: string;
  estado?: 'activo' | 'inactivo' | 'baja';
  puestoId?: string;
  departamento?: string;
  fechaIngresoDesde?: Date;
  fechaIngresoHasta?: Date;
  limit?: number;
  offset?: number;
}

export class EmployeesService {
  constructor(private readonly employeeRepository: Repository<Employee>) {}

  /**
   * Find all employees with filters
   */
  async findAll(params: EmployeeSearchParams): Promise<{ data: Employee[]; total: number }> {
    const {
      tenantId,
      search,
      estado,
      puestoId,
      departamento,
      fechaIngresoDesde,
      fechaIngresoHasta,
      limit = 50,
      offset = 0,
    } = params;

    const queryBuilder = this.employeeRepository
      .createQueryBuilder('employee')
      .leftJoinAndSelect('employee.puesto', 'puesto')
      .where('employee.tenant_id = :tenantId', { tenantId });

    if (search) {
      queryBuilder.andWhere(
        '(employee.codigo ILIKE :search OR employee.nombre ILIKE :search OR ' +
        'employee.apellido_paterno ILIKE :search OR employee.apellido_materno ILIKE :search OR ' +
        'employee.curp ILIKE :search OR employee.rfc ILIKE :search)',
        { search: `%${search}%` }
      );
    }

    if (estado) {
      queryBuilder.andWhere('employee.estado = :estado', { estado });
    }

    if (puestoId) {
      queryBuilder.andWhere('employee.puesto_id = :puestoId', { puestoId });
    }

    if (departamento) {
      queryBuilder.andWhere('employee.departamento ILIKE :departamento', { departamento: `%${departamento}%` });
    }

    if (fechaIngresoDesde) {
      queryBuilder.andWhere('employee.fecha_ingreso >= :fechaIngresoDesde', { fechaIngresoDesde });
    }

    if (fechaIngresoHasta) {
      queryBuilder.andWhere('employee.fecha_ingreso <= :fechaIngresoHasta', { fechaIngresoHasta });
    }

    queryBuilder
      .orderBy('employee.apellido_paterno', 'ASC')
      .addOrderBy('employee.apellido_materno', 'ASC')
      .addOrderBy('employee.nombre', 'ASC');

    const total = await queryBuilder.getCount();
    const data = await queryBuilder.skip(offset).take(limit).getMany();

    return { data, total };
  }

  /**
   * Find one employee by ID
   */
  async findOne(id: string, tenantId: string): Promise<Employee | null> {
    return this.employeeRepository.findOne({
      where: { id, tenantId },
      relations: ['puesto'],
    });
  }

  /**
   * Find employee by code
   */
  async findByCode(codigo: string, tenantId: string): Promise<Employee | null> {
    return this.employeeRepository.findOne({
      where: { codigo, tenantId },
      relations: ['puesto'],
    });
  }

  /**
   * Find employee by CURP
   */
  async findByCurp(curp: string, tenantId: string): Promise<Employee | null> {
    return this.employeeRepository.findOne({
      where: { curp, tenantId },
    });
  }

  /**
   * Find employee by RFC
   */
  async findByRfc(rfc: string, tenantId: string): Promise<Employee | null> {
    return this.employeeRepository.findOne({
      where: { rfc, tenantId },
    });
  }

  /**
   * Create new employee
   */
  async create(tenantId: string, dto: CreateEmployeeDto, createdById?: string): Promise<Employee> {
    // Check for existing code
    const existingCode = await this.findByCode(dto.codigo, tenantId);
    if (existingCode) {
      throw new Error('Ya existe un empleado con este codigo');
    }

    // Check for existing CURP
    if (dto.curp) {
      const existingCurp = await this.findByCurp(dto.curp, tenantId);
      if (existingCurp) {
        throw new Error('Ya existe un empleado con este CURP');
      }
    }

    const employee = this.employeeRepository.create({
      ...dto,
      tenantId,
      createdById,
      fechaNacimiento: dto.fechaNacimiento ? new Date(dto.fechaNacimiento) : undefined,
      fechaIngreso: new Date(dto.fechaIngreso),
      fechaBaja: dto.fechaBaja ? new Date(dto.fechaBaja) : undefined,
    });

    return this.employeeRepository.save(employee);
  }

  /**
   * Update employee
   */
  async update(id: string, tenantId: string, dto: UpdateEmployeeDto): Promise<Employee | null> {
    const employee = await this.findOne(id, tenantId);
    if (!employee) return null;

    // If changing code, check for duplicates
    if (dto.codigo && dto.codigo !== employee.codigo) {
      const existing = await this.findByCode(dto.codigo, tenantId);
      if (existing) {
        throw new Error('Ya existe un empleado con este codigo');
      }
    }

    // If changing CURP, check for duplicates
    if (dto.curp && dto.curp !== employee.curp) {
      const existing = await this.findByCurp(dto.curp, tenantId);
      if (existing && existing.id !== id) {
        throw new Error('Ya existe un empleado con este CURP');
      }
    }

    Object.assign(employee, {
      ...dto,
      fechaNacimiento: dto.fechaNacimiento ? new Date(dto.fechaNacimiento) : employee.fechaNacimiento,
      fechaIngreso: dto.fechaIngreso ? new Date(dto.fechaIngreso) : employee.fechaIngreso,
      fechaBaja: dto.fechaBaja ? new Date(dto.fechaBaja) : employee.fechaBaja,
    });

    return this.employeeRepository.save(employee);
  }

  /**
   * Delete employee (soft delete)
   */
  async delete(id: string, tenantId: string): Promise<boolean> {
    const employee = await this.findOne(id, tenantId);
    if (!employee) return false;

    // Instead of hard delete, mark as 'baja'
    employee.estado = 'baja';
    employee.fechaBaja = new Date();
    await this.employeeRepository.save(employee);

    return true;
  }

  /**
   * Get active employees
   */
  async getActive(tenantId: string): Promise<Employee[]> {
    return this.employeeRepository.find({
      where: { tenantId, estado: 'activo' },
      relations: ['puesto'],
      order: { apellidoPaterno: 'ASC', apellidoMaterno: 'ASC', nombre: 'ASC' },
    });
  }

  /**
   * Get employees by department
   */
  async getByDepartment(tenantId: string, departamento: string): Promise<Employee[]> {
    return this.employeeRepository.find({
      where: { tenantId, departamento, estado: 'activo' },
      relations: ['puesto'],
      order: { apellidoPaterno: 'ASC', nombre: 'ASC' },
    });
  }

  /**
   * Get employees by position
   */
  async getByPuesto(tenantId: string, puestoId: string): Promise<Employee[]> {
    return this.employeeRepository.find({
      where: { tenantId, puestoId, estado: 'activo' },
      order: { apellidoPaterno: 'ASC', nombre: 'ASC' },
    });
  }

  /**
   * Get employees with upcoming birthdays
   */
  async getUpcomingBirthdays(tenantId: string, daysAhead: number = 30): Promise<Employee[]> {
    const today = new Date();
    const endDate = new Date(today.getTime() + daysAhead * 24 * 60 * 60 * 1000);

    // This query is complex due to year-agnostic birthday matching
    // Simplified version - get all active employees with birthdays
    return this.employeeRepository
      .createQueryBuilder('employee')
      .where('employee.tenant_id = :tenantId', { tenantId })
      .andWhere('employee.estado = :estado', { estado: 'activo' })
      .andWhere('employee.fecha_nacimiento IS NOT NULL')
      .andWhere(
        `(EXTRACT(MONTH FROM employee.fecha_nacimiento) * 100 + EXTRACT(DAY FROM employee.fecha_nacimiento)) BETWEEN ` +
        `(EXTRACT(MONTH FROM CURRENT_DATE) * 100 + EXTRACT(DAY FROM CURRENT_DATE)) AND ` +
        `(EXTRACT(MONTH FROM (CURRENT_DATE + :days * INTERVAL '1 day')) * 100 + EXTRACT(DAY FROM (CURRENT_DATE + :days * INTERVAL '1 day')))`,
        { days: daysAhead }
      )
      .orderBy('EXTRACT(MONTH FROM employee.fecha_nacimiento)', 'ASC')
      .addOrderBy('EXTRACT(DAY FROM employee.fecha_nacimiento)', 'ASC')
      .getMany();
  }

  /**
   * Get employees with expiring documents (within days)
   */
  async getWithExpiringContracts(tenantId: string, daysAhead: number = 30): Promise<Employee[]> {
    const today = new Date();
    const endDate = new Date(today.getTime() + daysAhead * 24 * 60 * 60 * 1000);

    // Note: This would need contract entity relation for full implementation
    // Simplified to return employees with tipoContrato 'temporal' as a placeholder
    return this.employeeRepository.find({
      where: {
        tenantId,
        estado: 'activo',
        tipoContrato: 'temporal',
      },
      relations: ['puesto'],
      order: { apellidoPaterno: 'ASC' },
    });
  }

  /**
   * Get employee count by status
   */
  async getCountByStatus(tenantId: string): Promise<Record<string, number>> {
    const result = await this.employeeRepository
      .createQueryBuilder('employee')
      .select('employee.estado', 'estado')
      .addSelect('COUNT(*)', 'count')
      .where('employee.tenant_id = :tenantId', { tenantId })
      .groupBy('employee.estado')
      .getRawMany();

    return result.reduce((acc, row) => {
      acc[row.estado] = parseInt(row.count, 10);
      return acc;
    }, {} as Record<string, number>);
  }

  /**
   * Activate employee
   */
  async activate(id: string, tenantId: string): Promise<Employee | null> {
    const employee = await this.findOne(id, tenantId);
    if (!employee) return null;

    employee.estado = 'activo';
    employee.fechaBaja = null as unknown as Date;
    return this.employeeRepository.save(employee);
  }

  /**
   * Deactivate employee
   */
  async deactivate(id: string, tenantId: string): Promise<Employee | null> {
    const employee = await this.findOne(id, tenantId);
    if (!employee) return null;

    employee.estado = 'inactivo';
    return this.employeeRepository.save(employee);
  }

  /**
   * Terminate employee (dar de baja)
   */
  async terminate(id: string, tenantId: string, fechaBaja?: Date): Promise<Employee | null> {
    const employee = await this.findOne(id, tenantId);
    if (!employee) return null;

    employee.estado = 'baja';
    employee.fechaBaja = fechaBaja || new Date();
    return this.employeeRepository.save(employee);
  }
}
