import type { Pool } from 'pg';
import type { Cfdi, CfdiFilters, CfdiListResponse } from '@horux/shared';
import { markForInvalidation } from './metricas.service.js';
import { recomputarSaldoPendiente, uuidsAfectadosPorCfdi } from '../utils/saldo.js';

// Common SELECT columns mapping DB → camelCase
const CFDI_SELECT = `
  id, year, month, type, uuid, serie, folio, status,
  fecha_emision as "fechaEmision",
  rfc_emisor_id as "rfcEmisorId", rfc_emisor as "rfcEmisor", nombre_emisor as "nombreEmisor",
  rfc_receptor_id as "rfcReceptorId", rfc_receptor as "rfcReceptor", nombre_receptor as "nombreReceptor",
  subtotal, subtotal_mxn as "subtotalMxn",
  descuento, descuento_mxn as "descuentoMxn",
  total, total_mxn as "totalMxn",
  saldo_insoluto as "saldoInsoluto",
  moneda, tipo_cambio as "tipoCambio",
  tipo_comprobante as "tipoComprobante",
  metodo_pago as "metodoPago", forma_pago as "formaPago",
  uso_cfdi as "usoCfdi",
  pac, fecha_cert_sat as "fechaCertSat",
  fecha_cancelacion as "fechaCancelacion",
  uuid_relacionado as "uuidRelacionado",
  isr_retencion as "isrRetencion", isr_retencion_mxn as "isrRetencionMxn",
  iva_traslado as "ivaTraslado", iva_traslado_mxn as "ivaTrasladoMxn",
  iva_retencion as "ivaRetencion", iva_retencion_mxn as "ivaRetencionMxn",
  ieps_traslado as "iepsTraslado", ieps_traslado_mxn as "iepsTrasladoMxn",
  ieps_retencion as "iepsRetencion", ieps_retencion_mxn as "iepsRetencionMxn",
  impuestos_locales_trasladado as "impuestosLocalesTrasladado",
  impuestos_locales_trasladado_mxn as "impuestosLocalesTrasladoMxn",
  impuestos_locales_retenidos as "impuestosLocalesRetenidos",
  impuestos_locales_retenidos_mxn as "impuestosLocalesRetenidosMxn",
  monto_pago as "montoPago", monto_pago_mxn as "montoPagoMxn",
  fecha_pago_p as "fechaPagoP", num_parcialidad as "numParcialidad",
  isr_retencion_pago as "isrRetencionPago", isr_retencion_pago_mxn as "isrRetencionPagoMxn",
  iva_traslado_pago as "ivaTrasladoPago", iva_traslado_pago_mxn as "ivaTrasladoPagoMxn",
  iva_retencion_pago as "ivaRetencionPago", iva_retencion_pago_mxn as "ivaRetencionPagoMxn",
  ieps_traslado_pago as "iepsTrasladoPago", ieps_traslado_pago_mxn as "iepsTrasladoPagoMxn",
  ieps_retencion_pago as "iepsRetencionPago", ieps_retencion_pago_mxn as "iepsRetencionPagoMxn",
  saldo_pendiente as "saldoPendiente", saldo_pendiente_mxn as "saldoPendienteMxn",
  fecha_liquidacion as "fechaLiquidacion",
  fecha_pago as "fechaPago",
  fecha_inicial_pago as "fechaInicialPago",
  fecha_final_pago as "fechaFinalPago",
  num_dias_pagados as "numDiasPagados",
  num_seguro_social as "numSeguroSocial", puesto,
  salario_base_cot_apor as "salarioBaseCotApor",
  salario_base_cot_apor_mxn as "salarioBaseCotAporMxn",
  salario_diario_integrado as "salarioDiarioIntegrado",
  salario_diario_integrado_mxn as "salarioDiarioIntegradoMxn",
  total_percepciones as "totalPercepciones",
  total_percepciones_mxn as "totalPercepcionesMxn",
  total_deducciones as "totalDeducciones",
  total_deducciones_mxn as "totalDeduccionesMxn",
  imp_retenidos_nomina as "impRetenidosNomina",
  imp_retenidos_nomina_mxn as "impRetenidosNominaMxn",
  otras_deducciones_nomina as "otrasDeduccionesNomina",
  otras_deducciones_nomina_mxn as "otrasDeduccionesNominaMxn",
  subsidio_causado as "subsidioCausado",
  subsidio_causado_mxn as "subsidioCausadoMxn",
  conciliado,
  regimen_fiscal_emisor as "regimenFiscalEmisor",
  regimen_fiscal_receptor as "regimenFiscalReceptor",
  xml_url as "xmlUrl", pdf_url as "pdfUrl",
  xml_original as "xmlOriginal",
  cfdi_tipo_relacion as "cfdiTipoRelacion",
  cfdis_relacionados as "cfdisRelacionados",
  last_sat_sync as "lastSatSync",
  sat_sync_job_id as "satSyncJobId",
  source, facturapi_id as "facturapiId",
  creado_en as "creadoEn", actualizado_en as "actualizadoEn",
  contribuyente_id AS "contribuyenteId"
`;

