import type { Pool } from 'pg';
import { prisma } from '../config/database.js';
import { getRegimenesActivosClavesEfectivos } from './regimen.service.js';
import { contarTareasProximasVencer } from './tareas.service.js';

const VIGENTE = `status NOT IN ('Cancelado', '0')`;

/** Sanitize a contribuyente UUID for safe inline SQL injection */
function sanitizeUuid(id: string): string {
  return id.replace(/[^a-f0-9-]/gi, '');
}

export interface AlertaAuto {
  id: string;
  tipo: string;
  titulo: string;
  mensaje: string;
  prioridad: 'alta' | 'media' | 'baja';
  detalle?: string; // ruta para drill-down
  valor?: number;
}

/**
 * CFDI con discrepancia: facturas recibidas donde regimen_fiscal_receptor
 * no coincide con los regímenes activos del tenant.
 */
async function alertaDiscrepanciaRegimen(
  pool: Pool,
  tenantId: string,
  contribuyenteId?: string | null,
): Promise<AlertaAuto | null> {
  const activos = await getRegimenesActivosClavesEfectivos(tenantId, pool, contribuyenteId);
  if (activos.length === 0) return null;

  const cf = contribuyenteId ? `AND contribuyente_id = '${sanitizeUuid(contribuyenteId)}'` : '';

  const { rows: [r] } = await pool.query(`
    SELECT COUNT(*)::int as total
    FROM cfdis
    WHERE type = 'RECIBIDO' AND status = 'Vigente'
      AND fecha_cancelacion IS NULL
      AND regimen_fiscal_receptor IS NOT NULL
      AND regimen_fiscal_receptor != ALL($1)
      AND id NOT IN (SELECT cfdi_id FROM cfdi_descartados WHERE tipo_alerta = 'discrepancia-regimen')
      ${cf}
  `, [activos]);

  const total = r?.total || 0;
  if (total === 0) return null;

  return {
    id: 'discrepancia-regimen',
    tipo: 'discrepancia',
    titulo: 'CFDI con Discrepancia de Regimen',
    mensaje: `${total} factura(s) recibida(s) con regimen fiscal del receptor que no coincide con los regimenes activos.`,
    prioridad: 'alta',
    detalle: '/alertas/discrepancia-regimen',
    valor: total,
  };
}

/**
 * Calcula el Índice de Herfindahl-Hirschman (IHH).
 * IHH = Σ (cuota_de_mercado_i)^2 × 10000
 */
async function calcularIHH(
  pool: Pool,
  type: 'EMITIDO' | 'RECIBIDO',
  contribuyenteId?: string | null,
): Promise<number> {
  const rfcField = type === 'EMITIDO' ? 'rfc_receptor' : 'rfc_emisor';
  const cf = contribuyenteId ? `AND contribuyente_id = '${sanitizeUuid(contribuyenteId)}'` : '';

  const { rows } = await pool.query(`
    SELECT ${rfcField} as rfc, SUM(total_mxn) as total
    FROM cfdis
    WHERE type = $1 AND tipo_comprobante = 'I' AND ${VIGENTE}
      AND total_mxn > 0
      ${cf}
    GROUP BY ${rfcField}
  `, [type]);

  if (rows.length === 0) return 0;

  const totalGeneral = rows.reduce((s: number, r: any) => s + Number(r.total), 0);
  if (totalGeneral === 0) return 0;

  let ihh = 0;
  for (const row of rows) {
    const cuota = Number(row.total) / totalGeneral;
    ihh += cuota * cuota;
  }

  return Math.round(ihh * 10000);
}

/**
 * Concentración de clientes: IHH >= 2500 en facturas emitidas
 */
