import { Injectable, Logger } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from 'src/prisma/prisma.service';
import { RrhhContextService } from '../shared/rrhh-context.service';

@Injectable()
export class AlertsService {
  private readonly logger = new Logger(AlertsService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly rrhhContext: RrhhContextService,
  ) {}

  async detectarYGuardarAlertas(empresaId: string): Promise<number> {
    this.logger.log(`Detectando alertas para empresa ${empresaId}`);

    const config = await this.prisma.ai_empresa_config.findUnique({
      where: { empresa_id: empresaId },
    });

    const umbralMora = config?.umbral_alerta_mora ?? 30;
    const umbralStock = config?.umbral_stock_bajo ?? 5;
    const rrhhDiaCorte = (config as { rrhh_dia_corte_liquidacion?: number } | null)?.rrhh_dia_corte_liquidacion ?? 5;
    const rrhhAlertasOn = (config as { rrhh_alertas_habilitadas?: boolean } | null)?.rrhh_alertas_habilitadas ?? true;

    const alertas: {
      empresa_id: string;
      tipo: string;
      criticidad: string;
      titulo: string;
      descripcion: string;
      accion_sugerida?: string;
      entidad_tipo?: string;
      entidad_id?: string;
      canales_enviados: object;
    }[] = [];

    // ── 1. Mora crítica ───────────────────────────────────────────
    const clientesMora = await this.prisma.$queryRaw<{
      cliente_id: string;
      nombre: string;
      dias_mora: number;
      deuda: Prisma.Decimal;
    }[]>`
      SELECT
        c.id AS cliente_id,
        p.razon_social AS nombre,
        MAX(EXTRACT(DAY FROM NOW() - fc.dvenccuo))::int AS dias_mora,
        COALESCE(SUM(fc.saldo_pendiente), 0) AS deuda
      FROM factura_cuotas fc
      JOIN factura_cab cab ON cab.id = fc.factura_cab_id
      JOIN clientes c ON c.id = cab.cliente_id
      JOIN personas p ON p.id = c.persona_id
      WHERE cab.empresa_id = ${empresaId}::uuid
        AND fc.estado = 'pendiente'
        AND fc.dvenccuo < NOW()
      GROUP BY c.id, p.razon_social
      HAVING MAX(EXTRACT(DAY FROM NOW() - fc.dvenccuo)) >= ${umbralMora}
      ORDER BY dias_mora DESC
      LIMIT 20
    `;

    for (const c of clientesMora) {
      alertas.push({
        empresa_id: empresaId,
        tipo: 'mora_critica',
        criticidad: c.dias_mora >= 60 ? 'alta' : 'media',
        titulo: `Mora crítica: ${c.nombre}`,
        descripcion: `${c.nombre} lleva ${c.dias_mora} días sin pagar. Deuda acumulada: Gs. ${Math.round(Number(c.deuda)).toLocaleString('es-PY')}.`,
        accion_sugerida: `Coordinar visita del cobrador esta semana. Si supera 60 días considerar gestión judicial.`,
        entidad_tipo: 'cliente',
        entidad_id: c.cliente_id,
        canales_enviados: { dashboard: true, email: false, whatsapp: false },
      });
    }

    // ── 2. Límite de crédito superado ─────────────────────────────
    const clientesLimite = await this.prisma.$queryRaw<{
      cliente_id: string;
      nombre: string;
      saldo: Prisma.Decimal;
      limite: Prisma.Decimal;
    }[]>`
      SELECT
        c.id AS cliente_id,
        p.razon_social AS nombre,
        c.saldo_pendiente AS saldo,
        c.limite_credito AS limite
      FROM clientes c
      JOIN personas p ON p.id = c.persona_id
      WHERE p.empresa_id = ${empresaId}::uuid
        AND c.deleted = false
        AND c.limite_credito IS NOT NULL
        AND c.limite_credito > 0
        AND c.saldo_pendiente > c.limite_credito
      ORDER BY (c.saldo_pendiente - c.limite_credito) DESC
      LIMIT 20
    `;

    for (const c of clientesLimite) {
      const exceso = Number(c.saldo) - Number(c.limite);
      alertas.push({
        empresa_id: empresaId,
        tipo: 'limite_credito_superado',
        criticidad: 'alta',
        titulo: `Límite de crédito superado: ${c.nombre}`,
        descripcion: `Saldo pendiente Gs. ${Math.round(Number(c.saldo)).toLocaleString('es-PY')} supera el límite de Gs. ${Math.round(Number(c.limite)).toLocaleString('es-PY')} en Gs. ${Math.round(exceso).toLocaleString('es-PY')}.`,
        accion_sugerida: 'Bloquear nuevas ventas a crédito hasta regularizar el saldo.',
        entidad_tipo: 'cliente',
        entidad_id: c.cliente_id,
        canales_enviados: { dashboard: true, email: false, whatsapp: false },
      });
    }

    // ── 3. Stock bajo ─────────────────────────────────────────────
    const productosStockBajo = await this.prisma.$queryRaw<{
      producto_id: string;
      descripcion: string;
      cantidad: number;
    }[]>`
      SELECT
        p.id AS producto_id,
        p.descripcion,
        COALESCE(sd.cantidad_disponible, 0)::int AS cantidad
      FROM productos p
      JOIN stock_deposito sd ON sd.producto_id = p.id
      JOIN depositos d ON d.id = sd.deposito_id
      WHERE d.empresa_id = ${empresaId}::uuid
        AND p.active = true
        AND p.deleted = false
        AND COALESCE(sd.cantidad_disponible, 0) <= ${umbralStock}
      ORDER BY sd.cantidad_disponible ASC
      LIMIT 20
    `;

    for (const p of productosStockBajo) {
      alertas.push({
        empresa_id: empresaId,
        tipo: 'stock_bajo',
        criticidad: p.cantidad <= 0 ? 'alta' : 'media',
        titulo: `Stock bajo: ${p.descripcion}`,
        descripcion: `Solo quedan ${p.cantidad} unidades en stock${p.cantidad <= 0 ? ' — SIN STOCK' : ''}.`,
        accion_sugerida: 'Realizar pedido de reposición al proveedor.',
        entidad_tipo: 'producto',
        entidad_id: p.producto_id,
        canales_enviados: { dashboard: true, email: false, whatsapp: false },
      });
    }

    // ── 4. Cheques próximos a vencer (3 días) ─────────────────────
    try {
      const chequesProximos = await this.prisma.$queryRaw<{
        id: string;
        numero_cheque: string;
        banco_emisor: string;
        monto: Prisma.Decimal;
        fecha_vencimiento: Date;
        dias_restantes: number;
      }[]>`
        SELECT id, numero_cheque, banco_emisor, monto, fecha_vencimiento,
               (fecha_vencimiento - CURRENT_DATE)::int AS dias_restantes
        FROM tes_cheques
        WHERE empresa_id = ${empresaId}::uuid
          AND tipo = 'RECIBIDO'
          AND estado = 'EN_CARTERA'
          AND fecha_vencimiento BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '3 days'
        ORDER BY fecha_vencimiento ASC
        LIMIT 10
      `;

      for (const ch of chequesProximos) {
        alertas.push({
          empresa_id: empresaId,
          tipo: 'cheque_por_vencer',
          criticidad: ch.dias_restantes <= 1 ? 'alta' : 'media',
          titulo: `Cheque por vencer: ${ch.banco_emisor} Nro ${ch.numero_cheque}`,
          descripcion: `Cheque de Gs. ${Math.round(Number(ch.monto)).toLocaleString('es-PY')} vence en ${ch.dias_restantes} día${ch.dias_restantes !== 1 ? 's' : ''}. Presentar al banco para cobro.`,
          accion_sugerida: 'Depositar el cheque en cuenta bancaria antes de la fecha de vencimiento.',
          entidad_tipo: 'cheque',
          entidad_id: ch.id,
          canales_enviados: { dashboard: true, email: false, whatsapp: false },
        });
      }
    } catch { /* tabla tes_cheques no existe aún */ }

    // ── 5. Saldo bajo en cuentas bancarias ────────────────────────
    try {
      const cuentasSaldoBajo = await this.prisma.$queryRaw<{
        id: string;
        nombre: string;
        saldo_actual: Prisma.Decimal;
      }[]>`
        SELECT id, nombre, saldo_actual
        FROM tes_cuentas
        WHERE empresa_id = ${empresaId}::uuid
          AND activo = true
          AND moneda = 'PYG'
          AND saldo_actual < 500000
          AND saldo_actual >= 0
        ORDER BY saldo_actual ASC
        LIMIT 5
      `;

      for (const cu of cuentasSaldoBajo) {
        alertas.push({
          empresa_id: empresaId,
          tipo: 'saldo_cuenta_bajo',
          criticidad: Number(cu.saldo_actual) < 100_000 ? 'alta' : 'media',
          titulo: `Saldo bajo: ${cu.nombre}`,
          descripcion: `La cuenta "${cu.nombre}" tiene un saldo de Gs. ${Math.round(Number(cu.saldo_actual)).toLocaleString('es-PY')}.`,
          accion_sugerida: 'Realizar una transferencia para reponer el saldo antes de emitir pagos.',
          entidad_tipo: 'cuenta_bancaria',
          entidad_id: cu.id,
          canales_enviados: { dashboard: true, email: false, whatsapp: false },
        });
      }
    } catch { /* tabla tes_cuentas no existe aún */ }

    // ── RRHH (solo si la empresa tiene el módulo y las alertas están habilitadas) ─
    if (rrhhAlertasOn && (await this.rrhhContext.tieneModuloRRHH(empresaId))) {
      await this.detectarAlertasRRHH(empresaId, rrhhDiaCorte, alertas);
    }

    if (alertas.length > 0) {
      await this.prisma.ai_alertas.createMany({ data: alertas });
      this.logger.log(`${alertas.length} alertas generadas para empresa ${empresaId}`);
    }

    return alertas.length;
  }