export async function getCfdis(pool: Pool, filters: CfdiFilters): Promise<CfdiListResponse> {
  const page = filters.page || 1;
  const limit = filters.limit || 20;
  const offset = (page - 1) * limit;

  let whereClause = 'WHERE 1=1';
  const params: any[] = [];
  let paramIndex = 1;

  // El filtro "tipo" (EMITIDO / RECIBIDO) usa la posición del RFC del
  // contribuyente cuando viene contribuyenteId — más confiable que la
  // columna `type`, que puede quedar inconsistente cuando dos
  // contribuyentes del mismo tenant se facturan entre sí. Se aplica
  // abajo cuando ya conocemos el RFC vía la subquery.
  if (filters.tipo && !filters.contribuyenteId) {
    whereClause += ` AND type = $${paramIndex++}`;
    params.push(filters.tipo);
  }

  if (filters.tipoComprobante) {
    whereClause += ` AND tipo_comprobante = $${paramIndex++}`;
    params.push(filters.tipoComprobante);
  }

  if (filters.estado) {
    whereClause += ` AND status = $${paramIndex++}`;
    params.push(filters.estado);
  }

  if (filters.fechaInicio) {
    whereClause += ` AND fecha_emision >= $${paramIndex++}::date`;
    params.push(filters.fechaInicio);
  }

  if (filters.fechaFin) {
    whereClause += ` AND fecha_emision <= ($${paramIndex++}::date + interval '1 day')`;
    params.push(filters.fechaFin);
  }

  if (filters.rfc) {
    whereClause += ` AND (rfc_emisor ILIKE $${paramIndex} OR rfc_receptor ILIKE $${paramIndex++})`;
    params.push(`%${filters.rfc}%`);
  }

  if (filters.emisor) {
    whereClause += ` AND (rfc_emisor ILIKE $${paramIndex} OR nombre_emisor ILIKE $${paramIndex++})`;
    params.push(`%${filters.emisor}%`);
  }

  if (filters.receptor) {
    whereClause += ` AND (rfc_receptor ILIKE $${paramIndex} OR nombre_receptor ILIKE $${paramIndex++})`;
    params.push(`%${filters.receptor}%`);
  }

  if (filters.search) {
    whereClause += ` AND (uuid ILIKE $${paramIndex} OR nombre_emisor ILIKE $${paramIndex} OR nombre_receptor ILIKE $${paramIndex} OR rfc_emisor ILIKE $${paramIndex} OR rfc_receptor ILIKE $${paramIndex++})`;
    params.push(`%${filters.search}%`);
  }

  if (filters.contribuyenteId) {
    // Lado del contribuyente: si filters.tipo viene, restringe a EMITIDO
    // (rfc_emisor = X) o RECIBIDO (rfc_receptor = X). Si no, ambos lados
    // (OR contribuyente_id = X para casos donde el RFC no quedó
    // correctamente asignado pero el tenant lo poseía).
    if (filters.tipo === 'EMITIDO') {
      whereClause += ` AND rfc_emisor = (SELECT rfc FROM contribuyentes WHERE entidad_id = $${paramIndex++})`;
      params.push(filters.contribuyenteId);
    } else if (filters.tipo === 'RECIBIDO') {
      whereClause += ` AND rfc_receptor = (SELECT rfc FROM contribuyentes WHERE entidad_id = $${paramIndex++})`;
      params.push(filters.contribuyenteId);
    } else {
      whereClause += ` AND (contribuyente_id = $${paramIndex} OR rfc_emisor = (SELECT rfc FROM contribuyentes WHERE entidad_id = $${paramIndex}) OR rfc_receptor = (SELECT rfc FROM contribuyentes WHERE entidad_id = $${paramIndex++}))`;
      params.push(filters.contribuyenteId);
    }
  }

  params.push(limit, offset);
  const { rows: dataWithCount } = await pool.query(`
    SELECT ${CFDI_SELECT},
      COUNT(*) OVER() as total_count
    FROM cfdis
    ${whereClause}
    ORDER BY fecha_emision DESC
    LIMIT $${paramIndex++} OFFSET $${paramIndex}
  `, params);

  const total = Number(dataWithCount[0]?.total_count || 0);
  const data = dataWithCount.map(({ total_count, ...cfdi }: any) => cfdi) as Cfdi[];

  return {
    data,
    total,
    page,
    limit,
    totalPages: Math.ceil(total / limit),
  };
}