async function alertaConcentracionClientes(pool: Pool, contribuyenteId?: string | null): Promise<AlertaAuto | null> {
  const ihh = await calcularIHH(pool, 'EMITIDO', contribuyenteId);
  if (ihh < 2500) return null;

  return {
    id: 'concentracion-clientes',
    tipo: 'concentracion',
    titulo: 'Concentracion de Clientes',
    mensaje: `El indice HHI de clientes es ${ihh.toLocaleString()} (>=2500 indica alta concentracion). Dependencia excesiva en pocos clientes.`,
    prioridad: ihh >= 5000 ? 'alta' : 'media',
    detalle: '/alertas/concentracion-clientes',
    valor: ihh,
  };
}

/**
 * Concentración de proveedores: IHH >= 2500 en facturas recibidas
 */
async function alertaConcentracionProveedores(pool: Pool, contribuyenteId?: string | null): Promise<AlertaAuto | null> {
  const ihh = await calcularIHH(pool, 'RECIBIDO', contribuyenteId);
  if (ihh < 2500) return null;

  return {
    id: 'concentracion-proveedores',
    tipo: 'concentracion',
    titulo: 'Concentracion de Proveedores',
    mensaje: `El indice HHI de proveedores es ${ihh.toLocaleString()} (>=2500 indica alta concentracion). Dependencia excesiva en pocos proveedores.`,
    prioridad: ihh >= 5000 ? 'alta' : 'media',
    detalle: '/alertas/concentracion-proveedores',
    valor: ihh,
  };
}

/**
 * Riesgo cambiario: >10% de facturas en moneda != MXN
 */
async function alertaRiesgoCambiario(pool: Pool, contribuyenteId?: string | null): Promise<AlertaAuto | null> {
  const cf = contribuyenteId ? `AND contribuyente_id = '${sanitizeUuid(contribuyenteId)}'` : '';
  const { rows: [r] } = await pool.query(`
    SELECT
      COUNT(*)::int as total,
      COUNT(CASE WHEN moneda IS NOT NULL AND moneda != 'MXN' THEN 1 END)::int as no_mxn
    FROM cfdis
    WHERE ${VIGENTE} AND tipo_comprobante = 'I'
    ${cf}
  `);

  const total = r?.total || 0;
  const noMxn = r?.no_mxn || 0;
  if (total === 0 || noMxn === 0) return null;

  const porcentaje = Math.round((noMxn / total) * 10000) / 100;
  if (porcentaje <= 10) return null;

  return {
    id: 'riesgo-cambiario',
    tipo: 'riesgo',
    titulo: 'Riesgo Cambiario',
    mensaje: `${porcentaje}% de las facturas (${noMxn} de ${total}) estan en moneda extranjera. Exposicion a fluctuaciones del tipo de cambio.`,
    prioridad: porcentaje > 30 ? 'alta' : 'media',
    valor: porcentaje,
  };
}

/**
 * Riesgo de cancelaciones: >10% de facturas canceladas en últimos 5 años
 */
async function alertaRiesgoCancelaciones(pool: Pool, contribuyenteId?: string | null): Promise<AlertaAuto | null> {
  const hace5 = new Date();
  hace5.setFullYear(hace5.getFullYear() - 5);
  const fechaDesde = hace5.toISOString().split('T')[0];

  const cf = contribuyenteId ? `AND contribuyente_id = '${sanitizeUuid(contribuyenteId)}'` : '';

  const { rows: [r] } = await pool.query(`
    SELECT
      COUNT(*)::int as total,
      COUNT(CASE WHEN status IN ('Cancelado', '0') THEN 1 END)::int as cancelados
    FROM cfdis
    WHERE (fecha_emision - interval '1 hour') >= $1::date
    ${cf}
  `, [fechaDesde]);

  const total = r?.total || 0;
  const cancelados = r?.cancelados || 0;
  if (total === 0 || cancelados === 0) return null;

  const porcentaje = Math.round((cancelados / total) * 10000) / 100;
  if (porcentaje <= 10) return null;

  return {
    id: 'riesgo-cancelaciones',
    tipo: 'riesgo',
    titulo: 'Riesgo de Cancelaciones',
    mensaje: `${porcentaje}% de las facturas (${cancelados} de ${total}) en los ultimos 5 años han sido canceladas.`,
    prioridad: porcentaje > 25 ? 'alta' : 'media',
    detalle: '/alertas/cancelaciones',
    valor: porcentaje,
  };
}

