import type { Pool } from 'pg';
import type { EstadoResultados, FlujoEfectivo, ComparativoPeriodos, ConcentradoRfc } from '@horux/shared';
import { calcularIngresosPorRegimen, calcularEgresosPorRegimen } from './dashboard.service.js';

/**
 * Resuelve condiciones `esEmisor` / `esReceptor` para un contribuyente
 * usando su RFC. Reemplaza el par `type = 'X' AND contribuyente_id = Y`.
 * Si no hay contribuyente, retorna fallback a `type`.
 */
async function resolveEmisorReceptor(
  pool: Pool,
  contribuyenteId?: string | null,
): Promise<{ esEmisor: string; esReceptor: string }> {
  if (!contribuyenteId) {
    return { esEmisor: `type = 'EMITIDO'`, esReceptor: `type = 'RECIBIDO'` };
  }
  const safeId = contribuyenteId.replace(/[^a-f0-9-]/gi, '');
  if (!safeId) return { esEmisor: `type = 'EMITIDO'`, esReceptor: `type = 'RECIBIDO'` };
  const { rows } = await pool.query<{ rfc: string | null }>(
    `SELECT rfc FROM contribuyentes WHERE entidad_id = $1`,
    [safeId],
  );
  const rfc = (rows[0]?.rfc || '').replace(/[^A-Z0-9]/gi, '').toUpperCase();
  if (!rfc) {
    return {
      esEmisor: `type = 'EMITIDO' AND contribuyente_id = '${safeId}'`,
      esReceptor: `type = 'RECIBIDO' AND contribuyente_id = '${safeId}'`,
    };
  }
  return {
    esEmisor: `UPPER(rfc_emisor) = '${rfc}'`,
    esReceptor: `UPPER(rfc_receptor) = '${rfc}'`,
  };
}

function sanitizeContribUuid(id?: string | null): string {
  return id ? id.replace(/[^a-f0-9-]/gi, '') : '';
}

function toNumber(value: unknown): number {
  if (value === null || value === undefined) return 0;
  if (typeof value === 'number') return value;
  if (typeof value === 'bigint') return Number(value);
  if (typeof value === 'string') return parseFloat(value) || 0;
  if (typeof value === 'object' && value !== null && 'toNumber' in value) {
    return (value as { toNumber: () => number }).toNumber();
  }
  return Number(value) || 0;
}

export async function getEstadoResultados(
  pool: Pool,
  fechaInicio: string,
  fechaFin: string,
  tenantId: string,
  contribuyenteId?: string | null,
): Promise<EstadoResultados> {
  // Totales usando la misma lógica del dashboard
  const ingresosData = await calcularIngresosPorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, undefined, contribuyenteId);
  const egresosData = await calcularEgresosPorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, undefined, contribuyenteId);

  const totalIngresos = ingresosData.total;
  const totalEgresos = egresosData.total;
  const utilidadBruta = totalIngresos - totalEgresos;

  // Desglose por régimen como conceptos
  const ingresosConceptos = ingresosData.porRegimen.map(r => ({
    concepto: `${r.regimenClave} - ${r.regimenDescripcion}`,
    monto: r.monto,
  }));

  const egresosConceptos = egresosData.porRegimen.map(r => ({
    concepto: `${r.regimenClave} - ${r.regimenDescripcion}`,
    monto: r.monto,
  }));

  return {
    periodo: { inicio: fechaInicio, fin: fechaFin },
    ingresos: ingresosConceptos,
    egresos: egresosConceptos,
    totalIngresos,
    totalEgresos,
    utilidadBruta,
    impuestos: 0,
    utilidadNeta: utilidadBruta,
  };
}