export async function getCfdiById(pool: Pool, id: string): Promise<Cfdi | null> {
  const { rows } = await pool.query(`
    SELECT ${CFDI_SELECT}
    FROM cfdis
    WHERE id = $1
  `, [id]);

  return rows[0] || null;
}

export async function getConceptos(pool: Pool, cfdiId: string): Promise<any[]> {
  const { rows } = await pool.query(`
    SELECT
      id, cfdi_id as "cfdiId",
      clave_prod_serv as "claveProdServ",
      no_identificacion as "noIdentificacion",
      descripcion, cantidad,
      clave_unidad as "claveUnidad", unidad,
      valor_unitario as "valorUnitario",
      valor_unitario_mxn as "valorUnitarioMxn",
      importe, importe_mxn as "importeMxn",
      descuento, descuento_mxn as "descuentoMxn",
      isr_retencion as "isrRetencion",
      isr_retencion_mxn as "isrRetencionMxn",
      iva_traslado as "ivaTraslado",
      iva_traslado_mxn as "ivaTrasladoMxn",
      iva_retencion as "ivaRetencion",
      iva_retencion_mxn as "ivaRetencionMxn",
      ieps_traslado as "iepsTraslado",
      ieps_traslado_mxn as "iepsTrasladoMxn",
      ieps_retencion as "iepsRetencion",
      ieps_retencion_mxn as "iepsRetencionMxn"
    FROM cfdi_conceptos
    WHERE cfdi_id = $1
    ORDER BY id
  `, [cfdiId]);
  return rows;
}

export async function getXmlById(pool: Pool, id: string): Promise<string | null> {
  const { rows } = await pool.query(`
    SELECT xml_original FROM cfdis WHERE id = $1
  `, [id]);

  return rows[0]?.xml_original || null;
}