/**
 * Gastos recibidos pagados en efectivo > $2,000 — Art. 27 fracción III LISR.
 * No son deducibles. Surface el total + conteo para que el contador entienda
 * el impacto y, si aplica, gestione cambio de forma de pago con el proveedor.
 */
async function alertaRiesgoTransaccional(pool: Pool, contribuyenteId?: string | null): Promise<AlertaAuto | null> {
  const cf = contribuyenteId ? `AND contribuyente_id = '${sanitizeUuid(contribuyenteId)}'` : '';
  const { rows: [r] } = await pool.query(`
    SELECT
      COUNT(*)::int                                            AS facturas,
      COALESCE(SUM(COALESCE(total_mxn, 0)), 0)::numeric(14,2)  AS monto
    FROM cfdis
    WHERE ${VIGENTE}
      AND type = 'RECIBIDO'
      AND tipo_comprobante = 'I'
      AND metodo_pago = 'PUE'
      AND forma_pago = '01'
      AND COALESCE(total_mxn, 0) > 2000
      ${cf}
  `);

  const facturas = r?.facturas || 0;
  const monto = Number(r?.monto || 0);
  if (facturas === 0) return null;

  return {
    id: 'gastos-no-deducibles-efectivo',
    tipo: 'riesgo',
    titulo: 'Gastos no deducibles (efectivo > $2,000)',
    mensaje: `${facturas} factura${facturas === 1 ? '' : 's'} recibida${facturas === 1 ? '' : 's'} por $${monto.toLocaleString('es-MX')} MXN se pagaron en efectivo (>$2,000). Art. 27 fracción III LISR las hace NO deducibles para ISR.`,
    prioridad: monto > 50000 ? 'alta' : 'media',
    detalle: '/impuestos',
    valor: monto,
  };
}

/**
 * Estatus lista negra: si el RFC del tenant/contribuyente aparece en la lista negra
 */
async function alertaListaNegraPropia(
  pool: Pool,
  tenantId: string,
  contribuyenteId?: string | null,
): Promise<AlertaAuto | null> {
  let rfc: string | undefined;

  if (contribuyenteId) {
    const safeId = sanitizeUuid(contribuyenteId);
    const { rows } = await pool.query(
      'SELECT rfc FROM contribuyentes WHERE entidad_id = $1',
      [safeId],
    );
    rfc = rows[0]?.rfc;
  } else {
    const tenant = await prisma.tenant.findUnique({
      where: { id: tenantId },
      select: { rfc: true },
    });
    rfc = tenant?.rfc;
  }

  if (!rfc) return null;

  const registro = await prisma.listaNegra.findUnique({
    where: { rfc },
  });
  if (!registro) return null;

  return {
    id: 'lista-negra-propia',
    tipo: 'lista-negra',
    titulo: 'RFC en Lista Negra del SAT',
    mensaje: `Tu RFC (${rfc}) aparece en la lista del Art. 69-B del CFF con situacion: ${registro.situacion}.`,
    prioridad: 'alta',
  };
}

/**
 * Factura emitida a cliente en lista negra
 */
