/**
 * Rule Service
 * ERP Transportistas
 *
 * Business logic for dispatch and escalation rules.
 * Module: MAI-005 Despacho
 * Adapted from: erp-mecanicas-diesel MMD-011 RuleService
 */

import { Repository, DataSource } from 'typeorm';
import { ReglaDespacho, CondicionesDespachoTransporte } from '../entities/regla-despacho.entity';
import { ReglaEscalamiento, CanalNotificacion } from '../entities/regla-escalamiento.entity';

// DTOs
export interface CreateReglaDespachoDto {
  nombre: string;
  descripcion?: string;
  prioridad?: number;
  tipoViaje?: string;
  categoriaViaje?: string;
  condiciones: CondicionesDespachoTransporte;
  autoAsignar?: boolean;
  pesoAsignacion?: number;
  createdBy?: string;
}

export interface UpdateReglaDespachoDto {
  nombre?: string;
  descripcion?: string;
  prioridad?: number;
  tipoViaje?: string;
  categoriaViaje?: string;
  condiciones?: CondicionesDespachoTransporte;
  autoAsignar?: boolean;
  pesoAsignacion?: number;
  activo?: boolean;
}

export interface CreateReglaEscalamientoDto {
  nombre: string;
  descripcion?: string;
  dispararDespuesMinutos: number;
  dispararEstado?: string;
  dispararPrioridad?: string;
  escalarARol: string;
  escalarAUsuarios?: string[];
  canalNotificacion: CanalNotificacion;
  plantillaNotificacion?: string;
  datosNotificacion?: Record<string, any>;
  intervaloRepeticionMinutos?: number;
  maxEscalamientos?: number;
  createdBy?: string;
}

export interface UpdateReglaEscalamientoDto {
  nombre?: string;
  descripcion?: string;
  dispararDespuesMinutos?: number;
  dispararEstado?: string;
  dispararPrioridad?: string;
  escalarARol?: string;
  escalarAUsuarios?: string[];
  canalNotificacion?: CanalNotificacion;
  plantillaNotificacion?: string;
  datosNotificacion?: Record<string, any>;
  intervaloRepeticionMinutos?: number;
  maxEscalamientos?: number;
  activo?: boolean;
}

export interface ReglaCoincidente {
  regla: ReglaDespacho;
  score: number;
}

export class RuleService {
  private reglaDespachoRepository: Repository<ReglaDespacho>;
  private reglaEscalamientoRepository: Repository<ReglaEscalamiento>;

  constructor(dataSource: DataSource) {
    this.reglaDespachoRepository = dataSource.getRepository(ReglaDespacho);
    this.reglaEscalamientoRepository = dataSource.getRepository(ReglaEscalamiento);
  }

  // ==========================================
  // Reglas de Despacho
  // ==========================================

  async crearReglaDespacho(
    tenantId: string,
    dto: CreateReglaDespachoDto
  ): Promise<ReglaDespacho> {
    const regla = this.reglaDespachoRepository.create({
      tenantId,
      ...dto,
      prioridad: dto.prioridad ?? 0,
      activo: true,
    });
    return this.reglaDespachoRepository.save(regla);
  }

  async getReglaDespachoById(
    tenantId: string,
    id: string
  ): Promise<ReglaDespacho | null> {
    return this.reglaDespachoRepository.findOne({
      where: { id, tenantId },
    });
  }

  async actualizarReglaDespacho(
    tenantId: string,
    id: string,
    dto: UpdateReglaDespachoDto
  ): Promise<ReglaDespacho | null> {
    const regla = await this.getReglaDespachoById(tenantId, id);
    if (!regla) return null;

    Object.assign(regla, dto);
    return this.reglaDespachoRepository.save(regla);
  }

  async eliminarReglaDespacho(tenantId: string, id: string): Promise<boolean> {
    const result = await this.reglaDespachoRepository.delete({ id, tenantId });
    return (result.affected || 0) > 0;
  }