export async function getFlujoEfectivo(
  pool: Pool,
  fechaInicio: string,
  fechaFin: string,
  contribuyenteId?: string | null,
): Promise<FlujoEfectivo> {
  const VIGENTE = `status NOT IN ('Cancelado', '0')`;
  const RANGO = `fecha_emision >= $1::date AND fecha_emision < ($2::date + interval '1 day')`;
  const RANGO_PAGO = `fecha_pago_p >= $1::date AND fecha_pago_p < ($2::date + interval '1 day')`;
  const { esEmisor, esReceptor } = await resolveEmisorReceptor(pool, contribuyenteId);

  const { rows: entradasPUE } = await pool.query(`
    SELECT TO_CHAR(fecha_emision, 'YYYY-MM') as mes, COALESCE(SUM(total_mxn), 0) as total
    FROM cfdis
    WHERE ${esEmisor} AND tipo_comprobante = 'I' AND metodo_pago = 'PUE'
      AND ${VIGENTE} AND ${RANGO}
    GROUP BY mes ORDER BY mes
  `, [fechaInicio, fechaFin]);

  const { rows: entradasPago } = await pool.query(`
    SELECT TO_CHAR(fecha_pago_p, 'YYYY-MM') as mes, COALESCE(SUM(monto_pago_mxn), 0) as total
    FROM cfdis
    WHERE ${esEmisor} AND tipo_comprobante = 'P'
      AND ${VIGENTE} AND ${RANGO_PAGO}
    GROUP BY mes ORDER BY mes
  `, [fechaInicio, fechaFin]);

  const { rows: entradasNC } = await pool.query(`
    SELECT TO_CHAR(fecha_emision, 'YYYY-MM') as mes, COALESCE(SUM(total_mxn), 0) as total
    FROM cfdis
    WHERE ${esEmisor} AND tipo_comprobante = 'E' AND metodo_pago = 'PUE'
      AND COALESCE(cfdi_tipo_relacion, '') <> '07'
      AND ${VIGENTE} AND ${RANGO}
    GROUP BY mes ORDER BY mes
  `, [fechaInicio, fechaFin]);

  const { rows: salidasPUE } = await pool.query(`
    SELECT TO_CHAR(fecha_emision, 'YYYY-MM') as mes, COALESCE(SUM(total_mxn), 0) as total
    FROM cfdis
    WHERE ${esReceptor} AND tipo_comprobante = 'I' AND metodo_pago = 'PUE'
      AND ${VIGENTE} AND ${RANGO}
    GROUP BY mes ORDER BY mes
  `, [fechaInicio, fechaFin]);

  const { rows: salidasPago } = await pool.query(`
    SELECT TO_CHAR(fecha_pago_p, 'YYYY-MM') as mes, COALESCE(SUM(monto_pago_mxn), 0) as total
    FROM cfdis
    WHERE ${esReceptor} AND tipo_comprobante = 'P'
      AND ${VIGENTE} AND ${RANGO_PAGO}
    GROUP BY mes ORDER BY mes
  `, [fechaInicio, fechaFin]);

  const { rows: salidasNC } = await pool.query(`
    SELECT TO_CHAR(fecha_emision, 'YYYY-MM') as mes, COALESCE(SUM(total_mxn), 0) as total
    FROM cfdis
    WHERE ${esReceptor} AND tipo_comprobante = 'E' AND metodo_pago = 'PUE'
      AND COALESCE(cfdi_tipo_relacion, '') <> '07'
      AND ${VIGENTE} AND ${RANGO}
    GROUP BY mes ORDER BY mes
  `, [fechaInicio, fechaFin]);

  // Combinar por mes
  const mesesSet = new Set<string>();
  [...entradasPUE, ...entradasPago, ...entradasNC, ...salidasPUE, ...salidasPago, ...salidasNC].forEach((r: any) => mesesSet.add(r.mes));
  const mesesOrdenados = Array.from(mesesSet).sort();

  const get = (rows: any[], mes: string) => toNumber(rows.find((r: any) => r.mes === mes)?.total);

  const entradas = mesesOrdenados.map(mes => ({
    concepto: mes,
    monto: get(entradasPUE, mes) + get(entradasPago, mes) - get(entradasNC, mes),
  }));

  const salidas = mesesOrdenados.map(mes => ({
    concepto: mes,
    monto: get(salidasPUE, mes) + get(salidasPago, mes) - get(salidasNC, mes),
  }));

  const totalEntradas = entradas.reduce((s, e) => s + e.monto, 0);
  const totalSalidas = salidas.reduce((s, e) => s + e.monto, 0);

  return {
    periodo: { inicio: fechaInicio, fin: fechaFin },
    saldoInicial: 0,
    entradas,
    salidas,
    totalEntradas,
    totalSalidas,
    flujoNeto: totalEntradas - totalSalidas,
    saldoFinal: totalEntradas - totalSalidas,
  };
}

/**
 * Calcula entradas/salidas de un año completo mes a mes con la lógica de flujo de efectivo.
 */
