/**
 * LeaveTypes Service
 * @module HR
 */

import { Repository, FindOptionsWhere, ILike } from 'typeorm';
import { LeaveType, LeaveTypeCategory, AllocationType } from '../entities/leave-type.entity';
import { CreateLeaveTypeDto, UpdateLeaveTypeDto } from '../dto';

export interface LeaveTypeSearchParams {
  tenantId: string;
  companyId?: string;
  search?: string;
  leaveCategory?: LeaveTypeCategory;
  allocationType?: AllocationType;
  isActive?: boolean;
  requiresApproval?: boolean;
  isPaid?: boolean;
  limit?: number;
  offset?: number;
}

export class LeaveTypesService {
  constructor(private readonly leaveTypeRepository: Repository<LeaveType>) {}

  /**
   * Find all leave types with filters
   */
  async findAll(params: LeaveTypeSearchParams): Promise<{ data: LeaveType[]; total: number }> {
    const {
      tenantId,
      companyId,
      search,
      leaveCategory,
      allocationType,
      isActive,
      requiresApproval,
      isPaid,
      limit = 50,
      offset = 0,
    } = params;

    const where: FindOptionsWhere<LeaveType>[] = [];
    const baseWhere: FindOptionsWhere<LeaveType> = { tenantId };

    if (companyId) {
      baseWhere.companyId = companyId;
    }

    if (leaveCategory) {
      baseWhere.leaveCategory = leaveCategory;
    }

    if (allocationType) {
      baseWhere.allocationType = allocationType;
    }

    if (isActive !== undefined) {
      baseWhere.isActive = isActive;
    }

    if (requiresApproval !== undefined) {
      baseWhere.requiresApproval = requiresApproval;
    }

    if (isPaid !== undefined) {
      baseWhere.isPaid = isPaid;
    }

    if (search) {
      where.push(
        { ...baseWhere, name: ILike(`%${search}%`) },
        { ...baseWhere, code: ILike(`%${search}%`) }
      );
    } else {
      where.push(baseWhere);
    }

    const [data, total] = await this.leaveTypeRepository.findAndCount({
      where,
      take: limit,
      skip: offset,
      order: { name: 'ASC' },
    });

    return { data, total };
  }

  /**
   * Find one leave type by ID
   */
  async findOne(id: string, tenantId: string): Promise<LeaveType | null> {
    return this.leaveTypeRepository.findOne({
      where: { id, tenantId },
    });
  }

  /**
   * Find leave type by code
   */
  async findByCode(code: string, tenantId: string, companyId: string): Promise<LeaveType | null> {
    return this.leaveTypeRepository.findOne({
      where: { code, tenantId, companyId },
    });
  }

  /**
   * Create new leave type
   */
  async create(tenantId: string, dto: CreateLeaveTypeDto): Promise<LeaveType> {
    // Check for existing code
    const existingCode = await this.findByCode(dto.code, tenantId, dto.companyId);
    if (existingCode) {
      throw new Error('Ya existe un tipo de ausencia con este codigo');
    }

    const leaveType = this.leaveTypeRepository.create({
      ...dto,
      tenantId,
    });

    return this.leaveTypeRepository.save(leaveType);
  }

  /**
   * Update leave type
   */
  async update(id: string, tenantId: string, dto: UpdateLeaveTypeDto): Promise<LeaveType | null> {
    const leaveType = await this.findOne(id, tenantId);
    if (!leaveType) return null;

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

    Object.assign(leaveType, dto);
    return this.leaveTypeRepository.save(leaveType);
  }

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

    // Check if has leaves or allocations associated
    const leaveTypeWithRelations = await this.leaveTypeRepository.findOne({
      where: { id, tenantId },
      relations: ['leaves', 'allocations'],
    });

    if (leaveTypeWithRelations?.leaves && leaveTypeWithRelations.leaves.length > 0) {
      throw new Error('No se puede eliminar un tipo de ausencia con solicitudes asociadas');
    }

    if (leaveTypeWithRelations?.allocations && leaveTypeWithRelations.allocations.length > 0) {
      throw new Error('No se puede eliminar un tipo de ausencia con asignaciones asociadas');
    }

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

  /**
   * Get active leave types
   */
  async getActive(tenantId: string, companyId?: string): Promise<LeaveType[]> {
    const where: FindOptionsWhere<LeaveType> = {
      tenantId,
      isActive: true,
    };

    if (companyId) {
      where.companyId = companyId;
    }

    return this.leaveTypeRepository.find({
      where,
      order: { name: 'ASC' },
    });
  }

  /**
   * Get leave types by category
   */
  async getByCategory(tenantId: string, leaveCategory: LeaveTypeCategory): Promise<LeaveType[]> {
    return this.leaveTypeRepository.find({
      where: { tenantId, leaveCategory, isActive: true },
      order: { name: 'ASC' },
    });
  }

  /**
   * Get paid leave types
   */
  async getPaidTypes(tenantId: string, companyId?: string): Promise<LeaveType[]> {
    const where: FindOptionsWhere<LeaveType> = {
      tenantId,
      isPaid: true,
      isActive: true,
    };

    if (companyId) {
      where.companyId = companyId;
    }

    return this.leaveTypeRepository.find({
      where,
      order: { name: 'ASC' },
    });
  }

  /**
   * Get unpaid leave types
   */
  async getUnpaidTypes(tenantId: string, companyId?: string): Promise<LeaveType[]> {
    const where: FindOptionsWhere<LeaveType> = {
      tenantId,
      isPaid: false,
      isActive: true,
    };

    if (companyId) {
      where.companyId = companyId;
    }

    return this.leaveTypeRepository.find({
      where,
      order: { name: 'ASC' },
    });
  }

  /**
   * Get leave types requiring approval
   */
  async getRequiringApproval(tenantId: string): Promise<LeaveType[]> {
    return this.leaveTypeRepository.find({
      where: { tenantId, requiresApproval: true, isActive: true },
      order: { name: 'ASC' },
    });
  }

  /**
   * Get leave types requiring documentation
   */
  async getRequiringDocument(tenantId: string): Promise<LeaveType[]> {
    return this.leaveTypeRepository.find({
      where: { tenantId, requiresDocument: true, isActive: true },
      order: { name: 'ASC' },
    });
  }

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

    leaveType.isActive = true;
    return this.leaveTypeRepository.save(leaveType);
  }

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

    leaveType.isActive = false;
    return this.leaveTypeRepository.save(leaveType);
  }

  /**
   * Get count by category
   */
  async getCountByCategory(tenantId: string): Promise<Record<string, number>> {
    const result = await this.leaveTypeRepository
      .createQueryBuilder('leaveType')
      .select('leaveType.leave_category', 'category')
      .addSelect('COUNT(*)', 'count')
      .where('leaveType.tenant_id = :tenantId', { tenantId })
      .andWhere('leaveType.is_active = true')
      .groupBy('leaveType.leave_category')
      .getRawMany();

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