async function alertaClienteListaNegra(pool: Pool, contribuyenteId?: string | null): Promise<AlertaAuto | null> {
  // Fallback: consultar directo si dblink no funciona
  const listaRfcs = await prisma.listaNegra.findMany({
    where: { situacion: { in: ['Definitivo', 'Presunto'] } },
    select: { rfc: true },
  });
  const rfcSet = new Set(listaRfcs.map(l => l.rfc));

  const cf = contribuyenteId ? `AND contribuyente_id = '${sanitizeUuid(contribuyenteId)}'` : '';

  const { rows } = await pool.query(`
    SELECT DISTINCT rfc_receptor as rfc
    FROM cfdis
    WHERE type = 'EMITIDO' AND ${VIGENTE} AND tipo_comprobante = 'I'
    ${cf}
  `);

  const clientesEnLista = rows.filter((r: any) => rfcSet.has(r.rfc));
  if (clientesEnLista.length === 0) return null;

  return {
    id: 'lista-negra-clientes',
    tipo: 'lista-negra',
    titulo: 'Facturas Emitidas a Clientes en Lista Negra',
    mensaje: `${clientesEnLista.length} cliente(s) a los que has facturado aparecen en la lista negra del SAT (Art. 69-B).`,
    prioridad: 'alta',
    detalle: '/alertas/lista-negra-clientes',
    valor: clientesEnLista.length,
  };
}

/**
 * Factura recibida de proveedor en lista negra
 */
async function alertaProveedorListaNegra(pool: Pool, contribuyenteId?: string | null): Promise<AlertaAuto | null> {
  const listaRfcs = await prisma.listaNegra.findMany({
    where: { situacion: { in: ['Definitivo', 'Presunto'] } },
    select: { rfc: true },
  });
  const rfcSet = new Set(listaRfcs.map(l => l.rfc));

  const cf = contribuyenteId ? `AND contribuyente_id = '${sanitizeUuid(contribuyenteId)}'` : '';

  const { rows } = await pool.query(`
    SELECT DISTINCT rfc_emisor as rfc
    FROM cfdis
    WHERE type = 'RECIBIDO' AND ${VIGENTE} AND tipo_comprobante = 'I'
    ${cf}
  `);

  const proveedoresEnLista = rows.filter((r: any) => rfcSet.has(r.rfc));
  if (proveedoresEnLista.length === 0) return null;

  return {
    id: 'lista-negra-proveedores',
    tipo: 'lista-negra',
    titulo: 'Facturas Recibidas de Proveedores en Lista Negra',
    mensaje: `${proveedoresEnLista.length} proveedor(es) de los que has recibido facturas aparecen en la lista negra del SAT (Art. 69-B).`,
    prioridad: 'alta',
    detalle: '/alertas/lista-negra-proveedores',
    valor: proveedoresEnLista.length,
  };
}

/**
 * Facturas de periodos anteriores canceladas este mes.
 * Detecta CFDIs cuya fecha_cancelacion cae en el mes actual pero
 * cuya fecha_emision es de un mes anterior.
 */
async function alertaCancelacionPeriodoAnterior(pool: Pool, contribuyenteId?: string | null): Promise<AlertaAuto | null> {
  const ahora = new Date();
  const inicioMes = `${ahora.getFullYear()}-${String(ahora.getMonth() + 1).padStart(2, '0')}-01`;

  const cf = contribuyenteId ? `AND contribuyente_id = '${sanitizeUuid(contribuyenteId)}'` : '';

  const { rows: [r] } = await pool.query(`
    SELECT COUNT(*)::int as total,
      COALESCE(SUM(COALESCE(total_mxn, 0)), 0) as monto
    FROM cfdis
    WHERE status IN ('Cancelado', '0')
      AND fecha_cancelacion >= $1::date
      AND COALESCE(fecha_efectiva, fecha_emision - interval '1 hour') < $1::date
      ${cf}
  `, [inicioMes]);

  const total = r?.total || 0;
  if (total === 0) return null;

  const monto = Number(r.monto);
  const montoFmt = monto.toLocaleString('es-MX', { style: 'currency', currency: 'MXN' });

  return {
    id: 'cancelacion-periodo-anterior',
    tipo: 'cancelacion-retroactiva',
    titulo: 'Facturas de Periodos Anteriores Canceladas',
    mensaje: `${total} factura(s) emitida(s) en meses anteriores fueron canceladas este mes por un total de ${montoFmt}. Esto puede afectar declaraciones ya presentadas.`,
    prioridad: 'alta',
    detalle: '/alertas/cancelaciones-periodo-anterior',
    valor: total,
  };
}

