/**
 * Contracts Service
 * @module HR
 */

import { Repository, FindOptionsWhere, LessThanOrEqual, MoreThanOrEqual, Between, In } from 'typeorm';
import { Contract, ContractStatus, ContractType } from '../entities/contract.entity';
import { CreateContractDto, UpdateContractDto, TerminateContractDto } from '../dto';

export interface ContractSearchParams {
  tenantId: string;
  companyId?: string;
  employeeId?: string;
  search?: string;
  contractType?: ContractType;
  status?: ContractStatus;
  departmentId?: string;
  dateStartFrom?: Date;
  dateStartTo?: Date;
  expiringWithinDays?: number;
  limit?: number;
  offset?: number;
}

export class ContractsService {
  constructor(private readonly contractRepository: Repository<Contract>) {}

  /**
   * Find all contracts with filters
   */
  async findAll(params: ContractSearchParams): Promise<{ data: Contract[]; total: number }> {
    const {
      tenantId,
      companyId,
      employeeId,
      search,
      contractType,
      status,
      departmentId,
      dateStartFrom,
      dateStartTo,
      expiringWithinDays,
      limit = 50,
      offset = 0,
    } = params;

    const queryBuilder = this.contractRepository
      .createQueryBuilder('contract')
      .leftJoinAndSelect('contract.department', 'department')
      .where('contract.tenant_id = :tenantId', { tenantId });

    if (companyId) {
      queryBuilder.andWhere('contract.company_id = :companyId', { companyId });
    }

    if (employeeId) {
      queryBuilder.andWhere('contract.employee_id = :employeeId', { employeeId });
    }

    if (search) {
      queryBuilder.andWhere(
        '(contract.name ILIKE :search OR contract.reference ILIKE :search)',
        { search: `%${search}%` }
      );
    }

    if (contractType) {
      queryBuilder.andWhere('contract.contract_type = :contractType', { contractType });
    }

    if (status) {
      queryBuilder.andWhere('contract.status = :status', { status });
    }

    if (departmentId) {
      queryBuilder.andWhere('contract.department_id = :departmentId', { departmentId });
    }

    if (dateStartFrom) {
      queryBuilder.andWhere('contract.date_start >= :dateStartFrom', { dateStartFrom });
    }

    if (dateStartTo) {
      queryBuilder.andWhere('contract.date_start <= :dateStartTo', { dateStartTo });
    }

    if (expiringWithinDays) {
      const today = new Date();
      const futureDate = new Date(today.getTime() + expiringWithinDays * 24 * 60 * 60 * 1000);
      queryBuilder.andWhere('contract.date_end IS NOT NULL');
      queryBuilder.andWhere('contract.date_end BETWEEN :today AND :futureDate', { today, futureDate });
      queryBuilder.andWhere('contract.status = :activeStatus', { activeStatus: 'active' });
    }

    queryBuilder.orderBy('contract.date_start', 'DESC');

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

    return { data, total };
  }

  /**
   * Find one contract by ID
   */
  async findOne(id: string, tenantId: string): Promise<Contract | null> {
    return this.contractRepository.findOne({
      where: { id, tenantId },
      relations: ['department'],
    });
  }

  /**
   * Find active contract for employee
   */
  async findActiveByEmployee(employeeId: string, tenantId: string): Promise<Contract | null> {
    return this.contractRepository.findOne({
      where: { employeeId, tenantId, status: 'active' },
      relations: ['department'],
    });
  }

  /**
   * Find all contracts for employee
   */
  async findByEmployee(employeeId: string, tenantId: string): Promise<Contract[]> {
    return this.contractRepository.find({
      where: { employeeId, tenantId },
      relations: ['department'],
      order: { dateStart: 'DESC' },
    });
  }

  /**
   * Create new contract
   */
  async create(tenantId: string, dto: CreateContractDto): Promise<Contract> {
    // Check if employee already has an active contract
    if (dto.status === 'active') {
      const existingActive = await this.findActiveByEmployee(dto.employeeId, tenantId);
      if (existingActive) {
        throw new Error('El empleado ya tiene un contrato activo');
      }
    }

    const contract = this.contractRepository.create({
      ...dto,
      tenantId,
      dateStart: new Date(dto.dateStart),
      dateEnd: dto.dateEnd ? new Date(dto.dateEnd) : null,
      trialDateEnd: dto.trialDateEnd ? new Date(dto.trialDateEnd) : null,
    });

    return this.contractRepository.save(contract);
  }

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

