/**
 * Certificacion Service
 * ERP Transportistas
 *
 * Business logic for operator certifications management.
 * Module: MAI-005 Despacho
 * Adapted from: erp-mecanicas-diesel MMD-011 SkillService
 */

import { Repository, DataSource } from 'typeorm';
import { OperadorCertificacion, NivelCertificacion } from '../entities/operador-certificacion.entity';

// DTOs
export interface CreateCertificacionDto {
  operadorId: string;
  codigoCertificacion: string;
  nombreCertificacion: string;
  descripcion?: string;
  nivel?: NivelCertificacion;
  numeroCertificado?: string;
  fechaCertificacion?: Date;
  vigenciaHasta?: Date;
  documentoUrl?: string;
}

export interface UpdateCertificacionDto {
  nombreCertificacion?: string;
  descripcion?: string;
  nivel?: NivelCertificacion;
  numeroCertificado?: string;
  fechaCertificacion?: Date;
  vigenciaHasta?: Date;
  documentoUrl?: string;
  activa?: boolean;
}

export interface FiltrosCertificacion {
  operadorId?: string;
  codigoCertificacion?: string;
  nivel?: NivelCertificacion;
  activa?: boolean;
  porVencerDias?: number;
}

export interface MatrizCertificaciones {
  certificaciones: {
    codigo: string;
    nombre: string;
    operadoresCount: number;
    porNivel: Record<NivelCertificacion, number>;
  }[];
  operadores: {
    id: string;
    certificacionesCount: number;
    certificaciones: string[];
  }[];
}

export class CertificacionService {
  private certificacionRepository: Repository<OperadorCertificacion>;

  constructor(dataSource: DataSource) {
    this.certificacionRepository = dataSource.getRepository(OperadorCertificacion);
  }

  /**
   * Add certification to operator
   */
  async agregarCertificacion(tenantId: string, dto: CreateCertificacionDto): Promise<OperadorCertificacion> {
    // Check for existing certification
    const existente = await this.certificacionRepository.findOne({
      where: {
        tenantId,
        operadorId: dto.operadorId,
        codigoCertificacion: dto.codigoCertificacion,
      },
    });

    if (existente) {
      throw new Error(
        `Operador ${dto.operadorId} ya tiene certificacion ${dto.codigoCertificacion}`
      );
    }

    const certificacion = this.certificacionRepository.create({
      tenantId,
      ...dto,
      nivel: dto.nivel || NivelCertificacion.BASICO,
      activa: true,
    });

    return this.certificacionRepository.save(certificacion);
  }

  /**
   * Get certification by ID
   */
  async getById(tenantId: string, id: string): Promise<OperadorCertificacion | null> {
    return this.certificacionRepository.findOne({
      where: { id, tenantId },
    });
  }

  /**
   * Update certification
   */
  async actualizarCertificacion(
    tenantId: string,
    id: string,
    dto: UpdateCertificacionDto
  ): Promise<OperadorCertificacion | null> {
    const certificacion = await this.getById(tenantId, id);
    if (!certificacion) return null;

    Object.assign(certificacion, dto);
    return this.certificacionRepository.save(certificacion);
  }

  /**
   * Deactivate certification
   */
  async desactivarCertificacion(tenantId: string, id: string): Promise<boolean> {
    const certificacion = await this.getById(tenantId, id);
    if (!certificacion) return false;

    certificacion.activa = false;
    await this.certificacionRepository.save(certificacion);
    return true;
  }

  /**
   * Get certifications for operator
   */
  async getCertificacionesOperador(
    tenantId: string,
    operadorId: string,
    soloActivas: boolean = true
  ): Promise<OperadorCertificacion[]> {
    const where: any = { tenantId, operadorId };
    if (soloActivas) {
      where.activa = true;
    }

    return this.certificacionRepository.find({
      where,
      order: { codigoCertificacion: 'ASC' },
    });
  }

  /**
   * Get operators with specific certification
   */
  async getOperadoresConCertificacion(
    tenantId: string,
    codigoCertificacion: string,
    nivelMinimo?: NivelCertificacion
  ): Promise<OperadorCertificacion[]> {
    const qb = this.certificacionRepository
      .createQueryBuilder('cert')
      .where('cert.tenant_id = :tenantId', { tenantId })
      .andWhere('cert.codigo_certificacion = :codigoCertificacion', { codigoCertificacion })
      .andWhere('cert.activa = :activa', { activa: true });

    if (nivelMinimo) {
      const ordenNivel = [
        NivelCertificacion.BASICO,
        NivelCertificacion.INTERMEDIO,
        NivelCertificacion.AVANZADO,
        NivelCertificacion.EXPERTO,
        NivelCertificacion.FEDERAL,
      ];
      const minIndex = ordenNivel.indexOf(nivelMinimo);
      const nivelesValidos = ordenNivel.slice(minIndex);
      qb.andWhere('cert.nivel IN (:...niveles)', { niveles: nivelesValidos });
    }

    return qb.orderBy('cert.nivel', 'DESC').getMany();
  }

