import {
  calcularCertificado,
  convertirAMonedaProyecto,
  prorratearPorSuperficie,
  situacionPartida,
  superficieDe,
} from './obra-calculos';

describe('obra — cálculos', () => {
  it('certificado: fondo de reparo y retención sobre el bruto', () => {
    expect(calcularCertificado({ monto_bruto: 45_000_000, pct_fondo_reparo: 5, pct_retencion: 0 }, 0)).toEqual({
      fondo_reparo: 2_250_000,
      retencion: 0,
      monto_neto: 42_750_000,
    });
    expect(calcularCertificado({ monto_bruto: 10_000.55, pct_fondo_reparo: 5, pct_retencion: 3 }, 2)).toEqual({
      fondo_reparo: 500.03,
      retencion: 300.02,
      monto_neto: 9_200.5,
    });
  });

  describe('conversión a la moneda del proyecto', () => {
    const tasas: Record<string, number> = { USD: 7_300, BRL: 1_400 };
    const tasaDe = jest.fn((c: string) => Promise.resolve(tasas[c]));
    beforeEach(() => tasaDe.mockClear());

    it('misma moneda: no convierte ni pide cotización', async () => {
      const r = await convertirAMonedaProyecto(
        { base: 1000, monedaDoc: 'USD', cotizacionDoc: 7000, monedaProyecto: 'USD', tasaDe },
        2,
      );
      expect(r).toEqual({ monto: 1000, tipo_cambio: null, factor: 1 });
      expect(tasaDe).not.toHaveBeenCalled();
    });

    it('compra en USD, proyecto en guaraníes: usa la cotización del comprobante', async () => {
      const r = await convertirAMonedaProyecto(
        { base: 100, monedaDoc: 'USD', cotizacionDoc: 7_250, monedaProyecto: 'PYG', tasaDe },
        0,
      );
      expect(r).toEqual({ monto: 725_000, tipo_cambio: 7_250, factor: 7_250 });
      expect(tasaDe).not.toHaveBeenCalled();
    });

    it('comprobante en USD sin cotización propia: usa la vigente', async () => {
      const r = await convertirAMonedaProyecto(
        { base: 100, monedaDoc: 'USD', cotizacionDoc: 1, monedaProyecto: 'PYG', tasaDe },
        0,
      );
      expect(r.monto).toBe(730_000);
    });

    it('gasto en guaraníes, proyecto en USD: divide por la cotización a la fecha', async () => {
      const r = await convertirAMonedaProyecto(
        { base: 7_300_000, monedaDoc: 'PYG', cotizacionDoc: 1, monedaProyecto: 'USD', tasaDe },
        2,
      );
      expect(r).toEqual({ monto: 1000, tipo_cambio: 7_300, factor: 1 / 7_300 });
    });

    it('tercera moneda: pasa por guaraníes', async () => {
      const r = await convertirAMonedaProyecto(
        { base: 1000, monedaDoc: 'BRL', cotizacionDoc: null, monedaProyecto: 'USD', tasaDe },
        2,
      );
      expect(r.monto).toBeCloseTo((1000 * 1400) / 7300, 2);
    });
  });

  describe('prorrateo por superficie', () => {
    it('la suma de las partes da exacto el total', () => {
      const r = prorratearPorSuperficie(
        1_000_000,
        [
          { id: 'a', m2: 33 },
          { id: 'b', m2: 33 },
          { id: 'c', m2: 33 },
        ],
        0,
      );
      expect(r.reduce((a, x) => a + x.costo, 0)).toBe(1_000_000);
      expect(r.map((x) => x.costo).sort()).toEqual([333_333, 333_333, 333_334]);
    });

    it('proporcional a la superficie; sin superficie no recibe costo', () => {
      const r = prorratearPorSuperficie(
        300,
        [
          { id: 'a', m2: 100 },
          { id: 'b', m2: 50 },
          { id: 'c', m2: 0 },
        ],
        2,
      );
      expect(r.map((x) => x.costo)).toEqual([200, 100, 0]);
      expect(r[0].participacion).toBeCloseTo(66.6667, 3);
    });

    it('nadie con superficie: todo en cero', () => {
      expect(prorratearPorSuperficie(100, [{ id: 'a', m2: 0 }], 0)[0].costo).toBe(0);
    });

    it('superficie: edificada; si no hay, la del terreno', () => {
      expect(superficieDe({ m2_propios: 80, m2_comunes: 12.5, superficie_terreno: null })).toBe(92.5);
      expect(superficieDe({ m2_propios: null, m2_comunes: null, superficie_terreno: 360 })).toBe(360);
    });
  });

  it('situación de partida: saldo, % ejecutado y desvío contra el avance físico', () => {
    expect(situacionPartida({ presupuestado: 1_000, ejecutado: 600, pct_avance: 50 }, 0)).toEqual({
      saldo: 400,
      pct_ejecutado: 60,
      valor_ganado: 500,
      desvio: 100,
    });
    expect(situacionPartida({ presupuestado: 0, ejecutado: 10, pct_avance: 0 }, 0).pct_ejecutado).toBeNull();
  });
});
