import { redondear } from '../common/inm-utils';

/**
 * Cálculos de obra (M120). Funciones puras para testearlas solas.
 * Todos los montos van en la moneda del proyecto y sin IVA: el costo de obra
 * es la base imponible; el IVA de compras es crédito fiscal, no costo.
 */

/** Fondo de reparo, retención y neto a pagar de un certificado. */
export function calcularCertificado(
  c: { monto_bruto: number; pct_fondo_reparo: number; pct_retencion: number },
  dec: number,
) {
  const fondo_reparo = redondear((c.monto_bruto * c.pct_fondo_reparo) / 100, dec);
  const retencion = redondear((c.monto_bruto * c.pct_retencion) / 100, dec);
  return { fondo_reparo, retencion, monto_neto: redondear(c.monto_bruto - fondo_reparo - retencion, dec) };
}

/**
 * Lleva la base de un comprobante a la moneda del proyecto. `factor` (sin redondear)
 * es cuánto vale en la moneda del proyecto una unidad de la del comprobante.
 * `tasaDe(codigo)` devuelve los guaraníes por unidad de esa moneda a la fecha del
 * comprobante; sólo se pide cuando hace falta.
 */
export async function convertirAMonedaProyecto(
  a: {
    base: number;
    monedaDoc: string;
    cotizacionDoc: number | null;
    monedaProyecto: string;
    tasaDe: (codigo: string) => Promise<number>;
  },
  dec: number,
): Promise<{ monto: number; tipo_cambio: number | null; factor: number }> {
  if (a.monedaDoc === a.monedaProyecto) return { monto: redondear(a.base, dec), tipo_cambio: null, factor: 1 };
  // El comprobante en moneda extranjera trae su propia cotización; si no la tiene
  // (1 o vacía), se usa la vigente a su fecha.
  const gsPorDoc =
    a.monedaDoc === 'PYG' ? 1 : a.cotizacionDoc && a.cotizacionDoc > 1 ? a.cotizacionDoc : await a.tasaDe(a.monedaDoc);
  const gs = a.base * gsPorDoc;
  if (a.monedaProyecto === 'PYG') return { monto: redondear(gs, dec), tipo_cambio: gsPorDoc, factor: gsPorDoc };
  const gsPorProyecto = await a.tasaDe(a.monedaProyecto);
  return { monto: redondear(gs / gsPorProyecto, dec), tipo_cambio: gsPorProyecto, factor: gsPorDoc / gsPorProyecto };
}

/**
 * Reparte un costo entre unidades según su superficie, por resto mayor: la suma
 * de las partes da exacto el total. Unidades sin superficie quedan en 0.
 */
export function prorratearPorSuperficie<T extends { m2: number }>(total: number, unidades: T[], dec: number) {
  const superficie = unidades.reduce((a, u) => a + (u.m2 > 0 ? u.m2 : 0), 0);
  if (!(superficie > 0)) return unidades.map((u) => ({ ...u, participacion: 0, costo: 0 }));
  const factor = 10 ** dec;
  const exactas = unidades.map((u) => ((u.m2 > 0 ? u.m2 : 0) / superficie) * total * factor);
  const partes = exactas.map((x) => Math.floor(x + 1e-9));
  let resto = Math.round(total * factor) - partes.reduce((a, b) => a + b, 0);
  const orden = exactas.map((x, i) => ({ i, frac: x - Math.floor(x + 1e-9) })).sort((a, b) => b.frac - a.frac);
  for (const { i } of orden) {
    if (resto <= 0) break;
    if (unidades[i].m2 > 0) {
      partes[i] += 1;
      resto -= 1;
    }
  }
  return unidades.map((u, i) => ({
    ...u,
    participacion: redondear(((u.m2 > 0 ? u.m2 : 0) / superficie) * 100, 4),
    costo: partes[i] / factor,
  }));
}

/** Situación de una partida: ejecutado contra presupuesto y contra avance físico. */
export function situacionPartida(p: { presupuestado: number; ejecutado: number; pct_avance: number }, dec: number) {
  const saldo = redondear(p.presupuestado - p.ejecutado, dec);
  const pct_ejecutado = p.presupuestado > 0 ? redondear((p.ejecutado / p.presupuestado) * 100, 2) : null;
  // Lo que "debería" haberse gastado según el avance físico. Desvío positivo =
  // se gastó más de lo que el avance justifica.
  const valor_ganado = redondear((p.presupuestado * p.pct_avance) / 100, dec);
  return { saldo, pct_ejecutado, valor_ganado, desvio: redondear(p.ejecutado - valor_ganado, dec) };
}

/** Superficie que se usa para prorratear: propia + común; si no hay, la del terreno (lotes). */
export const superficieDe = (u: { m2_propios: unknown; m2_comunes: unknown; superficie_terreno: unknown }) => {
  const edificada = Number(u.m2_propios ?? 0) + Number(u.m2_comunes ?? 0);
  return edificada > 0 ? edificada : Number(u.superficie_terreno ?? 0);
};