  private async detectarAlertasRRHH(
    empresaId: string,
    diaCorte: number,
    alertas: {
      empresa_id: string;
      tipo: string;
      criticidad: string;
      titulo: string;
      descripcion: string;
      accion_sugerida?: string;
      entidad_tipo?: string;
      entidad_id?: string;
      canales_enviados: object;
    }[],
  ): Promise<void> {
    const hoy = new Date();
    // Período inmediatamente anterior (mes calendario)
    const mesAnt = hoy.getMonth() === 0 ? 12 : hoy.getMonth();
    const anioAnt = hoy.getMonth() === 0 ? hoy.getFullYear() - 1 : hoy.getFullYear();

    // ── R1. Liquidación mensual no cerrada pasado el día de corte ────
    try {
      if (hoy.getDate() > diaCorte) {
        const liqCerrada = await this.prisma.$queryRaw<{ cant: number }[]>`
          SELECT COUNT(*)::int AS cant
          FROM rrhh_liquidaciones_cabecera
          WHERE empresa_id = ${empresaId}::uuid
            AND tipo = 'MENSUAL'
            AND periodo_anio = ${anioAnt}
            AND periodo_mes  = ${mesAnt}
            AND estado = 'CERRADA'
        `;
        if ((liqCerrada[0]?.cant ?? 0) === 0) {
          alertas.push({
            empresa_id: empresaId,
            tipo: 'rrhh_liquidacion_no_cerrada',
            criticidad: 'alta',
            titulo: `Liquidación ${String(mesAnt).padStart(2, '0')}/${anioAnt} sin cerrar`,
            descripcion: `Pasó el día de corte (día ${diaCorte}) y la liquidación mensual de ${String(mesAnt).padStart(2, '0')}/${anioAnt} todavía no está cerrada.`,
            accion_sugerida: 'Revisar novedades pendientes y cerrar la liquidación mensual para emitir el reporte IPS y la acreditación bancaria.',
            entidad_tipo: 'rrhh_periodo',
            entidad_id: `${anioAnt}-${String(mesAnt).padStart(2, '0')}`,
            canales_enviados: { dashboard: true, email: false, whatsapp: false },
          });
        }
      }
    } catch (e) { this.logger.warn(`R1 liquidación: ${(e as Error).message}`); }

    // ── R2. Empleados activos con aporta_ips pero sin numero_ips ─────
    try {
      const sinIps = await this.prisma.$queryRaw<{ cant: number }[]>`
        SELECT COUNT(*)::int AS cant
        FROM rrhh_empleados
        WHERE empresa_id = ${empresaId}::uuid
          AND estado = 'ACTIVO'
          AND aporta_ips = true
          AND (numero_ips IS NULL OR LENGTH(TRIM(numero_ips)) = 0)
      `;
      const cant = sinIps[0]?.cant ?? 0;
      if (cant > 0) {
        alertas.push({
          empresa_id: empresaId,
          tipo: 'rrhh_empleados_sin_ips',
          criticidad: 'media',
          titulo: `${cant} empleado${cant !== 1 ? 's' : ''} sin número de IPS`,
          descripcion: `Hay ${cant} empleado${cant !== 1 ? 's activos' : ' activo'} con aporte IPS marcado pero sin número de patronal cargado.`,
          accion_sugerida: 'Cargar el número de IPS en la ficha del empleado para que aparezca correctamente en el reporte mensual.',
          entidad_tipo: 'rrhh_empleados',
          canales_enviados: { dashboard: true, email: false, whatsapp: false },
        });
      }
    } catch (e) { this.logger.warn(`R2 sin IPS: ${(e as Error).message}`); }

    // ── R3. Empleados activos sin cuenta bancaria ────────────────────
    try {
      const sinCbu = await this.prisma.$queryRaw<{ cant: number }[]>`
        SELECT COUNT(*)::int AS cant
        FROM rrhh_empleados
        WHERE empresa_id = ${empresaId}::uuid
          AND estado = 'ACTIVO'
          AND (cuenta_bancaria IS NULL OR LENGTH(TRIM(cuenta_bancaria)) = 0)
      `;
      const cant = sinCbu[0]?.cant ?? 0;
      if (cant > 0) {
        alertas.push({
          empresa_id: empresaId,
          tipo: 'rrhh_empleados_sin_cbu',
          criticidad: 'media',
          titulo: `${cant} empleado${cant !== 1 ? 's' : ''} sin cuenta bancaria`,
          descripcion: `${cant} empleado${cant !== 1 ? 's activos no tienen' : ' activo no tiene'} cuenta bancaria cargada. La acreditación bancaria los va a omitir.`,
          accion_sugerida: 'Cargar el número de cuenta bancaria en la ficha del empleado antes de generar el archivo de acreditación.',
          entidad_tipo: 'rrhh_empleados',
          canales_enviados: { dashboard: true, email: false, whatsapp: false },
        });
      }
    } catch (e) { this.logger.warn(`R3 sin CBU: ${(e as Error).message}`); }

    // ── R4. Préstamos vigentes con cuota no descontada en última liquidación cerrada ──
    try {
      const prestamosSinDescuento = await this.prisma.$queryRaw<{ cant: number }[]>`
        WITH ultima AS (
          SELECT id
          FROM rrhh_liquidaciones_cabecera
          WHERE empresa_id = ${empresaId}::uuid
            AND tipo = 'MENSUAL'
            AND estado = 'CERRADA'
          ORDER BY periodo_anio DESC, periodo_mes DESC
          LIMIT 1
        )
        SELECT COUNT(*)::int AS cant
        FROM rrhh_prestamos pr
        WHERE pr.empresa_id = ${empresaId}::uuid
          AND pr.estado = 'ACTIVO'
          AND pr.saldo_pendiente > 0
          AND EXISTS (SELECT 1 FROM ultima)
          AND NOT EXISTS (
            SELECT 1 FROM rrhh_liquidaciones_detalle ld
            JOIN ultima u ON u.id = ld.liquidacion_id
            WHERE ld.empleado_id = pr.empleado_id
              AND ld.monto > 0
              AND ld.concepto_id IN (
                SELECT id FROM rrhh_conceptos_liquidacion
                WHERE empresa_id = ${empresaId}::uuid
                  AND (categoria ILIKE '%PRESTAMO%' OR codigo ILIKE '%PREST%')
              )
          )
      `;
      const cant = prestamosSinDescuento[0]?.cant ?? 0;
      if (cant > 0) {
        alertas.push({
          empresa_id: empresaId,
          tipo: 'rrhh_prestamos_sin_descuento',
          criticidad: 'media',
          titulo: `${cant} préstamo${cant !== 1 ? 's' : ''} vigente${cant !== 1 ? 's' : ''} sin descuento en última liquidación`,
          descripcion: `${cant} préstamo${cant !== 1 ? 's' : ''} con saldo pendiente no recibieron cuota descontada en la última liquidación mensual cerrada.`,
          accion_sugerida: 'Revisar si la cuota debe aplicarse o si el préstamo está suspendido.',
          entidad_tipo: 'rrhh_prestamos',
          canales_enviados: { dashboard: true, email: false, whatsapp: false },
        });
      }
    } catch (e) { this.logger.warn(`R4 préstamos: ${(e as Error).message}`); }

    // ── R5. Vacaciones vencidas sin tomar (saldo de >14 meses) ───────
    try {
      const anioLimite = hoy.getFullYear() - 1;
      const vacVencidas = await this.prisma.$queryRaw<{ cant: number; dias: number }[]>`
        SELECT COUNT(*)::int AS cant, COALESCE(SUM(dias_pendientes), 0)::int AS dias
        FROM rrhh_vacaciones_saldo
        WHERE empresa_id = ${empresaId}::uuid
          AND anio <= ${anioLimite}
          AND dias_pendientes > 0
      `;
      const cant = vacVencidas[0]?.cant ?? 0;
      const dias = vacVencidas[0]?.dias ?? 0;
      if (cant > 0) {
        alertas.push({
          empresa_id: empresaId,
          tipo: 'rrhh_vacaciones_vencidas',
          criticidad: 'baja',
          titulo: `${dias} días de vacaciones vencidas sin tomar`,
          descripcion: `${cant} empleado${cant !== 1 ? 's tienen' : ' tiene'} saldo de vacaciones generado en ${anioLimite} o antes (${dias} días en total).`,
          accion_sugerida: 'Programar el goce de vacaciones para evitar acumulación según Art. 219 del Código Laboral.',
          entidad_tipo: 'rrhh_vacaciones',
          canales_enviados: { dashboard: true, email: false, whatsapp: false },
        });
      }
    } catch (e) { this.logger.warn(`R5 vacaciones vencidas: ${(e as Error).message}`); }
  }

  async getAlertas(empresaId: string) {
    return this.prisma.ai_alertas.findMany({
      where: { empresa_id: empresaId, resuelta: false },
      orderBy: [
        { criticidad: 'asc' }, // alta primero (a < m)
        { created_at: 'desc' },
      ],
    });
  }

  async marcarLeida(id: string, empresaId: string) {
    return this.prisma.ai_alertas.updateMany({
      where: { id, empresa_id: empresaId },
      data: { leida: true },
    });
  }

  async marcarResuelta(id: string, empresaId: string) {
    return this.prisma.ai_alertas.updateMany({
      where: { id, empresa_id: empresaId },
      data: { resuelta: true, leida: true },
    });
  }
}
