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

export interface TopCobrador {
  nombre: string;
  montoCobrado: number;
  cantRecibos: number;
}

export interface TopProducto {
  descripcion: string;
  totalVendido: number;
  cantidad: number;
}

export interface StockCritico {
  descripcion: string;
  stockActual: number;
}

export interface LoteProximoVencer {
  descripcion: string;
  numeroLote: string;
  cantidadDisponible: number;
  fechaVencimiento: string;
  diasRestantes: number;
}

export interface Rentabilidad {
  costoVentas: number;
  margenBruto: number;
  margenPct: number;
  topPorMargen: { descripcion: string; margen: number; margenPct: number }[];
}

export interface SaldoPorMoneda {
  moneda: string;
  total: number;
  cantCuentas: number;
}

export interface ChequePorCobrar {
  numeroCheque: string;
  bancoEmisor: string;
  monto: number;
  fechaVencimiento: string;
  diasRestantes: number;
}

export interface MetricasPeriodo {
  // Período actual
  totalFacturado: number;
  totalCobrado: number;
  tasaMora: number;
  clientesNuevos: number;
  ticketPromedio: number;
  // Nuevas métricas
  totalIva: number;
  cuentasPorCobrar: number;
  totalCompras: number;
  notasCredito: { cantidad: number; monto: number };
  topProductos: TopProducto[];
  stockCritico: StockCritico[];
  lotesProximosVencer: LoteProximoVencer[];
  rentabilidad: Rentabilidad;
  // Tesorería
  posicionCajaPyg: number;
  posicionCajaPorMoneda: SaldoPorMoneda[];
  chequesProximosACobrar: { cantidad: number; monto: number; detalle: ChequePorCobrar[] };
  // Período anterior (para tendencias)
  anterior: {
    totalFacturado: number;
    totalCobrado: number;
    clientesNuevos: number;
    totalCompras: number;
  };
  topCobradores: TopCobrador[];
}