export interface CreateCfdiData {
  uuid: string;
  type: 'EMITIDO' | 'RECIBIDO';
  serie?: string;
  folio?: string;
  status?: string;
  fechaEmision: string;
  rfcEmisor: string;
  nombreEmisor: string;
  rfcReceptor: string;
  nombreReceptor: string;
  subtotal: number;
  subtotalMxn?: number;
  descuento?: number;
  descuentoMxn?: number;
  total: number;
  totalMxn?: number;
  saldoInsoluto?: string;
  moneda?: string;
  tipoCambio?: number;
  tipoComprobante?: string;
  metodoPago?: string;
  formaPago?: string;
  usoCfdi?: string;
  pac?: string;
  fechaCertSat?: string;
  fechaCancelacion?: string;
  uuidRelacionado?: string;
  isrRetencion?: number;
  isrRetencionMxn?: number;
  ivaTraslado?: number;
  ivaTrasladoMxn?: number;
  ivaRetencion?: number;
  ivaRetencionMxn?: number;
  iepsTraslado?: number;
  iepsTrasladoMxn?: number;
  iepsRetencion?: number;
  iepsRetencionMxn?: number;
  impuestosLocalesTrasladado?: number;
  impuestosLocalesTrasladoMxn?: number;
  impuestosLocalesRetenidos?: number;
  impuestosLocalesRetenidosMxn?: number;
  montoPago?: number;
  montoPagoMxn?: number;
  fechaPagoP?: string;
  numParcialidad?: string;
  isrRetencionPago?: number;
  isrRetencionPagoMxn?: number;
  ivaTrasladoPago?: number;
  ivaTrasladoPagoMxn?: number;
  ivaRetencionPago?: number;
  ivaRetencionPagoMxn?: number;
  iepsTrasladoPago?: number;
  iepsTrasladoPagoMxn?: number;
  iepsRetencionPago?: number;
  iepsRetencionPagoMxn?: number;
  saldoPendiente?: number;
  saldoPendienteMxn?: number;
  fechaLiquidacion?: string;
  fechaPago?: string;
  fechaInicialPago?: string;
  fechaFinalPago?: string;
  numDiasPagados?: number;
  numSeguroSocial?: string;
  puesto?: string;
  salarioBaseCotApor?: number;
  salarioBaseCotAporMxn?: number;
  salarioDiarioIntegrado?: number;
  salarioDiarioIntegradoMxn?: number;
  totalPercepciones?: number;
  totalPercepcionesMxn?: number;
  totalDeducciones?: number;
  totalDeduccionesMxn?: number;
  impRetenidosNomina?: number;
  impRetenidosNominaMxn?: number;
  otrasDeduccionesNomina?: number;
  otrasDeduccionesNominaMxn?: number;
  subsidioCausado?: number;
  subsidioCausadoMxn?: number;
  conciliado?: string;
  regimenFiscalEmisor?: string;
  regimenFiscalReceptor?: string;
  xmlUrl?: string;
  pdfUrl?: string;
  xmlOriginal?: string;
  cfdiTipoRelacion?: string;
  cfdisRelacionados?: string;
  source?: string;
  contribuyenteId?: string;
}

function computeMxn(value: number | undefined, tipoCambio: number): number {
  return (value || 0) * tipoCambio;
}

