// ARMADOR DEL DOCUMENTO FISCAL. Aca SI se calcula el impuesto, porque este es
// el lado correcto de la frontera: el modulo corre en el local, con el RIF del
// negocio. Gustito Xpress no hace nada de esto: manda la venta y ya.
//
// Regla del piloto (decision de Gilberto): los precios de la carta se publican
// CON el IVA adentro, asi que la base se saca hacia atras y el cliente paga
// exactamente lo que dice el menu.
import type { DocumentoFiscal, RenglonFiscal, VentaEntrante } from './tipos.js';

export interface DatosEmisor {
  rif: string;
  razon_social: string;
  domicilio_fiscal: string;
  /** true = los precios ya traen el impuesto adentro (lo normal en Venezuela). */
  precios_con_iva: boolean;
  /**
   * IGTF: SOLO los designados sujetos pasivos especiales son agentes de
   * percepcion (Providencia SNAT/2022/000013). El modulo viene programado y
   * APAGADO: se enciende el dia que llegue la designacion, no antes.
   */
  igtf_activo?: boolean;
}

/** La alicuota vigente del IGTF sobre pagos en divisas. */
export const IGTF_PORCENTAJE = 3;

/**
 * Las alicuotas que existen en la Ley del IVA: general (16), reducida (8),
 * general + adicional para bienes suntuarios (31) y exento (0). Un documento
 * con una alicuota que no existe no se sella: la via de Gustito manda la
 * general del emisor (validada en su configuracion) y el talonario valida en
 * su carga, pero un programa de TERCEROS entrega renglones con su propia
 * alicuota, y ese numero tiene que ser de la ley, no un invento.
 */
export const ALICUOTAS_LEY = [0, 8, 16, 31];

const cent = (n: number) => Math.round(n * 100) / 100;

/** Parte un monto en base e impuesto, segun si el precio ya trae el IVA. */
export function desglosar(monto: number, alicuota: number, conIva: boolean): { base: number; iva: number } {
  const total = cent(monto);
  if (conIva) {
    const base = cent(total / (1 + alicuota / 100));
    return { base, iva: cent(total - base) };
  }
  return { base: total, iva: cent(total * (alicuota / 100)) };
}

/**
 * Convierte una venta de Gustito en un documento fiscal completo, con su
 * desglose por alicuota. No lo sella ni lo numera: de eso se encarga el libro.
 */
export function armarDocumento(venta: VentaEntrante, emisor: DatosEmisor): DocumentoFiscal {
  for (const r of [...venta.renglones, ...(venta.otros_cargos ?? [])]) {
    // Ojo con Number(null) y Number(''): dan 0, y una alicuota AUSENTE se
    // colaria en silencio como exento. Faltar no es lo mismo que estar exento.
    if (r.alicuota == null || String(r.alicuota).trim() === '' || !ALICUOTAS_LEY.includes(Number(r.alicuota))) {
      throw new Error(`La alicuota ${String(r.alicuota)} no existe en la Ley del IVA (validas: 16, 8, 31 y 0 exento)`);
    }
  }
  const conIva = emisor.precios_con_iva;
  const renglones: RenglonFiscal[] = venta.renglones.map((r) => {
    const totalLinea = cent(r.precio_usd * r.cantidad);
    const { base, iva } = desglosar(totalLinea, r.alicuota, conIva);
    return {
      nombre: r.nombre,
      cantidad: r.cantidad,
      precio_usd: r.precio_usd,
      alicuota: r.alicuota,
      base_usd: base,
      iva_usd: iva,
      total_usd: cent(base + iva),
    };
  });

  // Delivery, servicio y demas cargos van gravados igual que un renglon mas.
  for (const c of venta.otros_cargos ?? []) {
    const { base, iva } = desglosar(c.monto_usd, c.alicuota, conIva);
    renglones.push({
      nombre: c.nombre,
      cantidad: 1,
      precio_usd: c.monto_usd,
      alicuota: c.alicuota,
      base_usd: base,
      iva_usd: iva,
      total_usd: cent(base + iva),
    });
  }

  // Desglose por alicuota: el documento tiene que mostrar cuanto va a cada tasa.
  const mapa = new Map<number, { base_usd: number; iva_usd: number }>();
  for (const r of renglones) {
    const acc = mapa.get(r.alicuota) ?? { base_usd: 0, iva_usd: 0 };
    acc.base_usd = cent(acc.base_usd + r.base_usd);
    acc.iva_usd = cent(acc.iva_usd + r.iva_usd);
    mapa.set(r.alicuota, acc);
  }
  const por_alicuota = [...mapa.entries()]
    .map(([alicuota, v]) => ({ alicuota, ...v }))
    .sort((a, b) => a.alicuota - b.alicuota);

  const base_usd = cent(por_alicuota.reduce((s, x) => s + x.base_usd, 0));
  const iva_usd = cent(por_alicuota.reduce((s, x) => s + x.iva_usd, 0));
  const total_usd = cent(base_usd + iva_usd);

  // IGTF: solo con el modulo encendido Y pago en divisas. Zelle es divisa por
  // definicion; el efectivo solo si la venta lo dice explicito (no se adivina).
  const enDivisas = venta.pago?.en_divisas ?? venta.pago?.metodo === 'zelle';
  const igtf_usd = emisor.igtf_activo && enDivisas ? cent(total_usd * (IGTF_PORCENTAJE / 100)) : undefined;

  return {
    tipo: 'factura',
    emisor: { rif: emisor.rif, razon_social: emisor.razon_social, domicilio_fiscal: emisor.domicilio_fiscal },
    cliente: venta.cliente,
    canal: venta.canal,
    renglones,
    por_alicuota,
    base_usd,
    iva_usd,
    total_usd,
    tasa_bs: venta.tasa_bs,
    total_bs: venta.tasa_bs ? cent(total_usd * venta.tasa_bs) : null,
    ...(igtf_usd ? { igtf_usd } : {}),
    pago: venta.pago ?? null,
    origen_id: venta.origen_id,
  };
}