    // If activating and employee has another active contract
    if (dto.status === 'active' && contract.status !== 'active') {
      const existingActive = await this.findActiveByEmployee(contract.employeeId, tenantId);
      if (existingActive && existingActive.id !== id) {
        throw new Error('El empleado ya tiene un contrato activo');
      }
    }

    Object.assign(contract, {
      ...dto,
      dateStart: dto.dateStart ? new Date(dto.dateStart) : contract.dateStart,
      dateEnd: dto.dateEnd ? new Date(dto.dateEnd) : contract.dateEnd,
      trialDateEnd: dto.trialDateEnd ? new Date(dto.trialDateEnd) : contract.trialDateEnd,
    });

    return this.contractRepository.save(contract);
  }

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

    if (contract.status !== 'draft') {
      throw new Error('Solo se pueden eliminar contratos en estado borrador');
    }

    const result = await this.contractRepository.delete(id);
    return (result.affected ?? 0) > 0;
  }

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

    if (contract.status !== 'draft') {
      throw new Error('Solo se pueden activar contratos en estado borrador');
    }

    // Check if employee has another active contract
    const existingActive = await this.findActiveByEmployee(contract.employeeId, tenantId);
    if (existingActive) {
      throw new Error('El empleado ya tiene un contrato activo. Termine el contrato actual primero.');
    }

    contract.status = 'active';
    contract.activatedAt = new Date();
    return this.contractRepository.save(contract);
  }

  /**
   * Terminate contract
   */
  async terminate(
    id: string,
    tenantId: string,
    dto: TerminateContractDto,
    terminatedBy?: string
  ): Promise<Contract | null> {
    const contract = await this.findOne(id, tenantId);
    if (!contract) return null;

    if (contract.status !== 'active') {
      throw new Error('Solo se pueden terminar contratos activos');
    }

    contract.status = 'terminated';
    contract.terminatedAt = new Date();
    contract.terminatedBy = terminatedBy || null;
    contract.terminationReason = dto.terminationReason;

    return this.contractRepository.save(contract);
  }

  /**
   * Mark contract as expired
   */
  async markExpired(id: string, tenantId: string): Promise<Contract | null> {
    const contract = await this.findOne(id, tenantId);
    if (!contract) return null;

    contract.status = 'expired';
    return this.contractRepository.save(contract);
  }

  /**
   * Cancel contract
   */
  async cancel(id: string, tenantId: string): Promise<Contract | null> {
    const contract = await this.findOne(id, tenantId);
    if (!contract) return null;

    if (!['draft', 'active'].includes(contract.status)) {
      throw new Error('Solo se pueden cancelar contratos en borrador o activos');
    }

    contract.status = 'cancelled';
    return this.contractRepository.save(contract);
  }

  /**
   * Get expiring contracts
   */
  async getExpiring(tenantId: string, daysAhead: number = 30): Promise<Contract[]> {
    const today = new Date();
    const futureDate = new Date(today.getTime() + daysAhead * 24 * 60 * 60 * 1000);

    return this.contractRepository.find({
      where: {
        tenantId,
        status: 'active',
        dateEnd: Between(today, futureDate),
      },
      relations: ['department'],
      order: { dateEnd: 'ASC' },
    });
  }

  /**
   * Get contracts in trial period
   */
  async getInTrialPeriod(tenantId: string): Promise<Contract[]> {
    const today = new Date();

    return this.contractRepository.find({
      where: {
        tenantId,
        status: 'active',
        trialDateEnd: MoreThanOrEqual(today),
      },
      relations: ['department'],
      order: { trialDateEnd: 'ASC' },
    });
  }

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

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

  /**
   * Get contract count by type
   */
  async getCountByType(tenantId: string): Promise<Record<string, number>> {
    const result = await this.contractRepository
      .createQueryBuilder('contract')
      .select('contract.contract_type', 'contractType')
      .addSelect('COUNT(*)', 'count')
      .where('contract.tenant_id = :tenantId', { tenantId })
      .andWhere('contract.status = :status', { status: 'active' })
      .groupBy('contract.contract_type')
      .getRawMany();

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

  /**
   * Process expired contracts (batch operation)
   */
  async processExpiredContracts(tenantId: string): Promise<number> {
    const today = new Date();

    const result = await this.contractRepository.update(
      {
        tenantId,
        status: 'active',
        dateEnd: LessThanOrEqual(today),
      },
      {
        status: 'expired',
      }
    );

    return result.affected ?? 0;
  }
}