async function calcularFlujoPorMes(pool: Pool, año: number, contribuyenteId?: string | null): Promise<{ entradas: number[]; salidas: number[] }> {
  const VIGENTE = `status NOT IN ('Cancelado', '0')`;
  const fi = `${año}-01-01`;
  const ff = `${año}-12-31`;
  const RANGO = `fecha_emision >= $1::date AND fecha_emision < ($2::date + interval '1 day')`;
  const RANGO_PAGO = `fecha_pago_p >= $1::date AND fecha_pago_p < ($2::date + interval '1 day')`;
  const { esEmisor, esReceptor } = await resolveEmisorReceptor(pool, contribuyenteId);

  const q = async (lado: 'EMITIDO' | 'RECIBIDO', tc: string, campo: string, mp?: string) => {
    const mpF = mp ? `AND metodo_pago = '${mp}'` : '';
    const fechaCol = tc === 'P' ? 'fecha_pago_p' : 'fecha_emision';
    const rango = tc === 'P' ? RANGO_PAGO : RANGO;
    const noAnticipo = tc === 'E' ? `AND COALESCE(cfdi_tipo_relacion, '') <> '07'` : '';
    const ladoCond = lado === 'EMITIDO' ? esEmisor : esReceptor;
    const { rows } = await pool.query(`
      SELECT EXTRACT(MONTH FROM ${fechaCol})::int as mes, COALESCE(SUM(${campo}), 0) as total
      FROM cfdis
      WHERE ${ladoCond} AND tipo_comprobante = '${tc}' ${mpF} ${noAnticipo} AND ${VIGENTE} AND ${rango}
      GROUP BY mes
    `, [fi, ff]);
    const map = new Map<number, number>();
    for (const r of rows) map.set(r.mes, Number(r.total));
    return map;
  };

  const [ePUE, ePago, eNC, sPUE, sPago, sNC] = await Promise.all([
    q('EMITIDO', 'I', 'total_mxn', 'PUE'),
    q('EMITIDO', 'P', 'monto_pago_mxn'),
    q('EMITIDO', 'E', 'total_mxn', 'PUE'),
    q('RECIBIDO', 'I', 'total_mxn', 'PUE'),
    q('RECIBIDO', 'P', 'monto_pago_mxn'),
    q('RECIBIDO', 'E', 'total_mxn', 'PUE'),
  ]);

  const g = (map: Map<number, number>, m: number) => map.get(m) || 0;

  const entradas: number[] = [];
  const salidas: number[] = [];

  for (let m = 1; m <= 12; m++) {
    entradas.push(g(ePUE, m) + g(ePago, m) - g(eNC, m));
    salidas.push(g(sPUE, m) + g(sPago, m) - g(sNC, m));
  }

  return { entradas, salidas };
}

export async function getComparativo(
  pool: Pool,
  año: number,
  contribuyenteId?: string | null,
): Promise<ComparativoPeriodos> {
  const [actual, anterior] = await Promise.all([
    calcularFlujoPorMes(pool, año, contribuyenteId),
    calcularFlujoPorMes(pool, año - 1, contribuyenteId),
  ]);

  const meses = ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'];
  const utilidad = actual.entradas.map((e, i) => e - actual.salidas[i]);

  const totalActualIng = actual.entradas.reduce((a, b) => a + b, 0);
  const totalAnteriorIng = anterior.entradas.reduce((a, b) => a + b, 0);
  const totalActualEgr = actual.salidas.reduce((a, b) => a + b, 0);
  const totalAnteriorEgr = anterior.salidas.reduce((a, b) => a + b, 0);

  return {
    periodos: meses,
    ingresos: actual.entradas,
    egresos: actual.salidas,
    utilidad,
    variacionIngresos: totalAnteriorIng > 0 ? ((totalActualIng - totalAnteriorIng) / totalAnteriorIng) * 100 : 0,
    variacionEgresos: totalAnteriorEgr > 0 ? ((totalActualEgr - totalAnteriorEgr) / totalAnteriorEgr) * 100 : 0,
    variacionUtilidad: 0,
  };
}