/**
 * CFDIs tipo E (Egreso / nota de crédito) con cfdi_tipo_relacion != '07'
 * cuya(s) referencia(s) en cfdis_relacionados también aparecen en otro CFDI
 * con cfdi_tipo_relacion = '07'. Señal de que el emisor debió usar 07
 * (aplicación de anticipo) pero puso 01/02/03/04: inflá gastos e IVA
 * acreditable contra un anticipo ya consumido.
 *
 * La detección usa overlap de arrays (`&&`) sobre cfdis_relacionados
 * pipe-separados — si X y Y comparten al menos un UUID referenciado y Y
 * es 07, X es sospechoso.
 */
const SOSPECHOSA_TIPO_RELACION_WHERE = `
  c.tipo_comprobante = 'E'
  AND c.status NOT IN ('Cancelado', '0')
  AND c.cfdi_tipo_relacion IS NOT NULL
  AND c.cfdi_tipo_relacion <> '07'
  AND c.cfdis_relacionados IS NOT NULL
  AND c.cfdis_relacionados <> ''
  AND EXISTS (
    SELECT 1 FROM cfdis y
    WHERE y.id <> c.id
      AND y.cfdi_tipo_relacion = '07'
      AND y.status NOT IN ('Cancelado', '0')
      AND y.cfdis_relacionados IS NOT NULL
      AND y.cfdis_relacionados <> ''
      AND string_to_array(LOWER(y.cfdis_relacionados), '|')
          && string_to_array(LOWER(c.cfdis_relacionados), '|')
  )
  AND c.id NOT IN (
    SELECT cfdi_id FROM cfdi_descartados WHERE tipo_alerta = 'tipo-relacion-sospechosa'
  )
`;

async function alertaTipoRelacionSospechosa(
  pool: Pool,
  contribuyenteId?: string | null,
): Promise<AlertaAuto | null> {
  const cf = contribuyenteId ? `AND c.contribuyente_id = '${sanitizeUuid(contribuyenteId)}'` : '';
  const { rows: [r] } = await pool.query(`
    SELECT COUNT(*)::int AS total
    FROM cfdis c
    WHERE ${SOSPECHOSA_TIPO_RELACION_WHERE}
    ${cf}
  `);

  const total = r?.total || 0;
  if (total === 0) return null;

  return {
    id: 'tipo-relacion-sospechosa',
    tipo: 'cfdi-inconsistente',
    titulo: 'Nota de Crédito con Tipo de Relación sospechoso',
    mensaje: `${total} CFDI(s) tipo E con TipoRelacion distinto de 07 referencian un CFDI tratado como anticipo por otra factura. Revisa si deberían haberse emitido como 07 (aplicación de anticipo).`,
    prioridad: 'alta',
    detalle: '/alertas/tipo-relacion-sospechosa',
    valor: total,
  };
}

/** Exportado para reutilizar en el controller de drill-down. */
export const SOSPECHOSA_TIPO_RELACION_WHERE_EXPORT = SOSPECHOSA_TIPO_RELACION_WHERE;

/**
 * Tareas operativas próximas a vencer (≤3 días). Solo aplica cuando hay un
 * contribuyente seleccionado — sin contribuyente, no se puede contar
 * porque las tareas son siempre per-contribuyente.
 */
async function alertaTareasProximasVencer(
  pool: Pool,
  contribuyenteId?: string | null,
): Promise<AlertaAuto | null> {
  if (!contribuyenteId) return null;
  const { total } = await contarTareasProximasVencer(pool, contribuyenteId);
  if (total === 0) return null;
  return {
    id: 'tareas-proximas-vencer',
    tipo: 'tareas',
    titulo: 'Tareas próximas a vencer',
    mensaje: `${total} tarea(s) operativa(s) vencen en los próximos 3 días.`,
    prioridad: 'media',
    detalle: '/configuracion/obligaciones',
    valor: total,
  };
}