  async getReglasDespachoActivas(tenantId: string): Promise<ReglaDespacho[]> {
    return this.reglaDespachoRepository.find({
      where: { tenantId, activo: true },
      order: { prioridad: 'DESC' },
    });
  }

  async getReglasDespachoCoincidentes(
    tenantId: string,
    tipoViaje?: string,
    categoriaViaje?: string
  ): Promise<ReglaDespacho[]> {
    const qb = this.reglaDespachoRepository
      .createQueryBuilder('regla')
      .where('regla.tenant_id = :tenantId', { tenantId })
      .andWhere('regla.activo = :activo', { activo: true });

    // Match rules that apply to this trip type or are generic
    if (tipoViaje) {
      qb.andWhere(
        '(regla.tipo_viaje IS NULL OR regla.tipo_viaje = :tipoViaje)',
        { tipoViaje }
      );
    }

    if (categoriaViaje) {
      qb.andWhere(
        '(regla.categoria_viaje IS NULL OR regla.categoria_viaje = :categoriaViaje)',
        { categoriaViaje }
      );
    }

    return qb.orderBy('regla.prioridad', 'DESC').getMany();
  }

  /**
   * Evaluate rules and return applicable ones with scores
   * Transport-specific evaluation
   */
  async evaluarReglas(
    tenantId: string,
    contexto: {
      tipoViaje?: string;
      categoriaViaje?: string;
      certificacionesOperador?: string[];
      capacidadUnidad?: string;
      distanciaKm?: number;
      codigoZona?: string;
      pesoKg?: number;
      requiereFrio?: boolean;
      requiereLicenciaFederal?: boolean;
      requiereCertificadoMP?: boolean;
    }
  ): Promise<ReglaCoincidente[]> {
    const reglas = await this.getReglasDespachoCoincidentes(
      tenantId,
      contexto.tipoViaje,
      contexto.categoriaViaje
    );

    const coincidentes: ReglaCoincidente[] = [];

    for (const regla of reglas) {
      let score = regla.pesoAsignacion;
      let aplicable = true;
      const condiciones = regla.condiciones;

      // Check certification requirements
      if (condiciones.requiereCertificaciones && condiciones.requiereCertificaciones.length > 0) {
        const tieneCertificaciones = condiciones.requiereCertificaciones.every((cert) =>
          contexto.certificacionesOperador?.includes(cert)
        );
        if (!tieneCertificaciones) aplicable = false;
      }

      // Check unit capacity
      if (condiciones.capacidadMinimaUnidad) {
        const ordenCapacidad = ['light', 'medium', 'heavy'];
        const minIdx = ordenCapacidad.indexOf(condiciones.capacidadMinimaUnidad);
        const actualIdx = ordenCapacidad.indexOf(contexto.capacidadUnidad || 'light');
        if (actualIdx < minIdx) aplicable = false;
      }

      // Check distance
      if (condiciones.distanciaMaximaKm && contexto.distanciaKm) {
        if (contexto.distanciaKm > condiciones.distanciaMaximaKm) {
          aplicable = false;
        } else {
          // Bonus for being within distance
          score += Math.round((1 - contexto.distanciaKm / condiciones.distanciaMaximaKm) * 20);
        }
      }

      // Check zone
      if (condiciones.codigosZona && condiciones.codigosZona.length > 0) {
        if (!contexto.codigoZona || !condiciones.codigosZona.includes(contexto.codigoZona)) {
          aplicable = false;
        }
      }

      // Transport-specific: Check weight
      if (condiciones.pesoMaximoKg && contexto.pesoKg) {
        if (contexto.pesoKg > condiciones.pesoMaximoKg) {
          aplicable = false;
        }
      }

      // Transport-specific: Check refrigeration
      if (condiciones.requiereRefrigeracion && !contexto.requiereFrio) {
        // Rule requires refrigeration but context doesn't need it - skip this rule
      }

      // Transport-specific: Check federal license
      if (condiciones.requiereLicenciaFederal && !contexto.requiereLicenciaFederal) {
        aplicable = false;
      }

      // Transport-specific: Check hazmat certification
      if (condiciones.requiereCertificadoMP && !contexto.requiereCertificadoMP) {
        aplicable = false;
      }

      if (aplicable) {
        coincidentes.push({ regla, score });
      }
    }

    return coincidentes.sort((a, b) => b.score - a.score);
  }