export async function createCfdi(pool: Pool, data: CreateCfdiData): Promise<Cfdi> {
  if (!data.uuid) throw new Error('UUID es requerido');
  if (!data.fechaEmision) throw new Error('Fecha de emisión es requerida');
  if (!data.rfcEmisor) throw new Error('RFC Emisor es requerido');
  if (!data.rfcReceptor) throw new Error('RFC Receptor es requerido');

  const dateStr = typeof data.fechaEmision === 'string' && data.fechaEmision.match(/^\d{4}-\d{2}-\d{2}$/)
    ? `${data.fechaEmision}T12:00:00`
    : data.fechaEmision;

  const fechaEmision = new Date(dateStr);
  if (isNaN(fechaEmision.getTime())) {
    throw new Error(`Fecha de emisión inválida: ${data.fechaEmision}`);
  }

  const year = String(fechaEmision.getFullYear());
  const month = String(fechaEmision.getMonth() + 1).padStart(2, '0');
  const tc = data.tipoCambio || 1;

  const { rows } = await pool.query(`
    INSERT INTO cfdis (
      year, month, type, uuid, serie, folio, status, fecha_emision,
      rfc_emisor, nombre_emisor, rfc_receptor, nombre_receptor,
      subtotal, subtotal_mxn, descuento, descuento_mxn,
      total, total_mxn, saldo_insoluto, moneda, tipo_cambio,
      tipo_comprobante, metodo_pago, forma_pago, uso_cfdi,
      pac, fecha_cert_sat, fecha_cancelacion, uuid_relacionado,
      isr_retencion, isr_retencion_mxn, iva_traslado, iva_traslado_mxn,
      iva_retencion, iva_retencion_mxn, ieps_traslado, ieps_traslado_mxn,
      ieps_retencion, ieps_retencion_mxn,
      impuestos_locales_trasladado, impuestos_locales_trasladado_mxn,
      impuestos_locales_retenidos, impuestos_locales_retenidos_mxn,
      monto_pago, monto_pago_mxn, fecha_pago_p, num_parcialidad,
      isr_retencion_pago, isr_retencion_pago_mxn,
      iva_traslado_pago, iva_traslado_pago_mxn,
      iva_retencion_pago, iva_retencion_pago_mxn,
      ieps_traslado_pago, ieps_traslado_pago_mxn,
      ieps_retencion_pago, ieps_retencion_pago_mxn,
      saldo_pendiente, saldo_pendiente_mxn,
      fecha_liquidacion, fecha_pago, fecha_inicial_pago, fecha_final_pago,
      num_dias_pagados, num_seguro_social, puesto,
      salario_base_cot_apor, salario_base_cot_apor_mxn,
      salario_diario_integrado, salario_diario_integrado_mxn,
      total_percepciones, total_percepciones_mxn,
      total_deducciones, total_deducciones_mxn,
      imp_retenidos_nomina, imp_retenidos_nomina_mxn,
      otras_deducciones_nomina, otras_deducciones_nomina_mxn,
      subsidio_causado, subsidio_causado_mxn,
      conciliado,
      regimen_fiscal_emisor, regimen_fiscal_receptor,
      xml_url, pdf_url, xml_original,
      cfdi_tipo_relacion, cfdis_relacionados,
      source,
      contribuyente_id
    ) VALUES (
      $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,
      $11,$12,$13,$14,$15,$16,$17,$18,$19,$20,
      $21,$22,$23,$24,$25,$26,$27,$28,$29,$30,
      $31,$32,$33,$34,$35,$36,$37,$38,$39,$40,
      $41,$42,$43,$44,$45,$46,$47,$48,$49,$50,
      $51,$52,$53,$54,$55,$56,$57,$58,$59,$60,
      $61,$62,$63,$64,$65,$66,$67,$68,$69,$70,
      $71,$72,$73,$74,$75,$76,$77,$78,$79,$80,
      $81,$82,$83,$84,$85,$86,
      $87,$88,
      $89
    )
    RETURNING ${CFDI_SELECT}
  `, [
    year, month,
    data.type || 'ingreso',
    data.uuid,
    data.serie || null,
    data.folio || null,
    data.status || 'vigente',
    fechaEmision,
    data.rfcEmisor,
    data.nombreEmisor || 'Sin nombre',
    data.rfcReceptor,
    data.nombreReceptor || 'Sin nombre',
    data.subtotal || 0,
    data.subtotalMxn ?? computeMxn(data.subtotal, tc),
    data.descuento || 0,
    data.descuentoMxn ?? computeMxn(data.descuento, tc),
    data.total || 0,
    data.totalMxn ?? computeMxn(data.total, tc),
    data.saldoInsoluto || null,
    data.moneda || 'MXN',
    tc,
    data.tipoComprobante || null,
    data.metodoPago || null,
    data.formaPago || null,
    data.usoCfdi || null,
    data.pac || null,
    data.fechaCertSat || null,
    data.fechaCancelacion || null,
    data.uuidRelacionado || null,
    data.isrRetencion || 0,
    data.isrRetencionMxn ?? computeMxn(data.isrRetencion, tc),
    data.ivaTraslado || 0,
    data.ivaTrasladoMxn ?? computeMxn(data.ivaTraslado, tc),
    data.ivaRetencion || 0,
    data.ivaRetencionMxn ?? computeMxn(data.ivaRetencion, tc),
    data.iepsTraslado || 0,
    data.iepsTrasladoMxn ?? computeMxn(data.iepsTraslado, tc),
    data.iepsRetencion || 0,
    data.iepsRetencionMxn ?? computeMxn(data.iepsRetencion, tc),
    data.impuestosLocalesTrasladado || 0,
    data.impuestosLocalesTrasladoMxn ?? computeMxn(data.impuestosLocalesTrasladado, tc),
    data.impuestosLocalesRetenidos || 0,
    data.impuestosLocalesRetenidosMxn ?? computeMxn(data.impuestosLocalesRetenidos, tc),
    data.montoPago || 0,
    data.montoPagoMxn ?? computeMxn(data.montoPago, tc),
    data.fechaPagoP || null,
    data.numParcialidad || null,
    data.isrRetencionPago || 0,
    data.isrRetencionPagoMxn ?? computeMxn(data.isrRetencionPago, tc),
    data.ivaTrasladoPago || 0,
    data.ivaTrasladoPagoMxn ?? computeMxn(data.ivaTrasladoPago, tc),
    data.ivaRetencionPago || 0,
    data.ivaRetencionPagoMxn ?? computeMxn(data.ivaRetencionPago, tc),
    data.iepsTrasladoPago || 0,
    data.iepsTrasladoPagoMxn ?? computeMxn(data.iepsTrasladoPago, tc),
    data.iepsRetencionPago || 0,
    data.iepsRetencionPagoMxn ?? computeMxn(data.iepsRetencionPago, tc),
    data.saldoPendiente || 0,
    data.saldoPendienteMxn ?? computeMxn(data.saldoPendiente, tc),
    data.fechaLiquidacion || null,
    data.fechaPago || null,
    data.fechaInicialPago || null,
    data.fechaFinalPago || null,
    data.numDiasPagados || 0,
    data.numSeguroSocial || null,
    data.puesto || null,
    data.salarioBaseCotApor || 0,
    data.salarioBaseCotAporMxn ?? computeMxn(data.salarioBaseCotApor, tc),
    data.salarioDiarioIntegrado || 0,
    data.salarioDiarioIntegradoMxn ?? computeMxn(data.salarioDiarioIntegrado, tc),
    data.totalPercepciones || 0,
    data.totalPercepcionesMxn ?? computeMxn(data.totalPercepciones, tc),
    data.totalDeducciones || 0,
    data.totalDeduccionesMxn ?? computeMxn(data.totalDeducciones, tc),
    data.impRetenidosNomina || 0,
    data.impRetenidosNominaMxn ?? computeMxn(data.impRetenidosNomina, tc),
    data.otrasDeduccionesNomina || 0,
    data.otrasDeduccionesNominaMxn ?? computeMxn(data.otrasDeduccionesNomina, tc),
    data.subsidioCausado || 0,
    data.subsidioCausadoMxn ?? computeMxn(data.subsidioCausado, tc),
    data.conciliado || null,
    data.regimenFiscalEmisor || null,
    data.regimenFiscalReceptor || null,
    data.xmlUrl || null,
    data.pdfUrl || null,
    data.xmlOriginal || null,
    data.cfdiTipoRelacion || null,
    data.cfdisRelacionados || null,
    data.source || 'manual',
    data.contribuyenteId ?? null,
  ]);

  // Retroactive invalidation hook: mark cached metrics stale for prior-year CFDIs
  try {
    const cfdiDate = new Date(data.fechaEmision || new Date());
    const cfdiYear = cfdiDate.getFullYear();
    const currentYear = new Date().getFullYear();
    if (cfdiYear < currentYear && data.contribuyenteId) {
      await markForInvalidation(pool, data.contribuyenteId, cfdiYear, cfdiDate.getMonth() + 1, 'CFDI_INSERT');
    }
  } catch (err) {
    console.error('[Metricas] Invalidation hook failed (non-blocking):', err);
  }

  // Recompute saldo_pendiente_mxn de los CFDIs afectados por este insert.
  // Un I PPD recalcula su propio saldo (considera anticipo si es I/07); un
  // P o E no-07 recalcula los I PPD que referencia.
  try {
    const afectados = uuidsAfectadosPorCfdi({
      uuid: data.uuid!,
      tipoComprobante: data.tipoComprobante ?? null,
      metodoPago: data.metodoPago ?? null,
      cfdiTipoRelacion: data.cfdiTipoRelacion ?? null,
      uuidRelacionado: data.uuidRelacionado ?? null,
      cfdisRelacionados: data.cfdisRelacionados ?? null,
    });
    if (afectados.length > 0) {
      await recomputarSaldoPendiente(pool, afectados);
    }
  } catch (err) {
    console.error('[Saldo] Recompute hook failed (non-blocking):', err);
  }

  return rows[0];
}