/**
 * RESICO PF cerca de salir del régimen por exceso de ingresos anuales.
 *
 * Art. 113-E LISR — el contribuyente PF en RESICO debe salir del régimen si
 * sus ingresos del ejercicio exceden $3,500,000. **Importante:** el SAT
 * considera ingresos acumulados de TODOS los regímenes del contribuyente, no
 * solo los del 626. Por eso este query no filtra por `regimen_fiscal_emisor`.
 *
 * Umbrales:
 *   - $2,500,000 → alerta media (margen ~$1M)
 *   - $3,000,000 → alerta alta (margen ~$500k al límite)
 *   - $3,500,000 → alerta alta crítica ("ya superaste")
 *
 * Aplica solo cuando:
 *   1. Hay un contribuyente seleccionado (per-tenant no tiene sentido — la
 *      alerta es por entidad fiscal individual)
 *   2. RFC de 13 caracteres (Persona Física — RESICO PM no tiene este límite)
 *   3. Régimen 626 está en su lista de regímenes activos
 *
 * Cálculo de ingresos: agregado de CFDIs emitidos vigentes del año en curso:
 *   + I PUE (cobradas al emitir)
 *   + P (complementos de pago — cobros de PPD anteriores)
 *   - E PUE (notas de crédito netan)
 *
 * Sin desglose por régimen ni filtro de conciliación. Se usa `total_mxn` como
 * proxy de ingreso (incluye IVA — sobreestima ~16%, conservador para alerta).
 */
async function alertaResicoPfLimiteIngresos(
  pool: Pool,
  contribuyenteId?: string | null,
): Promise<AlertaAuto | null> {
  if (!contribuyenteId) return null;

  const safeId = sanitizeUuid(contribuyenteId);

  // Verificar elegibilidad: PF (RFC 13) + régimen 626 activo
  const { rows: contribRows } = await pool.query(
    `SELECT rfc, regimen_fiscal FROM contribuyentes WHERE entidad_id = $1`,
    [safeId],
  );
  const contrib = contribRows[0];
  if (!contrib) return null;

  const rfc: string = contrib.rfc || '';
  if (rfc.length !== 13) return null; // PM no aplica

  const regimenesCsv: string = contrib.regimen_fiscal || '';
  const regimenes = regimenesCsv.split(',').map((s: string) => s.trim()).filter(Boolean);
  if (!regimenes.includes('626')) return null;

  // Suma ingresos del año en curso, agregado de TODOS los regímenes
  const año = new Date().getFullYear();
  const { rows: [r] } = await pool.query(`
    SELECT COALESCE(SUM(
      CASE
        WHEN tipo_comprobante = 'I' AND metodo_pago = 'PUE' THEN COALESCE(total_mxn, 0)
        WHEN tipo_comprobante = 'P'                          THEN COALESCE(monto_pago_mxn, 0)
        WHEN tipo_comprobante = 'E' AND metodo_pago = 'PUE' THEN -COALESCE(total_mxn, 0)
        ELSE 0
      END
    ), 0)::numeric AS ingresos
    FROM cfdis
    WHERE type = 'EMITIDO'
      AND status NOT IN ('Cancelado', '0')
      AND EXTRACT(YEAR FROM COALESCE(fecha_efectiva, fecha_emision - interval '1 hour')) = $1
      AND contribuyente_id = $2
  `, [año, safeId]);

  const ingresos = Number(r?.ingresos || 0);
  const UMBRAL_AVISO = 2_500_000;
  const UMBRAL_ALTO = 3_000_000;
  const LIMITE_LEGAL = 3_500_000; // Art. 113-E LISR

  if (ingresos < UMBRAL_AVISO) return null;

  const ingresosFmt = ingresos.toLocaleString('es-MX', {
    style: 'currency', currency: 'MXN', maximumFractionDigits: 0,
  });

  let prioridad: 'alta' | 'media' = 'media';
  let titulo = `RESICO PF cerca del límite anual`;
  let mensaje = `Ingresos acumulados ${año} (todos los regímenes): ${ingresosFmt}. Límite RESICO PF: $3,500,000 (Art. 113-E LISR). Se considera ingresos de TODOS los regímenes, no solo del 626.`;

  if (ingresos >= LIMITE_LEGAL) {
    prioridad = 'alta';
    titulo = `RESICO PF: límite anual EXCEDIDO`;
    mensaje = `Ingresos acumulados ${año} (todos los regímenes): ${ingresosFmt}. Excede el límite de $3,500,000 del Art. 113-E LISR. El contribuyente debe salir de RESICO PF y tributar bajo régimen general (PF Empresarial).`;
  } else if (ingresos >= UMBRAL_ALTO) {
    prioridad = 'alta';
    titulo = `RESICO PF: cerca del límite ($3M+)`;
  }

  return {
    id: 'resico-pf-limite-ingresos',
    tipo: 'limite-regimen',
    titulo,
    mensaje,
    prioridad,
    valor: ingresos,
  };
}