export async function getConcentradoRfc(
  pool: Pool,
  fechaInicio: string,
  fechaFin: string,
  tipo: 'cliente' | 'proveedor',
  contribuyenteId?: string | null,
): Promise<ConcentradoRfc[]> {
  const { esEmisor, esReceptor } = await resolveEmisorReceptor(pool, contribuyenteId);

  if (tipo === 'cliente') {
    const { rows: data } = await pool.query(`
      SELECT rfc_receptor as rfc, nombre_receptor as nombre,
        'cliente' as tipo,
        SUM(total_mxn) as "totalFacturado",
        SUM(iva_traslado_mxn) as "totalIva",
        COUNT(*)::int as "cantidadCfdis"
      FROM cfdis
      WHERE ${esEmisor} AND tipo_comprobante = 'I' AND status NOT IN ('Cancelado', '0')
      AND fecha_emision BETWEEN $1::date AND $2::date
      GROUP BY rfc_receptor, nombre_receptor
      ORDER BY "totalFacturado" DESC
    `, [fechaInicio, fechaFin]);
    return data.map((d: any) => ({
      rfc: d.rfc,
      nombre: d.nombre,
      tipo: 'cliente' as const,
      totalFacturado: toNumber(d.totalFacturado),
      totalIva: toNumber(d.totalIva),
      cantidadCfdis: d.cantidadCfdis
    }));
  } else {
    const { rows: data } = await pool.query(`
      SELECT rfc_emisor as rfc, nombre_emisor as nombre,
        'proveedor' as tipo,
        SUM(total_mxn) as "totalFacturado",
        SUM(iva_traslado_mxn) as "totalIva",
        COUNT(*)::int as "cantidadCfdis"
      FROM cfdis
      WHERE ${esReceptor} AND tipo_comprobante = 'I' AND status NOT IN ('Cancelado', '0')
      AND fecha_emision BETWEEN $1::date AND $2::date
      GROUP BY rfc_emisor, nombre_emisor
      ORDER BY "totalFacturado" DESC
    `, [fechaInicio, fechaFin]);
    return data.map((d: any) => ({
      rfc: d.rfc,
      nombre: d.nombre,
      tipo: 'proveedor' as const,
      totalFacturado: toNumber(d.totalFacturado),
      totalIva: toNumber(d.totalIva),
      cantidadCfdis: d.cantidadCfdis
    }));
  }
}

export interface CuentasPendientes {
  cantidadCfdis: number;
  saldoTotal: number;
  detalle: { rfc: string; nombre: string; cantidad: number; saldo: number }[];
}

export async function getCuentasXPagar(
  pool: Pool,
  fechaInicio: string,
  fechaFin: string,
  regimen?: string,
  contribuyenteId?: string | null,
): Promise<CuentasPendientes> {
  const regimenFilter = regimen ? `AND regimen_fiscal_receptor = '${regimen}'` : '';
  const { esReceptor } = await resolveEmisorReceptor(pool, contribuyenteId);

  const { rows } = await pool.query(`
    SELECT
      rfc_emisor as rfc,
      nombre_emisor as nombre,
      COUNT(*)::int as cantidad,
      COALESCE(SUM(COALESCE(saldo_pendiente_mxn, total_mxn)), 0) as saldo
    FROM cfdis
    WHERE ${esReceptor} AND tipo_comprobante = 'I' AND metodo_pago = 'PPD'
      AND status NOT IN ('Cancelado', '0')
      AND fecha_emision >= $1::date
      AND fecha_emision < ($2::date + interval '1 day')
      AND COALESCE(saldo_pendiente_mxn, total_mxn) > 0.01
      ${regimenFilter}
    GROUP BY rfc_emisor, nombre_emisor
    ORDER BY saldo DESC
  `, [fechaInicio, fechaFin]);

  const detalle = rows.map((r: any) => ({
    rfc: r.rfc,
    nombre: r.nombre,
    cantidad: r.cantidad,
    saldo: toNumber(r.saldo),
  }));

  return {
    cantidadCfdis: detalle.reduce((s, d) => s + d.cantidad, 0),
    saldoTotal: detalle.reduce((s, d) => s + d.saldo, 0),
    detalle,
  };
}

export async function getCuentasXCobrar(
  pool: Pool,
  fechaInicio: string,
  fechaFin: string,
  regimen?: string,
  contribuyenteId?: string | null,
): Promise<CuentasPendientes> {
  const regimenFilter = regimen ? `AND regimen_fiscal_emisor = '${regimen}'` : '';
  const { esEmisor } = await resolveEmisorReceptor(pool, contribuyenteId);

  const { rows } = await pool.query(`
    SELECT
      rfc_receptor as rfc,
      nombre_receptor as nombre,
      COUNT(*)::int as cantidad,
      COALESCE(SUM(COALESCE(saldo_pendiente_mxn, total_mxn)), 0) as saldo
    FROM cfdis
    WHERE ${esEmisor} AND tipo_comprobante = 'I' AND metodo_pago = 'PPD'
      AND status NOT IN ('Cancelado', '0')
      AND fecha_emision >= $1::date
      AND fecha_emision < ($2::date + interval '1 day')
      AND COALESCE(saldo_pendiente_mxn, total_mxn) > 0.01
      ${regimenFilter}
    GROUP BY rfc_receptor, nombre_receptor
    ORDER BY saldo DESC
  `, [fechaInicio, fechaFin]);

  const detalle = rows.map((r: any) => ({
    rfc: r.rfc,
    nombre: r.nombre,
    cantidad: r.cantidad,
    saldo: toNumber(r.saldo),
  }));

  return {
    cantidadCfdis: detalle.reduce((s, d) => s + d.cantidad, 0),
    saldoTotal: detalle.reduce((s, d) => s + d.saldo, 0),
    detalle,
  };
}