export interface BatchInsertResult {
  inserted: number;
  duplicates: number;
  errors: number;
  errorMessages: string[];
}

export async function createManyCfdis(pool: Pool, cfdis: CreateCfdiData[]): Promise<number> {
  const result = await createManyCfdisBatch(pool, cfdis);
  return result.inserted;
}

export async function createManyCfdisBatch(pool: Pool, cfdis: CreateCfdiData[]): Promise<BatchInsertResult> {
  const result: BatchInsertResult = {
    inserted: 0,
    duplicates: 0,
    errors: 0,
    errorMessages: []
  };

  if (cfdis.length === 0) return result;

  for (const cfdi of cfdis) {
    try {
      await createCfdi(pool, cfdi);
      result.inserted++;
    } catch (error: any) {
      const errorMsg = error.message || 'Error desconocido';
      if (errorMsg.includes('duplicate') || errorMsg.includes('unique')) {
        result.duplicates++;
      } else {
        result.errors++;
        if (result.errorMessages.length < 10) {
          result.errorMessages.push(`${cfdi.uuid?.substring(0, 8) || 'N/A'}: ${errorMsg}`);
        }
      }
    }
  }

  return result;
}

export async function deleteCfdi(pool: Pool, id: string): Promise<void> {
  // Fetch before deleting so we can fire the invalidation hook
  const { rows: pre } = await pool.query(
    `SELECT fecha_emision, contribuyente_id FROM cfdis WHERE id = $1`,
    [id]
  );
  await pool.query(`DELETE FROM cfdis WHERE id = $1`, [id]);

  // Retroactive invalidation hook: mark cached metrics stale for prior-year CFDIs
  try {
    if (pre[0]) {
      const cfdiDate = new Date(pre[0].fecha_emision || new Date());
      const cfdiYear = cfdiDate.getFullYear();
      const currentYear = new Date().getFullYear();
      if (cfdiYear < currentYear && pre[0].contribuyente_id) {
        await markForInvalidation(pool, pre[0].contribuyente_id, cfdiYear, cfdiDate.getMonth() + 1, 'CFDI_INSERT');
      }
    }
  } catch (err) {
    console.error('[Metricas] Invalidation hook failed (non-blocking):', err);
  }
}

