import type { Pool } from 'pg';

/**
 * SQL que computa el saldo pendiente de un CFDI I PPD.
 *
 * Fórmula (misma que §13 del doc Horux_despacho):
 *   saldo = total_mxn
 *         − Σ P.monto_pago_mxn  (complementos P que referencian el UUID)
 *         − Σ E.total_mxn       (NC donde cfdi_tipo_relacion <> '07')
 *         − anticipo_aplicado   (si el CFDI es I/07, suma de totales de
 *                                los UUIDs en cfdis_relacionados)
 *
 * NO se clamp a 0: un saldo negativo indica over-pago o over-aplicación
 * (señal útil). Los filtros downstream pueden excluir saldos < 0.01 si
 * solo quieren ver pendientes.
 *
 * `alias` = alias de la tabla `cfdis` en la query (ej. 'c', 'cfdis').
 */
export function saldoComputadoExpr(alias: string): string {
  const c = alias;
  return `(
    COALESCE(${c}.total_mxn, 0)
    - COALESCE((
        SELECT SUM(COALESCE(p.monto_pago_mxn, 0))
        FROM cfdis p
        WHERE p.tipo_comprobante = 'P'
          AND LOWER(COALESCE(p.uuid_relacionado, '')) LIKE '%' || LOWER(${c}.uuid) || '%'
          AND p.status NOT IN ('Cancelado', '0')
      ), 0)
    - COALESCE((
        SELECT SUM(COALESCE(e.total_mxn, 0))
        FROM cfdis e
        WHERE e.tipo_comprobante = 'E'
          AND COALESCE(e.cfdi_tipo_relacion, '') <> '07'
          AND e.cfdis_relacionados IS NOT NULL
          AND LOWER(${c}.uuid) = ANY(string_to_array(LOWER(e.cfdis_relacionados), '|'))
          AND e.status NOT IN ('Cancelado', '0')
      ), 0)
    - CASE WHEN ${c}.cfdi_tipo_relacion = '07' AND ${c}.cfdis_relacionados IS NOT NULL THEN
        COALESCE((
          SELECT SUM(COALESCE(a.total_mxn, 0))
          FROM cfdis a
          WHERE LOWER(a.uuid) = ANY(string_to_array(LOWER(${c}.cfdis_relacionados), '|'))
            AND a.status NOT IN ('Cancelado', '0')
        ), 0)
      ELSE 0 END
  )`;
}

/**
 * Recomputa `saldo_pendiente_mxn` para un conjunto de UUIDs de CFDIs.
 * Solo aplica a I PPD vigentes (otros tipos no tienen saldo pendiente
 * en el sentido de cuentas por cobrar/pagar). Idempotente.
 *
 * UUIDs se normalizan a lowercase para matchear independiente del case
 * en que vienen (XML suele traer uppercase, parser local normaliza).
 */
export async function recomputarSaldoPendiente(
  pool: Pool,
  uuids: string[],
): Promise<number> {
  if (uuids.length === 0) return 0;
  const expr = saldoComputadoExpr('c');
  const lowered = uuids.map(u => u.toLowerCase());
  const { rowCount } = await pool.query(
    `UPDATE cfdis c
        SET saldo_pendiente_mxn = ${expr}
      WHERE LOWER(c.uuid) = ANY($1::text[])
        AND c.tipo_comprobante = 'I'
        AND c.metodo_pago = 'PPD'
        AND c.status NOT IN ('Cancelado', '0')`,
    [lowered],
  );
  return rowCount ?? 0;
}

/**
 * Recomputa saldo pendiente para TODAS las I PPD vigentes del tenant.
 * Usado por el backfill one-shot y por validación/debug.
 */
export async function recomputarSaldoTodos(pool: Pool): Promise<number> {
  const expr = saldoComputadoExpr('c');
  const { rowCount } = await pool.query(
    `UPDATE cfdis c
        SET saldo_pendiente_mxn = ${expr}
      WHERE c.tipo_comprobante = 'I'
        AND c.metodo_pago = 'PPD'
        AND c.status NOT IN ('Cancelado', '0')`,
  );
  return rowCount ?? 0;
}

/**
 * Dado un CFDI recién insertado/actualizado, devuelve el set de UUIDs
 * cuya saldo_pendiente_mxn debe recomputarse. Centraliza la lógica de
 * "qué filas tocar" para cada tipo:
 *
 * - I PPD: su propio UUID (saldo inicial, considera anticipo si es I/07)
 * - P: los UUIDs referenciados en uuid_relacionado (pueden ser pipe-separated)
 * - E no-07: los UUIDs en cfdis_relacionados
 * - Otros tipos: no afectan saldos
 */
export function uuidsAfectadosPorCfdi(cfdi: {
  uuid: string;
  tipoComprobante: string | null;
  metodoPago?: string | null;
  cfdiTipoRelacion?: string | null;
  uuidRelacionado?: string | null;
  cfdisRelacionados?: string | null;
}): string[] {
  const result: string[] = [];

  if (cfdi.tipoComprobante === 'I' && cfdi.metodoPago === 'PPD' && cfdi.uuid) {
    result.push(cfdi.uuid.toLowerCase());
  }

  if (cfdi.tipoComprobante === 'P' && cfdi.uuidRelacionado) {
    for (const ref of cfdi.uuidRelacionado.split('|')) {
      const clean = ref.trim().toLowerCase();
      if (clean) result.push(clean);
    }
  }

  if (
    cfdi.tipoComprobante === 'E' &&
    cfdi.cfdiTipoRelacion !== '07' &&
    cfdi.cfdisRelacionados
  ) {
    for (const ref of cfdi.cfdisRelacionados.split('|')) {
      const clean = ref.trim().toLowerCase();
      if (clean) result.push(clean);
    }
  }

  return result;
}