/**
 * Alerta si la última Opinión de Cumplimiento no es Positiva.
 */
async function alertaOpinionCumplimiento(pool: Pool, contribuyenteId?: string | null): Promise<AlertaAuto | null> {
  let rfcFilter = '';
  if (contribuyenteId) {
    const safeId = sanitizeUuid(contribuyenteId);
    const { rows: rfcRows } = await pool.query(
      'SELECT rfc FROM contribuyentes WHERE entidad_id = $1',
      [safeId],
    );
    const rfc: string | undefined = rfcRows[0]?.rfc;
    if (rfc) rfcFilter = `WHERE rfc = '${rfc.replace(/'/g, "''")}'`;
  }

  const { rows } = await pool.query(`
    SELECT estatus, fecha_consulta
    FROM opiniones_cumplimiento
    ${rfcFilter}
    ORDER BY fecha_consulta DESC
    LIMIT 1
  `);

  if (rows.length === 0) return null;

  const { estatus, fecha_consulta } = rows[0];
  if (estatus === 'Positiva') return null;

  const fecha = new Date(fecha_consulta).toLocaleDateString('es-MX');

  return {
    id: 'opinion-cumplimiento-negativa',
    tipo: 'opinion-cumplimiento',
    titulo: `Opinión de Cumplimiento: ${estatus}`,
    mensaje: `Tu Opinión de Cumplimiento ante el SAT es ${estatus}. Última consulta: ${fecha}. Revisa tus obligaciones fiscales.`,
    prioridad: 'alta',
    valor: 1,
  };
}

/**
 * Genera todas las alertas automáticas para un tenant.
 * Cada alerta se envuelve en try/catch para que un fallo en una no
 * bloquee el resto (robustez ante timeouts o errores transitorios).
 */