export async function getEmisores(pool: Pool, search: string, limit: number = 10, contribuyenteId?: string): Promise<{ rfc: string; nombre: string }[]> {
  let whereClause = 'WHERE rfc_emisor ILIKE $1 OR nombre_emisor ILIKE $1';
  const params: any[] = [`%${search}%`, limit];
  if (contribuyenteId) {
    const safeId = contribuyenteId.replace(/[^a-f0-9-]/gi, '');
    whereClause += ` AND (contribuyente_id = '${safeId}' OR rfc_emisor = (SELECT rfc FROM contribuyentes WHERE entidad_id = '${safeId}') OR rfc_receptor = (SELECT rfc FROM contribuyentes WHERE entidad_id = '${safeId}'))`;
  }
  const { rows } = await pool.query(`
    SELECT DISTINCT rfc_emisor as rfc, nombre_emisor as nombre
    FROM cfdis
    ${whereClause}
    ORDER BY nombre_emisor
    LIMIT $2
  `, params);
  return rows;
}

export async function getReceptores(pool: Pool, search: string, limit: number = 10, contribuyenteId?: string): Promise<{ rfc: string; nombre: string }[]> {
  let whereClause = 'WHERE rfc_receptor ILIKE $1 OR nombre_receptor ILIKE $1';
  const params: any[] = [`%${search}%`, limit];
  if (contribuyenteId) {
    const safeId = contribuyenteId.replace(/[^a-f0-9-]/gi, '');
    whereClause += ` AND (contribuyente_id = '${safeId}' OR rfc_emisor = (SELECT rfc FROM contribuyentes WHERE entidad_id = '${safeId}') OR rfc_receptor = (SELECT rfc FROM contribuyentes WHERE entidad_id = '${safeId}'))`;
  }
  const { rows } = await pool.query(`
    SELECT DISTINCT rfc_receptor as rfc, nombre_receptor as nombre
    FROM cfdis
    ${whereClause}
    ORDER BY nombre_receptor
    LIMIT $2
  `, params);
  return rows;
}