  async listarReglasDespacho(
    tenantId: string,
    soloActivas: boolean = false,
    paginacion = { pagina: 1, limite: 20 }
  ) {
    const qb = this.reglaDespachoRepository
      .createQueryBuilder('regla')
      .where('regla.tenant_id = :tenantId', { tenantId });

    if (soloActivas) {
      qb.andWhere('regla.activo = :activo', { activo: true });
    }

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

    const [data, total] = await qb
      .orderBy('regla.prioridad', 'DESC')
      .skip(skip)
      .take(paginacion.limite)
      .getManyAndCount();

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

  // ==========================================
  // Reglas de Escalamiento
  // ==========================================

  async crearReglaEscalamiento(
    tenantId: string,
    dto: CreateReglaEscalamientoDto
  ): Promise<ReglaEscalamiento> {
    const regla = this.reglaEscalamientoRepository.create({
      tenantId,
      ...dto,
      activo: true,
    });
    return this.reglaEscalamientoRepository.save(regla);
  }

  async getReglaEscalamientoById(
    tenantId: string,
    id: string
  ): Promise<ReglaEscalamiento | null> {
    return this.reglaEscalamientoRepository.findOne({
      where: { id, tenantId },
    });
  }

  async actualizarReglaEscalamiento(
    tenantId: string,
    id: string,
    dto: UpdateReglaEscalamientoDto
  ): Promise<ReglaEscalamiento | null> {
    const regla = await this.getReglaEscalamientoById(tenantId, id);
    if (!regla) return null;

    Object.assign(regla, dto);
    return this.reglaEscalamientoRepository.save(regla);
  }

  async eliminarReglaEscalamiento(tenantId: string, id: string): Promise<boolean> {
    const result = await this.reglaEscalamientoRepository.delete({ id, tenantId });
    return (result.affected || 0) > 0;
  }

  async getReglasEscalamientoActivas(tenantId: string): Promise<ReglaEscalamiento[]> {
    return this.reglaEscalamientoRepository.find({
      where: { tenantId, activo: true },
      order: { dispararDespuesMinutos: 'ASC' },
    });
  }

  /**
   * Get escalation rules that should trigger based on elapsed time
   */
  async getReglasEscalamientoDisparadas(
    tenantId: string,
    minutosTranscurridos: number,
    estadoViaje?: string,
    prioridadViaje?: string
  ): Promise<ReglaEscalamiento[]> {
    const qb = this.reglaEscalamientoRepository
      .createQueryBuilder('regla')
      .where('regla.tenant_id = :tenantId', { tenantId })
      .andWhere('regla.activo = :activo', { activo: true })
      .andWhere('regla.disparar_despues_minutos <= :minutosTranscurridos', { minutosTranscurridos });

    if (estadoViaje) {
      qb.andWhere(
        '(regla.disparar_estado IS NULL OR regla.disparar_estado = :estadoViaje)',
        { estadoViaje }
      );
    }

    if (prioridadViaje) {
      qb.andWhere(
        '(regla.disparar_prioridad IS NULL OR regla.disparar_prioridad = :prioridadViaje)',
        { prioridadViaje }
      );
    }

    return qb.orderBy('regla.disparar_despues_minutos', 'ASC').getMany();
  }

  async listarReglasEscalamiento(
    tenantId: string,
    soloActivas: boolean = false,
    paginacion = { pagina: 1, limite: 20 }
  ) {
    const qb = this.reglaEscalamientoRepository
      .createQueryBuilder('regla')
      .where('regla.tenant_id = :tenantId', { tenantId });

    if (soloActivas) {
      qb.andWhere('regla.activo = :activo', { activo: true });
    }

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

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

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