  /**
   * Check if operator has required certifications
   */
  async validarCertificacionesOperador(
    tenantId: string,
    operadorId: string,
    certificacionesRequeridas: string[],
    nivelMinimo?: NivelCertificacion
  ): Promise<{ valido: boolean; faltantes: string[] }> {
    const certificaciones = await this.getCertificacionesOperador(tenantId, operadorId);
    const ordenNivel = [
      NivelCertificacion.BASICO,
      NivelCertificacion.INTERMEDIO,
      NivelCertificacion.AVANZADO,
      NivelCertificacion.EXPERTO,
      NivelCertificacion.FEDERAL,
    ];

    const codigosCert = new Set(
      certificaciones
        .filter((c) => {
          if (!nivelMinimo) return true;
          return ordenNivel.indexOf(c.nivel) >= ordenNivel.indexOf(nivelMinimo);
        })
        .map((c) => c.codigoCertificacion)
    );

    const faltantes = certificacionesRequeridas.filter((codigo) => !codigosCert.has(codigo));

    return {
      valido: faltantes.length === 0,
      faltantes,
    };
  }

  /**
   * Get expiring certifications
   */
  async getCertificacionesPorVencer(
    tenantId: string,
    diasAnticipacion: number = 30
  ): Promise<OperadorCertificacion[]> {
    const fechaVencimiento = new Date();
    fechaVencimiento.setDate(fechaVencimiento.getDate() + diasAnticipacion);

    return this.certificacionRepository
      .createQueryBuilder('cert')
      .where('cert.tenant_id = :tenantId', { tenantId })
      .andWhere('cert.activa = :activa', { activa: true })
      .andWhere('cert.vigencia_hasta IS NOT NULL')
      .andWhere('cert.vigencia_hasta <= :fechaVencimiento', { fechaVencimiento })
      .orderBy('cert.vigencia_hasta', 'ASC')
      .getMany();
  }

  /**
   * Verify certification (admin approval)
   */
  async verificarCertificacion(
    tenantId: string,
    id: string,
    verificadoPor: string
  ): Promise<OperadorCertificacion | null> {
    const certificacion = await this.getById(tenantId, id);
    if (!certificacion) return null;

    certificacion.verificadoPor = verificadoPor;
    certificacion.verificadoEn = new Date();
    return this.certificacionRepository.save(certificacion);
  }

  /**
   * Get certification matrix (overview of all certifications and operators)
   */
  async getMatrizCertificaciones(tenantId: string): Promise<MatrizCertificaciones> {
    const todasCertificaciones = await this.certificacionRepository.find({
      where: { tenantId, activa: true },
    });

    // Group by certification code
    const certMap = new Map<
      string,
      { nombre: string; operadores: Set<string>; niveles: Record<NivelCertificacion, number> }
    >();

    // Group by operator
    const operadorMap = new Map<string, Set<string>>();

    for (const cert of todasCertificaciones) {
      // Certification aggregation
      if (!certMap.has(cert.codigoCertificacion)) {
        certMap.set(cert.codigoCertificacion, {
          nombre: cert.nombreCertificacion,
          operadores: new Set(),
          niveles: {
            [NivelCertificacion.BASICO]: 0,
            [NivelCertificacion.INTERMEDIO]: 0,
            [NivelCertificacion.AVANZADO]: 0,
            [NivelCertificacion.EXPERTO]: 0,
            [NivelCertificacion.FEDERAL]: 0,
          },
        });
      }
      const certData = certMap.get(cert.codigoCertificacion)!;
      certData.operadores.add(cert.operadorId);
      certData.niveles[cert.nivel]++;

      // Operator aggregation
      if (!operadorMap.has(cert.operadorId)) {
        operadorMap.set(cert.operadorId, new Set());
      }
      operadorMap.get(cert.operadorId)!.add(cert.codigoCertificacion);
    }

    return {
      certificaciones: Array.from(certMap.entries()).map(([codigo, data]) => ({
        codigo,
        nombre: data.nombre,
        operadoresCount: data.operadores.size,
        porNivel: data.niveles,
      })),
      operadores: Array.from(operadorMap.entries()).map(([id, certs]) => ({
        id,
        certificacionesCount: certs.size,
        certificaciones: Array.from(certs),
      })),
    };
  }

  /**
   * List all certifications with filters
   */
  async listar(
    tenantId: string,
    filtros: FiltrosCertificacion = {},
    paginacion = { pagina: 1, limite: 20 }
  ) {
    const qb = this.certificacionRepository
      .createQueryBuilder('cert')
      .where('cert.tenant_id = :tenantId', { tenantId });

    if (filtros.operadorId) {
      qb.andWhere('cert.operador_id = :operadorId', {
        operadorId: filtros.operadorId,
      });
    }
    if (filtros.codigoCertificacion) {
      qb.andWhere('cert.codigo_certificacion = :codigoCertificacion', {
        codigoCertificacion: filtros.codigoCertificacion,
      });
    }
    if (filtros.nivel) {
      qb.andWhere('cert.nivel = :nivel', { nivel: filtros.nivel });
    }
    if (filtros.activa !== undefined) {
      qb.andWhere('cert.activa = :activa', { activa: filtros.activa });
    }
    if (filtros.porVencerDias) {
      const fechaVencimiento = new Date();
      fechaVencimiento.setDate(fechaVencimiento.getDate() + filtros.porVencerDias);
      qb.andWhere('cert.vigencia_hasta IS NOT NULL');
      qb.andWhere('cert.vigencia_hasta <= :fechaVencimiento', { fechaVencimiento });
    }

    const skip = (paginacion.pagina - 1) * paginacion.limite;

    const [data, total] = await qb
      .orderBy('cert.codigo_certificacion', 'ASC')
      .skip(skip)
      .take(paginacion.limite)
      .getManyAndCount();

    return {
      data,
      total,
      pagina: paginacion.pagina,
      limite: paginacion.limite,
      totalPaginas: Math.ceil(total / paginacion.limite),
    };
  }
}