/** Lo que se copia del papel cuando se cargan las facturas del talonario. */
export interface FacturaDeTalonario {
  /** El numero que ya venia impreso en el papel. */
  numero: string;
  /** Cuando se emitio DE VERDAD (no cuando se esta cargando). */
  emitido_en: string;
  cliente: { nombre: string; rif?: string | null; cedula?: string | null };
  /** Lo que se cobro en total, tal cual dice el papel. */
  total_usd: number;
  alicuota: number;
  tasa_bs?: number | null;
  nota?: string | null;
}

/**
 * Arma el documento de una factura que se hizo A MANO en el talonario mientras
 * todo estaba caido. No se le pone numero de la estacion ni se transmite: ese
 * papel ya tiene su numero y su control impresos. Se anota para que el reporte
 * del mes cuadre con lo que de verdad se vendio.
 */
export function armarFacturaDeTalonario(f: FacturaDeTalonario, emisor: DatosEmisor): DocumentoFiscal {
  // El papel siempre trae el total cobrado, asi que la base se saca hacia atras
  // sin importar como esten publicados los precios.
  const { base, iva } = desglosar(f.total_usd, f.alicuota, true);
  const renglon: RenglonFiscal = {
    nombre: f.nota?.trim() || 'Consumo facturado en talonario',
    cantidad: 1,
    precio_usd: cent(f.total_usd),
    alicuota: f.alicuota,
    base_usd: base,
    iva_usd: iva,
    total_usd: cent(base + iva),
  };
  return {
    tipo: 'factura_manual',
    emisor: { rif: emisor.rif, razon_social: emisor.razon_social, domicilio_fiscal: emisor.domicilio_fiscal },
    cliente: { nombre: f.cliente.nombre, rif: f.cliente.rif ?? null, cedula: f.cliente.cedula ?? null },
    canal: 'caja',
    renglones: [renglon],
    por_alicuota: [{ alicuota: f.alicuota, base_usd: base, iva_usd: iva }],
    base_usd: base,
    iva_usd: iva,
    total_usd: cent(base + iva),
    tasa_bs: f.tasa_bs ?? null,
    total_bs: f.tasa_bs ? cent(cent(base + iva) * f.tasa_bs) : null,
    pago: null,
    // Dos facturas del mismo talonario no se cargan dos veces.
    origen_id: `talonario:${f.numero.trim().toUpperCase()}`,
    talonario: { numero: f.numero.trim().toUpperCase(), emitido_en: f.emitido_en },
  };
}

/** Documento de una nota de credito: los mismos renglones, en negativo. */
export function armarNotaCredito(doc: DocumentoFiscal, motivo: string): DocumentoFiscal {
  return {
    ...doc,
    tipo: 'nota_credito',
    renglones: doc.renglones.map((r) => ({
      ...r,
      base_usd: -r.base_usd,
      iva_usd: -r.iva_usd,
      total_usd: -r.total_usd,
    })),
    por_alicuota: doc.por_alicuota.map((a) => ({ ...a, base_usd: -a.base_usd, iva_usd: -a.iva_usd })),
    base_usd: -doc.base_usd,
    iva_usd: -doc.iva_usd,
    total_usd: -doc.total_usd,
    total_bs: doc.total_bs != null ? -doc.total_bs : null,
    // Si la factura percibio IGTF, la nota lo devuelve tambien.
    ...(doc.igtf_usd != null ? { igtf_usd: -doc.igtf_usd } : {}),
    origen_id: `${doc.origen_id}:nc`,
    pago: null,
    // El motivo viaja en el documento: la norma exige saber por que se corrigio.
    motivo,
  };
}