export async function getResumenCfdis(pool: Pool, año: number, mes: number, contribuyenteId?: string) {
  let whereClause = `WHERE status NOT IN ('Cancelado', '0') AND year = $1 AND month = $2`;
  if (contribuyenteId) {
    const safeId = contribuyenteId.replace(/[^a-f0-9-]/gi, '');
    whereClause += ` AND (contribuyente_id = '${safeId}' OR rfc_emisor = (SELECT rfc FROM contribuyentes WHERE entidad_id = '${safeId}') OR rfc_receptor = (SELECT rfc FROM contribuyentes WHERE entidad_id = '${safeId}'))`;
  }
  const { rows } = await pool.query(`
    SELECT
      COALESCE(SUM(CASE WHEN type = 'EMITIDO' AND tipo_comprobante = 'I' THEN total_mxn ELSE 0 END), 0) as total_ingresos,
      COALESCE(SUM(CASE WHEN type = 'RECIBIDO' AND tipo_comprobante = 'I' THEN total_mxn ELSE 0 END), 0) as total_egresos,
      COUNT(CASE WHEN type = 'EMITIDO' AND tipo_comprobante = 'I' THEN 1 END) as count_ingresos,
      COUNT(CASE WHEN type = 'RECIBIDO' AND tipo_comprobante = 'I' THEN 1 END) as count_egresos,
      COALESCE(SUM(CASE WHEN type = 'EMITIDO' THEN iva_traslado_mxn ELSE 0 END), 0) as iva_trasladado,
      COALESCE(SUM(CASE WHEN type = 'RECIBIDO' THEN iva_traslado_mxn ELSE 0 END), 0) as iva_acreditable
    FROM cfdis
    ${whereClause}
  `, [String(año), String(mes).padStart(2, '0')]);

  const r = rows[0];
  return {
    totalIngresos: Number(r?.total_ingresos || 0),
    totalEgresos: Number(r?.total_egresos || 0),
    countIngresos: Number(r?.count_ingresos || 0),
    countEgresos: Number(r?.count_egresos || 0),
    ivaTrasladado: Number(r?.iva_trasladado || 0),
    ivaAcreditable: Number(r?.iva_acreditable || 0),
  };
}
