import { TaxItemType } from "@erp/core";
import type { Dinero } from "dinero.js";
import { InvoiceItemTaxSummary } from "./calculate-invoice-item-amounts";
import { toDinero } from "./calculate-utils";

export interface InvoiceHeaderCalcInput {
  subtotal_amount: number;
  discount_amount: number;
  header_discount_amount: number;
  taxable_amount: number;
  taxes_amount: number;
  taxes_summary: InvoiceItemTaxSummary[];
  total_amount: number;
}

export interface InvoiceHeaderCalcResult {
  subtotal_amount: number;
  discount_amount: number;
  header_discount_amount: number;
  taxable_amount: number;
  taxes_summary: InvoiceItemTaxSummary[];
  taxes_amount: number;
  total_amount: number;
}

/**
 * Agrega los importes de todas las líneas y recalcula los totales generales
 * con precisión financiera (sumas exactas en céntimos).
 */
export function calculateInvoiceHeaderAmounts(
  items: InvoiceHeaderCalcInput[],
  currency: string
): InvoiceHeaderCalcResult {
  const defaultScale = 2;

  let subtotal = toDinero(0, defaultScale, currency);
  let discount = toDinero(0, defaultScale, currency);
  let header_discount = toDinero(0, defaultScale, currency);
  let taxable = toDinero(0, defaultScale, currency);
  let taxes = toDinero(0, defaultScale, currency);
  let total = toDinero(0, defaultScale, currency);
  const taxes_summary: InvoiceItemTaxSummary[] = [];

  for (const item of items) {
    subtotal = subtotal.add(toDinero(item.subtotal_amount, defaultScale, currency));
    discount = discount.add(toDinero(item.discount_amount, defaultScale, currency));
    header_discount = header_discount.add(toDinero(item.discount_amount, defaultScale, currency));
    taxable = taxable.add(toDinero(item.taxable_amount, defaultScale, currency));
    taxes = taxes.add(toDinero(item.taxes_amount, defaultScale, currency));
    total = total.add(toDinero(item.total_amount, defaultScale, currency));
    taxes_summary.push(...item.taxes_summary);
  }

  const toNum = (d: Dinero.Dinero) => d.toUnit();

  return {
    subtotal_amount: toNum(subtotal),
    discount_amount: toNum(discount),
    header_discount_amount: toNum(header_discount),
    taxable_amount: toNum(taxable),
    taxes_amount: toNum(taxes),
    total_amount: toNum(total),
    taxes_summary: calculateTaxesSummary(taxes_summary, currency),
  };
}

function calculateTaxesSummary(
  items_summary: InvoiceItemTaxSummary[],
  currency: string
): InvoiceItemTaxSummary[] {
  const defaultScale = 2;
  const summaryMap = new Map<
    string,
    { tax: TaxItemType; taxable_amount: Dinero; taxes_amount: Dinero }
  >();

  for (const item of items_summary) {
    const { taxable_amount, taxes_amount, ...tax } = item;
    const key = tax.code;

    const current = summaryMap.get(key) ?? {
      tax,
      taxable_amount: toDinero(0, defaultScale, currency),
      taxes_amount: toDinero(0, defaultScale, currency),
    };

    summaryMap.set(key, {
      tax: current.tax,
      taxable_amount: current.taxable_amount.add(toDinero(taxable_amount, defaultScale, currency)),
      taxes_amount: current.taxes_amount.add(toDinero(taxes_amount, defaultScale, currency)),
    });
  }

  // Convertimos el mapa en un array con números desescalados
  const result: InvoiceItemTaxSummary[] = [];

  for (const { tax, taxable_amount, taxes_amount } of summaryMap.values()) {
    result.push({
      ...tax,
      taxable_amount: taxable_amount.toUnit(),
      taxes_amount: taxes_amount.toUnit(),
    });
  }

  // Los devolvermos ordenador: primero los que suman,
  // luego los que restan: IVA, IGIC, IPSI, Recargo de equivalencia, Retención.
  return result.sort((a, b) => a.name.localeCompare(b.name));
}