const SYSTEM_PROMPT_RESUMEN = `
Sos el analista financiero de una empresa que usa un ERP llamado Novasis.
Tu trabajo es escribir el resumen ejecutivo diario para el gerente/CEO.

Reglas:
- Escribí en español neutro, lenguaje de negocios simple
- 3-4 párrafos máximo
- 80% texto, evitá listas con bullets
- Explicá el POR QUÉ de cada número importante
- Terminá con UNA acción concreta y priorizada que debería tomar hoy el gerente
- No uses frases genéricas como "es importante analizar". Sé directo y específico
- Usá los números del contexto, formateados con puntos como separadores de miles
`.trim();

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

  constructor(
    private readonly prisma: PrismaService,
    private readonly aiProvider: AiProviderService,
    private readonly rrhhContext: RrhhContextService,
    private readonly modulesContext: ModulesContextService,
  ) {}

  async calcularMetricasPeriodo(empresaId: string, periodo: string): Promise<MetricasPeriodo> {
    const [year, month] = periodo.split('-').map(Number);
    const fechaDesde = new Date(year, month - 1, 1);
    const fechaHasta = new Date(year, month, 1);
    // Período anterior para tendencias
    const fechaAntDesde = new Date(year, month - 2, 1);
    const fechaAntHasta = new Date(year, month - 1, 1);

    const [
      facturadoResult,
      cobradoResult,
      moraResult,
      clientesResult,
      ticketResult,
      cobradoresResult,
      productosResult,
      ivaResult,
      cuentasPorCobrarResult,
      comprasResult,
      notasCreditoResult,
      stockCriticoResult,
      facturadoAntResult,
      cobradoAntResult,
      clientesAntResult,
      comprasAntResult,
      rentabilidadResult,
      lotesResult,
      posicionCajaResult,
      chequesProximosResult,
    ] = await Promise.all([
      // Total facturado — convertido a PYG usando dticam (cotización guardada en la factura)
      this.prisma.$queryRaw<{ total: Prisma.Decimal }[]>`
        SELECT COALESCE(SUM(fs.dtotgralope * COALESCE(fc.dticam, 1)), 0) AS total
        FROM factura_subtotales fs
        JOIN factura_cab fc ON fc.id = fs.factura_cab_id
        WHERE fc.empresa_id = ${empresaId}::uuid
          AND fc.dfeemide >= ${fechaDesde} AND fc.dfeemide < ${fechaHasta}
          AND fc.estado != 'Anulada'
      `,
      // Total cobrado
      this.prisma.$queryRaw<{ total: Prisma.Decimal }[]>`
        SELECT COALESCE(SUM(monto_total), 0) AS total
        FROM recibos_cobro
        WHERE empresa_id = ${empresaId}::uuid
          AND fecha_emision >= ${fechaDesde} AND fecha_emision < ${fechaHasta}
          AND estado != 'anulado'
      `,
      // Tasa mora (estado puede ser 'pendiente' o 'Pendiente' por inconsistencia de datos)
      this.prisma.$queryRaw<{ vencidas: bigint; pendientes: bigint }[]>`
        SELECT
          COUNT(*) FILTER (WHERE fc.dvenccuo < NOW() AND LOWER(fc.estado) = 'pendiente') AS vencidas,
          COUNT(*) FILTER (WHERE LOWER(fc.estado) = 'pendiente') AS pendientes
        FROM factura_cuotas fc
        JOIN factura_cab cab ON cab.id = fc.factura_cab_id
        WHERE cab.empresa_id = ${empresaId}::uuid
      `,
      // Clientes nuevos
      this.prisma.$queryRaw<{ cantidad: bigint }[]>`
        SELECT COUNT(*) AS cantidad
        FROM clientes c
        JOIN personas p ON p.id = c.persona_id
        WHERE p.empresa_id = ${empresaId}::uuid
          AND c.created_at >= ${fechaDesde} AND c.created_at < ${fechaHasta}
          AND c.deleted = false
      `,
      // Ticket promedio — convertido a PYG
      this.prisma.$queryRaw<{ promedio: Prisma.Decimal }[]>`
        SELECT COALESCE(AVG(fs.dtotgralope * COALESCE(fc.dticam, 1)), 0) AS promedio
        FROM factura_subtotales fs
        JOIN factura_cab fc ON fc.id = fs.factura_cab_id
        WHERE fc.empresa_id = ${empresaId}::uuid
          AND fc.dfeemide >= ${fechaDesde} AND fc.dfeemide < ${fechaHasta}
          AND fc.estado != 'Anulada'
      `,
      // Top cobradores
      this.prisma.$queryRaw<{ nombre: string; montoCobrado: Prisma.Decimal; cantRecibos: bigint }[]>`
        SELECT vc.nombre,
          COALESCE(SUM(rc.monto_total), 0) AS "montoCobrado",
          COUNT(rc.id) AS "cantRecibos"
        FROM recibos_cobro rc
        JOIN vendedores_cobradores vc ON vc.id = rc.cobrador_id
        WHERE rc.empresa_id = ${empresaId}::uuid
          AND rc.fecha_emision >= ${fechaDesde} AND rc.fecha_emision < ${fechaHasta}
          AND rc.estado != 'anulado'
        GROUP BY vc.id, vc.nombre
        ORDER BY "montoCobrado" DESC LIMIT 5
      `,
      // Top productos — monto convertido a PYG con dticam
      this.prisma.$queryRaw<{ descripcion: string; totalVendido: Prisma.Decimal; cantidad: Prisma.Decimal }[]>`
        SELECT fd.ddesproser AS descripcion,
          COALESCE(SUM(fd.dtotopeitem * COALESCE(fc.dticam, 1)), 0) AS "totalVendido",
          COALESCE(SUM(fd.dcantproser), 0) AS cantidad
        FROM factura_det fd
        JOIN factura_cab fc ON fc.id = fd.factura_cab_id
        WHERE fc.empresa_id = ${empresaId}::uuid
          AND fc.dfeemide >= ${fechaDesde} AND fc.dfeemide < ${fechaHasta}
          AND fc.estado != 'Anulada'
        GROUP BY fd.ddesproser
        ORDER BY "totalVendido" DESC LIMIT 5
      `,
      // IVA total — convertido a PYG con dticam
      this.prisma.$queryRaw<{ total: Prisma.Decimal }[]>`
        SELECT COALESCE(SUM(fs.dtotiva * COALESCE(fc.dticam, 1)), 0) AS total
        FROM factura_subtotales fs
        JOIN factura_cab fc ON fc.id = fs.factura_cab_id
        WHERE fc.empresa_id = ${empresaId}::uuid
          AND fc.dfeemide >= ${fechaDesde} AND fc.dfeemide < ${fechaHasta}
          AND fc.estado != 'Anulada'
      `,
      // Cuentas por cobrar: saldo pendiente acumulado total (todas las facturas)
      // Cuentas por cobrar — saldo pendiente convertido a PYG con dticam de la factura
      this.prisma.$queryRaw<{ total: Prisma.Decimal }[]>`
        SELECT COALESCE(SUM(fq.saldo_pendiente * COALESCE(fc.dticam, 1)), 0) AS total
        FROM factura_cuotas fq
        JOIN factura_cab fc ON fc.id = fq.factura_cab_id
        WHERE fc.empresa_id = ${empresaId}::uuid
          AND LOWER(fq.estado) = 'pendiente'
      `,
      // Compras del período — convertido a PYG con cotizacion
      this.prisma.$queryRaw<{ total: Prisma.Decimal }[]>`
        SELECT COALESCE(SUM(total * COALESCE(cotizacion, 1)), 0) AS total
        FROM compra_cab
        WHERE empresa_id = ${empresaId}::uuid
          AND fecha_emision >= ${fechaDesde} AND fecha_emision < ${fechaHasta}
          AND estado != 'anulada'
      `,
      // Notas de crédito del período
      this.prisma.$queryRaw<{ cantidad: bigint; monto: Prisma.Decimal }[]>`
        SELECT COUNT(nc.id) AS cantidad,
          COALESCE(SUM(ncs.dtotgralope), 0) AS monto
        FROM nota_credito_cab nc
        LEFT JOIN nota_credito_subtotal ncs ON ncs.nota_credito_cab_id = nc.id
        WHERE nc.empresa_id = ${empresaId}::uuid
          AND nc.dfeemide >= ${fechaDesde} AND nc.dfeemide < ${fechaHasta}
          AND nc.estado != 'Anulada'
      `,
      // Stock crítico: productos con stock actual <= 5 (umbral básico)
      this.prisma.$queryRaw<{ descripcion: string; stockActual: Prisma.Decimal }[]>`
        SELECT pr.descripcion, COALESCE(SUM(sd.cantidad_disponible), 0) AS "stockActual"
        FROM productos pr
        LEFT JOIN stock_deposito sd ON sd.producto_id = pr.id
        WHERE pr.empresa_id = ${empresaId}::uuid
          AND pr.deleted = false
          AND pr.active = true
        GROUP BY pr.id, pr.descripcion
        HAVING COALESCE(SUM(sd.cantidad_disponible), 0) <= 5
        ORDER BY "stockActual" ASC
        LIMIT 5
      `,
      // Período anterior — facturado convertido a PYG
      this.prisma.$queryRaw<{ total: Prisma.Decimal }[]>`
        SELECT COALESCE(SUM(fs.dtotgralope * COALESCE(fc.dticam, 1)), 0) AS total
        FROM factura_subtotales fs
        JOIN factura_cab fc ON fc.id = fs.factura_cab_id
        WHERE fc.empresa_id = ${empresaId}::uuid
          AND fc.dfeemide >= ${fechaAntDesde} AND fc.dfeemide < ${fechaAntHasta}
          AND fc.estado != 'Anulada'
      `,
      // Período anterior — cobrado
      this.prisma.$queryRaw<{ total: Prisma.Decimal }[]>`
        SELECT COALESCE(SUM(monto_total), 0) AS total
        FROM recibos_cobro
        WHERE empresa_id = ${empresaId}::uuid
          AND fecha_emision >= ${fechaAntDesde} AND fecha_emision < ${fechaAntHasta}
          AND estado != 'anulado'
      `,
      // Período anterior — clientes
      this.prisma.$queryRaw<{ cantidad: bigint }[]>`
        SELECT COUNT(*) AS cantidad
        FROM clientes c
        JOIN personas p ON p.id = c.persona_id
        WHERE p.empresa_id = ${empresaId}::uuid
          AND c.created_at >= ${fechaAntDesde} AND c.created_at < ${fechaAntHasta}
          AND c.deleted = false
      `,
      // Período anterior — compras
      this.prisma.$queryRaw<{ total: Prisma.Decimal }[]>`
        SELECT COALESCE(SUM(total * COALESCE(cotizacion, 1)), 0) AS total
        FROM compra_cab
        WHERE empresa_id = ${empresaId}::uuid
          AND fecha_emision >= ${fechaAntDesde} AND fecha_emision < ${fechaAntHasta}
          AND estado != 'anulada'
      `,
      // Rentabilidad — costo vs venta por item del período
      this.prisma.$queryRaw<{ descripcion: string; costoTotal: Prisma.Decimal; ventaTotal: Prisma.Decimal }[]>`
        SELECT
          fd.ddesproser AS descripcion,
          COALESCE(SUM(fd.costo_total_venta * COALESCE(fc.dticam, 1)), 0) AS "costoTotal",
          COALESCE(SUM(fd.dtotopeitem * COALESCE(fc.dticam, 1)), 0) AS "ventaTotal"
        FROM factura_det fd
        JOIN factura_cab fc ON fc.id = fd.factura_cab_id
        WHERE fc.empresa_id = ${empresaId}::uuid
          AND fc.dfeemide >= ${fechaDesde} AND fc.dfeemide < ${fechaHasta}
          AND fc.estado != 'Anulada'
          AND fd.costo_total_venta IS NOT NULL AND fd.costo_total_venta > 0
        GROUP BY fd.ddesproser
        ORDER BY "ventaTotal" DESC
        LIMIT 10
      `,
      // Lotes próximos a vencer (30 días)
      this.prisma.$queryRaw<{ descripcion: string; numeroLote: string; cantidadDisponible: Prisma.Decimal; fechaVencimiento: Date; diasRestantes: bigint }[]>`
        SELECT
          pr.descripcion,
          lp.numero_lote AS "numeroLote",
          lp.cantidad_disponible AS "cantidadDisponible",
          lp.fecha_vencimiento AS "fechaVencimiento",
          (lp.fecha_vencimiento - CURRENT_DATE)::int AS "diasRestantes"
        FROM lotes_producto lp
        JOIN productos pr ON pr.id = lp.producto_id
        WHERE lp.empresa_id = ${empresaId}::uuid
          AND lp.activo = true
          AND lp.cantidad_disponible > 0
          AND lp.fecha_vencimiento IS NOT NULL
          AND lp.fecha_vencimiento >= CURRENT_DATE
          AND lp.fecha_vencimiento <= CURRENT_DATE + INTERVAL '30 days'
        ORDER BY lp.fecha_vencimiento ASC
        LIMIT 10
      `,
      // Posición de caja por moneda (nunca mezclar PYG con USD/etc.)
      this.prisma.$queryRaw<{ moneda: string; total: Prisma.Decimal; cant_cuentas: bigint }[]>`
        SELECT moneda, COALESCE(SUM(saldo_actual), 0) AS total, COUNT(*) AS cant_cuentas
        FROM tes_cuentas
        WHERE empresa_id = ${empresaId}::uuid AND activo = true
        GROUP BY moneda
        ORDER BY moneda ASC
      `.catch(() => []),
      // Cheques en cartera próximos a cobrar (7 días)
      this.prisma.$queryRaw<{ numeroCheque: string; bancoEmisor: string; monto: Prisma.Decimal; fechaVencimiento: Date; diasRestantes: bigint }[]>`
        SELECT
          numero_cheque AS "numeroCheque",
          banco_emisor AS "bancoEmisor",
          monto,
          fecha_vencimiento AS "fechaVencimiento",
          (fecha_vencimiento - CURRENT_DATE)::int AS "diasRestantes"
        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 '7 days'
        ORDER BY fecha_vencimiento ASC
        LIMIT 20
      `.catch(() => []),
    ]);

    const pendientes = Number(moraResult[0]?.pendientes ?? 0);
    const vencidas = Number(moraResult[0]?.vencidas ?? 0);
    const tasaMora = pendientes > 0 ? Math.round((vencidas / pendientes) * 100 * 100) / 100 : 0;

    // Rentabilidad
    const costoVentas = rentabilidadResult.reduce((s, r) => s + Number(r.costoTotal), 0);
    const totalVentasConCosto = rentabilidadResult.reduce((s, r) => s + Number(r.ventaTotal), 0);
    const margenBruto = totalVentasConCosto - costoVentas;
    const margenPct = totalVentasConCosto > 0 ? Math.round((margenBruto / totalVentasConCosto) * 100 * 10) / 10 : 0;
    const topPorMargen = rentabilidadResult
      .map((r) => {
        const venta = Number(r.ventaTotal);
        const costo = Number(r.costoTotal);
        const margen = venta - costo;
        return { descripcion: r.descripcion, margen, margenPct: venta > 0 ? Math.round((margen / venta) * 100 * 10) / 10 : 0 };
      })
      .sort((a, b) => b.margen - a.margen)
      .slice(0, 5);

    return {
      totalFacturado: Number(facturadoResult[0]?.total ?? 0),
      totalCobrado: Number(cobradoResult[0]?.total ?? 0),
      tasaMora,
      clientesNuevos: Number(clientesResult[0]?.cantidad ?? 0),
      ticketPromedio: Number(ticketResult[0]?.promedio ?? 0),
      totalIva: Number(ivaResult[0]?.total ?? 0),
      cuentasPorCobrar: Number(cuentasPorCobrarResult[0]?.total ?? 0),
      totalCompras: Number(comprasResult[0]?.total ?? 0),
      notasCredito: {
        cantidad: Number(notasCreditoResult[0]?.cantidad ?? 0),
        monto: Number(notasCreditoResult[0]?.monto ?? 0),
      },
      topCobradores: cobradoresResult.map((r) => ({
        nombre: r.nombre,
        montoCobrado: Number(r.montoCobrado),
        cantRecibos: Number(r.cantRecibos),
      })),
      topProductos: productosResult.map((r) => ({
        descripcion: r.descripcion,
        totalVendido: Number(r.totalVendido),
        cantidad: Number(r.cantidad),
      })),
      stockCritico: stockCriticoResult.map((r) => ({
        descripcion: r.descripcion,
        stockActual: Number(r.stockActual),
      })),
      lotesProximosVencer: lotesResult.map((r) => ({
        descripcion: r.descripcion,
        numeroLote: r.numeroLote,
        cantidadDisponible: Number(r.cantidadDisponible),
        fechaVencimiento: r.fechaVencimiento instanceof Date ? r.fechaVencimiento.toISOString().slice(0, 10) : String(r.fechaVencimiento),
        diasRestantes: Number(r.diasRestantes),
      })),
      rentabilidad: { costoVentas, margenBruto, margenPct, topPorMargen },
      posicionCajaPyg: Number((posicionCajaResult as any[]).find((r: any) => r.moneda === 'PYG')?.total ?? 0),
      posicionCajaPorMoneda: (posicionCajaResult as any[]).map((r: any) => ({
        moneda: r.moneda,
        total: Number(r.total),
        cantCuentas: Number(r.cant_cuentas),
      })),
      chequesProximosACobrar: {
        cantidad: (chequesProximosResult as any[]).length,
        monto: (chequesProximosResult as any[]).reduce((s: number, c: any) => s + Number(c.monto), 0),
        detalle: (chequesProximosResult as any[]).map((c: any) => ({
          numeroCheque: c.numeroCheque,
          bancoEmisor: c.bancoEmisor,
          monto: Number(c.monto),
          fechaVencimiento: c.fechaVencimiento instanceof Date ? c.fechaVencimiento.toISOString().slice(0, 10) : String(c.fechaVencimiento),
          diasRestantes: Number(c.diasRestantes),
        })),
      },
      anterior: {
        totalFacturado: Number(facturadoAntResult[0]?.total ?? 0),
        totalCobrado: Number(cobradoAntResult[0]?.total ?? 0),
        clientesNuevos: Number(clientesAntResult[0]?.cantidad ?? 0),
        totalCompras: Number(comprasAntResult[0]?.total ?? 0),
      },
    };
  }

  async generarInsightConIA(empresaId: string, periodo: string): Promise<void> {
    this.logger.log(`Generando insight para empresa ${empresaId} período ${periodo}`);

    const [metricas, dominios] = await Promise.all([
      this.calcularMetricasPeriodo(empresaId, periodo),
      this.modulesContext.getActiveDomains(empresaId),
    ]);
    const has = (d: string) => dominios.includes(d as any);

    const formatGs = (n: number) => `Gs. ${Math.round(n).toLocaleString('es-PY')}`;
    const formatPct = (n: number) => `${n.toFixed(1)}%`;

    const pct = (a: number, b: number) => (b > 0 ? (((a - b) / b) * 100).toFixed(1) : null);
    const trend = (a: number, b: number) => {
      const p = pct(a, b);
      return p ? ` (${Number(p) >= 0 ? '+' : ''}${p}% vs mes ant.)` : '';
    };

    const bloques: string[] = [];

    if (has('VENTAS')) {
      bloques.push(
`VENTAS:
- Total facturado: ${formatGs(metricas.totalFacturado)}${trend(metricas.totalFacturado, metricas.anterior.totalFacturado)}
- Ticket promedio: ${formatGs(metricas.ticketPromedio)}
- IVA total del período: ${formatGs(metricas.totalIva)}

TOP PRODUCTOS MÁS VENDIDOS:
${metricas.topProductos.map((p, i) => `${i + 1}. ${p.descripcion}: ${formatGs(p.totalVendido)} (${p.cantidad} unidades)`).join('\n') || '- Sin datos'}

RENTABILIDAD:
- Costo de ventas: ${formatGs(metricas.rentabilidad.costoVentas)}
- Margen bruto: ${formatGs(metricas.rentabilidad.margenBruto)} (${formatPct(metricas.rentabilidad.margenPct)})
${metricas.rentabilidad.topPorMargen.length > 0 ? 'Top productos por margen:\n' + metricas.rentabilidad.topPorMargen.map((p, i) => `  ${i + 1}. ${p.descripcion}: ${formatGs(p.margen)} (${p.margenPct}%)`).join('\n') : '- Sin datos de costo en ventas del período'}`
      );
    }

    if (has('COBRANZAS')) {
      bloques.push(
`COBRANZAS:
- Total cobrado: ${formatGs(metricas.totalCobrado)}${trend(metricas.totalCobrado, metricas.anterior.totalCobrado)}
- Cuentas por cobrar (saldo acumulado pendiente): ${formatGs(metricas.cuentasPorCobrar)}
- Tasa de mora: ${formatPct(metricas.tasaMora)}

NOTAS DE CRÉDITO:
- Cantidad emitidas: ${metricas.notasCredito.cantidad}
- Monto total: ${formatGs(metricas.notasCredito.monto)}`
      );
    }

    if (has('COMPRAS')) {
      bloques.push(
`COMPRAS:
- Total compras del período: ${formatGs(metricas.totalCompras)}${trend(metricas.totalCompras, metricas.anterior.totalCompras)}`
      );
    }

    if (has('CLIENTES')) {
      bloques.push(
`CLIENTES:
- Nuevos registrados: ${metricas.clientesNuevos}${trend(metricas.clientesNuevos, metricas.anterior.clientesNuevos)}`
      );
    }

    if (has('INVENTARIO')) {
      bloques.push(
`STOCK CRÍTICO (≤5 unidades):
${metricas.stockCritico.map((p) => `- ${p.descripcion}: ${p.stockActual} unidades`).join('\n') || '- Sin productos en stock crítico'}

LOTES PRÓXIMOS A VENCER (30 días):
${metricas.lotesProximosVencer.map((l) => `- ${l.descripcion} [Lote: ${l.numeroLote}]: ${l.cantidadDisponible} unidades — vence ${l.fechaVencimiento} (${l.diasRestantes} días)`).join('\n') || '- Sin lotes próximos a vencer'}`
      );
    }

    if (has('TESORERIA')) {
      bloques.push(
`TESORERÍA:
- Posición de caja por moneda:
${metricas.posicionCajaPorMoneda.length > 0
  ? metricas.posicionCajaPorMoneda.map((m) =>
      `  · ${m.moneda}: ${m.moneda === 'PYG' ? formatGs(m.total) : `${m.moneda} ${m.total.toLocaleString('es-PY', { minimumFractionDigits: 2 })}`} (${m.cantCuentas} cuenta${m.cantCuentas !== 1 ? 's' : ''})`
    ).join('\n')
  : '  · Sin cuentas de tesorería registradas'
}
- Cheques en cartera próximos a cobrar (7 días): ${metricas.chequesProximosACobrar.cantidad} cheques por ${formatGs(metricas.chequesProximosACobrar.monto)}
${metricas.chequesProximosACobrar.detalle.map((c) => `  · ${c.bancoEmisor} Nro ${c.numeroCheque}: ${formatGs(c.monto)} — vence ${c.fechaVencimiento} (${c.diasRestantes} días)`).join('\n') || '  · Sin cheques próximos a vencer'}`
      );
    }

    if (has('RRHH')) {
      const rrhh = await this._calcularMetricasRRHHInterno(empresaId, periodo).catch(() => null);
      if (rrhh) {
        bloques.push(
`RRHH / NÓMINA:
- Empleados activos: ${rrhh.empleadosActivos}
- Nómina del mes (neto a pagar): ${formatGs(rrhh.nominaMes.netoAPagar)} (${rrhh.nominaMes.empleados} empleados)
- IPS del mes: ${formatGs(rrhh.ips.total)} (Obrero ${formatGs(rrhh.ips.obrero)} · Patronal ${formatGs(rrhh.ips.patronal)})
- Proyección próximo mes: ${formatGs(rrhh.proyeccionNomina)}
- Préstamos activos: ${rrhh.prestamosActivos.cantidad} (saldo ${formatGs(rrhh.prestamosActivos.saldoPendiente)})
- Vacaciones pendientes: ${rrhh.vacaciones.diasPendientes} días acumulados`
        );
      }
    }

    const prompt = `
Datos del período ${periodo} para el resumen ejecutivo.

DOMINIOS ACTIVOS DE LA EMPRESA: ${dominios.join(', ') || '(ninguno)'}.
IMPORTANTE: Sólo analizá los datos de los dominios listados arriba. NO menciones módulos, áreas o conceptos fuera de esos dominios (ej: si no está VENTAS, no hables de facturación; si no está COBRANZAS, no hables de cuentas por cobrar; si no está CONTABILIDAD, no hables de asientos). Si la empresa sólo tiene un dominio activo, enfocá todo el análisis en ese dominio.

${bloques.join('\n\n')}

Escribí el resumen ejecutivo para el gerente, limitándote estrictamente a los dominios activos listados.
    `.trim();

    const result = await this.aiProvider.completar(empresaId, prompt, SYSTEM_PROMPT_RESUMEN, {
      feature: 'INSIGHTS',
    });

    // Marcar insights anteriores del mismo tipo/período como no vigentes
    await this.prisma.ai_insights.updateMany({
      where: { empresa_id: empresaId, tipo: 'resumen_ejecutivo', periodo, vigente: true },
      data: { vigente: false },
    });

    await this.prisma.ai_insights.create({
      data: {
        empresa_id: empresaId,
        tipo: 'resumen_ejecutivo',
        periodo,
        titulo: `Resumen ejecutivo — ${periodo}`,
        narrativa: result.content,
        datos_json: metricas as any,
        tokens_usados: result.tokensUsados,
        modelo_usado:
          (await this.prisma.ai_empresa_config.findUnique({ where: { empresa_id: empresaId } }))?.modelo ?? null,
        vigente: true,
      },
    });

    this.logger.log(`Insight generado para empresa ${empresaId} — ${result.tokensUsados} tokens`);
  }

  // ─── RRHH / Nómina ──────────────────────────────────────────────────────
  //
  // KPIs RRHH calculados on-demand (no se persisten en ai_insights para
  // evitar leak de datos sensibles a usuarios sin permiso). El controller
  // pasa el usuarioId y acá se verifica módulo + permiso. Si no aplica,
  // devolvemos null y el frontend simplemente no renderiza el acordeón.

  async calcularMetricasRRHH(
    empresaId: string,
    usuarioId: string,
    periodo?: string,
  ): Promise<MetricasRRHH | null> {
    const habilitado = await this.rrhhContext.puedeVerRRHH(usuarioId, empresaId);
    if (!habilitado) return null;
    return this._calcularMetricasRRHHInterno(empresaId, periodo);
  }

  // Variante interna sin chequeo de permiso por usuario. Usar SÓLO desde
  // generación system-side (narrativa) cuando el dominio RRHH ya está
  // confirmado activo a nivel empresa vía ModulesContextService.
  private async _calcularMetricasRRHHInterno(
    empresaId: string,
    periodo?: string,
  ): Promise<MetricasRRHH | null> {
    const hoy = new Date();
    const [yearStr, monthStr] = (periodo ?? `${hoy.getFullYear()}-${String(hoy.getMonth() + 1).padStart(2, '0')}`).split('-');
    const anio = Number(yearStr);
    const mes = Number(monthStr);
    const anioMesAnt = mes === 1 ? { anio: anio - 1, mes: 12 } : { anio, mes: mes - 1 };

    const [
      empleadosActivosRes,
      liquidacionMesRes,
      liquidacionAntRes,
      ipsMesRes,
      prestamosRes,
      vacacionesPendientesRes,
      ultimos3MesesRes,
    ] = await Promise.all([
      this.prisma.$queryRaw<{ total: bigint }[]>`
        SELECT COUNT(*) AS total FROM rrhh_empleados
        WHERE empresa_id = ${empresaId}::uuid AND estado = 'ACTIVO'
      `,
      this.prisma.$queryRaw<{ neto: Prisma.Decimal; ingresos: Prisma.Decimal; empleados: bigint }[]>`
        SELECT COALESCE(SUM(total_neto), 0) AS neto,
               COALESCE(SUM(total_ingresos), 0) AS ingresos,
               COALESCE(SUM(cantidad_empleados), 0) AS empleados
        FROM rrhh_liquidaciones_cabecera
        WHERE empresa_id = ${empresaId}::uuid
          AND estado IN ('CALCULADA','CERRADA')
          AND periodo_anio = ${anio} AND periodo_mes = ${mes}
      `,
      this.prisma.$queryRaw<{ neto: Prisma.Decimal }[]>`
        SELECT COALESCE(SUM(total_neto), 0) AS neto
        FROM rrhh_liquidaciones_cabecera
        WHERE empresa_id = ${empresaId}::uuid
          AND estado IN ('CALCULADA','CERRADA')
          AND periodo_anio = ${anioMesAnt.anio} AND periodo_mes = ${anioMesAnt.mes}
      `,
      this.prisma.$queryRaw<{ total: Prisma.Decimal; obrero: Prisma.Decimal; patronal: Prisma.Decimal }[]>`
        SELECT COALESCE(SUM(total_a_depositar), 0) AS total,
               COALESCE(SUM(total_aporte_obrero), 0) AS obrero,
               COALESCE(SUM(total_aporte_patronal), 0) AS patronal
        FROM rrhh_reportes_ips
        WHERE empresa_id = ${empresaId}::uuid
          AND estado != 'ANULADO'
          AND periodo_anio = ${anio} AND periodo_mes = ${mes}
      `,
      this.prisma.$queryRaw<{ cantidad: bigint; saldo: Prisma.Decimal }[]>`
        SELECT COUNT(*) AS cantidad, COALESCE(SUM(saldo_pendiente), 0) AS saldo
        FROM rrhh_prestamos
        WHERE empresa_id = ${empresaId}::uuid AND estado = 'ACTIVO'
      `,
      this.prisma.$queryRaw<{ dias: Prisma.Decimal }[]>`
        SELECT COALESCE(SUM(dias_pendientes), 0) AS dias
        FROM rrhh_vacaciones_saldo
        WHERE empresa_id = ${empresaId}::uuid AND dias_pendientes > 0
      `,
      // Proyección simple: promedio últimos 3 meses cerrados.
      this.prisma.$queryRaw<{ promedio: Prisma.Decimal }[]>`
        SELECT COALESCE(AVG(total_neto), 0) AS promedio
        FROM (
          SELECT total_neto FROM rrhh_liquidaciones_cabecera
          WHERE empresa_id = ${empresaId}::uuid
            AND estado = 'CERRADA'
            AND MAKE_DATE(periodo_anio, periodo_mes, 1) >= (DATE_TRUNC('month', CURRENT_DATE) - INTERVAL '3 months')
            AND MAKE_DATE(periodo_anio, periodo_mes, 1) < DATE_TRUNC('month', CURRENT_DATE)
          ORDER BY periodo_anio DESC, periodo_mes DESC
          LIMIT 3
        ) ultimas
      `,
    ]);

    const netoMes = Number(liquidacionMesRes[0]?.neto ?? 0);
    const netoMesAnt = Number(liquidacionAntRes[0]?.neto ?? 0);
    const variacionPct = netoMesAnt > 0 ? Math.round(((netoMes - netoMesAnt) / netoMesAnt) * 100 * 10) / 10 : null;

    return {
      empleadosActivos: Number(empleadosActivosRes[0]?.total ?? 0),
      nominaMes: {
        netoAPagar: netoMes,
        totalIngresos: Number(liquidacionMesRes[0]?.ingresos ?? 0),
        empleados: Number(liquidacionMesRes[0]?.empleados ?? 0),
      },
      nominaMesAnterior: { netoAPagar: netoMesAnt },
      variacionPct,
      ips: {
        total: Number(ipsMesRes[0]?.total ?? 0),
        obrero: Number(ipsMesRes[0]?.obrero ?? 0),
        patronal: Number(ipsMesRes[0]?.patronal ?? 0),
      },
      prestamosActivos: {
        cantidad: Number(prestamosRes[0]?.cantidad ?? 0),
        saldoPendiente: Number(prestamosRes[0]?.saldo ?? 0),
      },
      vacaciones: { diasPendientes: Number(vacacionesPendientesRes[0]?.dias ?? 0) },
      proyeccionNomina: Number(ultimos3MesesRes[0]?.promedio ?? 0),
    };
  }
}

export interface MetricasRRHH {
  empleadosActivos: number;
  nominaMes: { netoAPagar: number; totalIngresos: number; empleados: number };
  nominaMesAnterior: { netoAPagar: number };
  variacionPct: number | null;
  ips: { total: number; obrero: number; patronal: number };
  prestamosActivos: { cantidad: number; saldoPendiente: number };
  vacaciones: { diasPendientes: number };
  proyeccionNomina: number;
}