export async function generarAlertasAutomaticas(
  pool: Pool,
  tenantId: string,
  contribuyenteId?: string | null,
): Promise<AlertaAuto[]> {
  const generadores: { name: string; fn: () => Promise<AlertaAuto | null> }[] = [
    { name: 'lista-negra-propia', fn: () => alertaListaNegraPropia(pool, tenantId, contribuyenteId) },
    { name: 'lista-negra-clientes', fn: () => alertaClienteListaNegra(pool, contribuyenteId) },
    { name: 'lista-negra-proveedores', fn: () => alertaProveedorListaNegra(pool, contribuyenteId) },
    { name: 'discrepancia-regimen', fn: () => alertaDiscrepanciaRegimen(pool, tenantId, contribuyenteId) },
    { name: 'concentracion-clientes', fn: () => alertaConcentracionClientes(pool, contribuyenteId) },
    { name: 'concentracion-proveedores', fn: () => alertaConcentracionProveedores(pool, contribuyenteId) },
    { name: 'riesgo-cambiario', fn: () => alertaRiesgoCambiario(pool, contribuyenteId) },
    { name: 'riesgo-cancelaciones', fn: () => alertaRiesgoCancelaciones(pool, contribuyenteId) },
    { name: 'riesgo-transaccional', fn: () => alertaRiesgoTransaccional(pool, contribuyenteId) },
    { name: 'cancelacion-periodo-anterior', fn: () => alertaCancelacionPeriodoAnterior(pool, contribuyenteId) },
    { name: 'opinion-cumplimiento', fn: () => alertaOpinionCumplimiento(pool, contribuyenteId) },
    { name: 'tipo-relacion-sospechosa', fn: () => alertaTipoRelacionSospechosa(pool, contribuyenteId) },
    { name: 'tareas-proximas-vencer', fn: () => alertaTareasProximasVencer(pool, contribuyenteId) },
    { name: 'resico-pf-limite-ingresos', fn: () => alertaResicoPfLimiteIngresos(pool, contribuyenteId) },
  ];

  const alertas: AlertaAuto[] = [];
  for (const g of generadores) {
    try {
      const a = await g.fn();
      if (a) alertas.push(a);
    } catch (err: any) {
      console.error(`[AlertasAuto] Fallo ${g.name} (tenant=${tenantId}, contribuyente=${contribuyenteId}):`, err.message || err);
    }
  }

  if (alertas.length > 0) {
    console.log(`[AlertasAuto] tenant=${tenantId} contribuyente=${contribuyenteId || 'null'} generadas=${alertas.map(a => a.id).join(', ')}`);
  }

  return alertas;
}

/**
 * Breakdown mensual de discrepancias de régimen de los últimos N meses.
 * Cuenta facturas RECIBIDAS donde regimen_fiscal_receptor no coincide con
 * los regímenes activos del tenant. Útil para el correo semanal — el cliente
 * ve cuántas facturas con error le emitieron mes por mes.
 */
export async function getDiscrepanciasPorMes(
  pool: Pool,
  tenantId: string,
  monthsBack = 6,
  contribuyenteId?: string | null,
): Promise<Array<{ año: number; mes: number; count: number; label: string }>> {
  const activos = await getRegimenesActivosClavesEfectivos(tenantId, pool, contribuyenteId);
  if (activos.length === 0) return [];

  const desde = new Date();
  desde.setMonth(desde.getMonth() - (monthsBack - 1));
  desde.setDate(1);
  const desdeStr = desde.toISOString().split('T')[0];

  const cf = contribuyenteId ? `AND contribuyente_id = '${sanitizeUuid(contribuyenteId)}'` : '';

  const { rows } = await pool.query(`
    SELECT
      EXTRACT(YEAR FROM COALESCE(fecha_efectiva, fecha_emision - interval '1 hour'))::int as año,
      EXTRACT(MONTH FROM COALESCE(fecha_efectiva, fecha_emision - interval '1 hour'))::int as mes,
      COUNT(*)::int as count
    FROM cfdis
    WHERE type = 'RECIBIDO' AND ${VIGENTE}
      AND regimen_fiscal_receptor IS NOT NULL
      AND regimen_fiscal_receptor != ALL($1)
      AND fecha_emision >= $2::date
      ${cf}
    GROUP BY año, mes
    ORDER BY año DESC, mes DESC
  `, [activos, desdeStr]);

  const NOMBRES_MES = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'];

  return rows.map((r: any) => ({
    año: r.año,
    mes: r.mes,
    count: r.count,
    label: `${NOMBRES_MES[r.mes - 1]} ${r.año}`,
  }));
}
