/**
 * Utilidades para el "Registro de Comprobantes de Ventas, Compras, Ingresos y/o
 * Egresos" de Marangatu (SET Paraguay, especificación 06/2021).
 *
 * Formato del archivo importable:
 *  - CSV delimitado por ';' o TXT por tabulaciones, UTF-8, SIN fila de cabecera.
 *  - Montos enteros positivos sin decimales, con IVA incluido.
 *  - RUC sin dígito verificador.
 *  - Nombre del archivo: <RUC>_REG_MMAAAA_XXXXX (dentro de un ZIP homónimo).
 *
 * Ver docs/docs-pdf/especificaciones-libros-compra-venta-marangatu.md
 */

import { comprobante_compra_tipo } from '@prisma/client';

export const DELIM_CSV = ';';

/** Persona (cliente/proveedor) con los campos mínimos para identificar. */
export interface PersonaMarangatu {
  razon_social?: string | null;
  ruc?: string | null; // sin DV (persona.ruc ya es VarChar(8))
  nro_documento?: string | null;
}

/** Tabla 3: tipos de identificación. */
export const TIPO_ID = {
  RUC: 11,
  CEDULA: 12,
  PASAPORTE: 13,
  CEDULA_EXTRANJERO: 14,
  SIN_NOMBRE: 15,
  DIPLOMATICO: 16,
  IDENTIFICACION_TRIBUTARIA: 17,
} as const;

/** Tabla 4 (subconjunto usado). */
export const TIPO_COMPROBANTE = {
  AUTOFACTURA: 101,
  FACTURA: 109,
  NOTA_CREDITO: 110,
  NOTA_DEBITO: 111,
  TICKET_MAQUINA: 112,
  DESPACHO_IMPORTACION: 107,
  // Registro de egresos
  COMPROBANTE_EXTERIOR: 202,
  EXTRACTO_TARJETA: 207,
} as const;

/** Tabla 2: condición de la operación. */
export const CONDICION = { CONTADO: 1, CREDITO: 2 } as const;

/** Tabla 1: tipos de registro. */
export const TIPO_REGISTRO = { VENTAS: 1, COMPRAS: 2, INGRESOS: 3, EGRESOS: 4 } as const;

/** Tipo de comprobante de compras y gastos del ERP → código de la Tabla 4 y registro al que va. */
export const COMPROBANTE_COMPRA_MARANGATU: Record<comprobante_compra_tipo, { codigo: number; registro: number }> = {
  [comprobante_compra_tipo.FACTURA]: { codigo: TIPO_COMPROBANTE.FACTURA, registro: TIPO_REGISTRO.COMPRAS },
  [comprobante_compra_tipo.AUTOFACTURA]: { codigo: TIPO_COMPROBANTE.AUTOFACTURA, registro: TIPO_REGISTRO.COMPRAS },
  [comprobante_compra_tipo.DESPACHO_IMPORTACION]: { codigo: TIPO_COMPROBANTE.DESPACHO_IMPORTACION, registro: TIPO_REGISTRO.COMPRAS },
  [comprobante_compra_tipo.COMPROBANTE_EXTERIOR]: { codigo: TIPO_COMPROBANTE.COMPROBANTE_EXTERIOR, registro: TIPO_REGISTRO.EGRESOS },
  [comprobante_compra_tipo.EXTRACTO_TARJETA]: { codigo: TIPO_COMPROBANTE.EXTRACTO_TARJETA, registro: TIPO_REGISTRO.EGRESOS },
};

/**
 * Fecha → 'dd/mm/aaaa'. Usa los componentes UTC a propósito: las fechas de emisión
 * se guardan como la fecha-calendario a medianoche UTC (o con hora real UTC), y el
 * resto del ERP las lee así (formatDateResponse = toISOString). Usar la hora LOCAL
 * correría la fecha un día en servidores fuera de UTC (Paraguay es UTC-3/-4).
 */
export function fmtFecha(d: Date | string | null | undefined): string {
  if (!d) return '';
  const date = d instanceof Date ? d : new Date(d);
  if (Number.isNaN(date.getTime())) return '';
  const dd = String(date.getUTCDate()).padStart(2, '0');
  const mm = String(date.getUTCMonth() + 1).padStart(2, '0');
  const aaaa = String(date.getUTCFullYear());
  return `${dd}/${mm}/${aaaa}`;
}

/** Monto → entero positivo sin decimales (string). Negativos se llevan a 0. */
export function montoEntero(v: number | string | null | undefined): string {
  const n = Math.round(Number(v ?? 0));
  return String(n > 0 ? n : 0);
}

/** Número de comprobante 'est-punto-numero' → '001-001-0000123'. */
export function numeroComprobante(
  est?: string | null,
  punto?: string | null,
  numero?: string | null,
): string {
  const e = String(est ?? '').replace(/\D/g, '').padStart(3, '0').slice(-3);
  const p = String(punto ?? '').replace(/\D/g, '').padStart(3, '0').slice(-3);
  const n = String(numero ?? '').replace(/\D/g, '').padStart(7, '0').slice(-7);
  if (!numero) return '';
  return `${e}-${p}-${n}`;
}

/** Timbrado → solo dígitos (NUM 8). */
export function timbrado(v?: string | null): string {
  return String(v ?? '').replace(/\D/g, '');
}

/** Código de tipo de identificación según los datos de la persona. */
export function tipoIdentificacion(p?: PersonaMarangatu | null): number {
  if (p?.ruc && p.ruc.trim()) return TIPO_ID.RUC;
  if (p?.nro_documento && p.nro_documento.trim()) return TIPO_ID.CEDULA;
  return TIPO_ID.SIN_NOMBRE;
}

/** Número de identificación (RUC sin DV, o cédula, o '0' para sin nombre). */
export function numeroIdentificacion(p?: PersonaMarangatu | null): string {
  if (p?.ruc && p.ruc.trim()) return p.ruc.trim();
  if (p?.nro_documento && p.nro_documento.trim()) return p.nro_documento.trim();
  return '0';
}

/**
 * Nombre o razón social del comprador/proveedor. Para RUC/Cédula/Sin Nombre no es
 * requerido (Marangatu lo obtiene de sus registros oficiales), pero enviarlo es
 * inofensivo y reproduce el ejemplo oficial de la SET, así que se incluye siempre
 * que exista.
 */
export function nombreRazonSocial(razonSocial?: string | null): string {
  return (razonSocial ?? '').trim();
}

/** Descripción de condición ('Contado'/'Crédito') → código 1/2. */
export function condicionCodigo(descripcion?: string | null): number {
  const d = (descripcion ?? '').toLowerCase();
  return d.includes('cr') /* credito/crédito */ ? CONDICION.CREDITO : CONDICION.CONTADO;
}

/** Booleano → 'S'/'N' (Tabla 5). */
export function boolSN(v: boolean): string {
  return v ? 'S' : 'N';
}

/** Obligaciones a las que imputa un comprobante (columnas IMPUTA IVA/IRE/IRP-RSP y NO IMPUTA). */
export interface Imputacion {
  iva: boolean;
  ire: boolean;
  irp: boolean;
  /** Una parte del comprobante no se imputa. Sólo vale junto con otra imputación. */
  noImputa: boolean;
}

/**
 * Imputación de un comprobante de compra para el archivo.
 *
 * Regla de la SET (especificación 06/2021, "Consideraciones generales"): todo
 * comprobante imputa al menos a una obligación (IVA, IRE o IRP-RSP), y "no imputa"
 * sólo se marca junto con otra. Una fila con las tres en N la rechaza Marangatu.
 *
 * - Lo elegido en el comprobante manda (null = no se eligió).
 * - Si no se eligió, se usa lo de la empresa; un gasto no deducible no imputa IVA,
 *   salvo que el IVA sea lo único que la empresa imputa (si no, la fila quedaría vacía).
 * - Si con todo eso no imputa a nada, vuelve a lo de la empresa.
 */
export function imputacionComprobante(
  empresa: { iva: boolean; ire: boolean; irp: boolean },
  comprobante: { iva?: boolean | null; ire?: boolean | null; irp?: boolean | null; noImputa?: boolean | null } = {},
  opciones: { deducibleIva?: boolean } = {},
): Imputacion {
  const otrasDeEmpresa = empresa.ire || empresa.irp;
  const ivaPorDefecto = empresa.iva && (opciones.deducibleIva !== false || !otrasDeEmpresa);
  let iva = comprobante.iva ?? ivaPorDefecto;
  let ire = comprobante.ire ?? empresa.ire;
  let irp = comprobante.irp ?? empresa.irp;
  if (!iva && !ire && !irp) ({ iva, ire, irp } = empresa);
  const imputaAlgo = iva || ire || irp;
  return { iva, ire, irp, noImputa: Boolean(comprobante.noImputa) && imputaAlgo };
}

/** ¿Moneda extranjera? (código != 'PYG'). */
export function monedaExtranjera(codigo?: string | null): string {
  return boolSN(!!codigo && codigo.toUpperCase() !== 'PYG');
}

/**
 * Escapa un campo para CSV con ';'. Si contiene ';', '"' o salto de línea,
 * se envuelve en comillas dobles duplicando las internas.
 */
export function csvCampo(valor: string | number): string {
  const s = String(valor ?? '');
  if (/[;"\n\r]/.test(s)) {
    return `"${s.replace(/"/g, '""')}"`;
  }
  return s;
}

/** Une una fila de campos con el delimitador CSV, escapando cada uno. */
export function filaCsv(campos: (string | number)[]): string {
  return campos.map(csvCampo).join(DELIM_CSV);
}

/**
 * Nombre de archivo reglamentario: <RUC sin DV>_REG_MMAAAA_XXXXX
 * @param secuencia identificador alfanumérico del contribuyente (<=5), p.ej. 'V0001'.
 */
export function nombreArchivo(rucSinDv: string, mes: number, anio: number, secuencia: string): string {
  const mm = String(mes).padStart(2, '0');
  const seq = (secuencia || '00001').replace(/[^A-Za-z0-9]/g, '').slice(0, 5) || '00001';
  return `${rucSinDv}_REG_${mm}${anio}_${seq}`;
}

/**
 * Fila del registro de COMPRAS (tipo 2, 20 columnas).
 *
 * Reglas por tipo de comprobante de la especificación:
 * - 109 FACTURA: la condición (contado/crédito) es obligatoria.
 * - 101 AUTOFACTURA: los montos por tasa van en 0 y sólo se informa el total.
 * - 107 DESPACHO: timbrado 0 y sin número de comprobante.
 */
export function filaCompra(opts: {
  persona: PersonaMarangatu | null;
  tipoComprobante: number;
  fecha: Date | string | null;
  timbrado?: string | null;
  numero?: string;
  gross10: number;
  gross5: number;
  exento: number;
  condicion?: number | '';
  monedaExtranjera: string;
  imputacion: Imputacion;
  asocNumero?: string;
  asocTimbrado?: string;
}): string {
  const g10 = Math.max(0, Math.round(opts.gross10));
  const g5 = Math.max(0, Math.round(opts.gross5));
  const ex = Math.max(0, Math.round(opts.exento));
  const total = g10 + g5 + ex;
  const soloTotal = opts.tipoComprobante === TIPO_COMPROBANTE.AUTOFACTURA;
  const esDespacho = opts.tipoComprobante === TIPO_COMPROBANTE.DESPACHO_IMPORTACION;
  return filaCsv([
    TIPO_REGISTRO.COMPRAS, // 1 tipo de registro
    tipoIdentificacion(opts.persona), // 2 tipo identificación proveedor
    numeroIdentificacion(opts.persona), // 3 número identificación
    nombreRazonSocial(opts.persona?.razon_social), // 4 nombre/razón social
    opts.tipoComprobante, // 5 tipo de comprobante
    fmtFecha(opts.fecha), // 6 fecha emisión
    esDespacho ? '0' : timbrado(opts.timbrado), // 7 timbrado
    esDespacho ? '' : opts.numero ?? '', // 8 número comprobante
    montoEntero(soloTotal ? 0 : g10), // 9 gravado 10% (IVA incluido)
    montoEntero(soloTotal ? 0 : g5), // 10 gravado 5% (IVA incluido)
    montoEntero(soloTotal ? 0 : ex), // 11 no gravado / exento
    montoEntero(total), // 12 total del comprobante
    opts.tipoComprobante === TIPO_COMPROBANTE.FACTURA ? opts.condicion ?? CONDICION.CONTADO : '', // 13 condición
    opts.monedaExtranjera, // 14 operación en moneda extranjera
    boolSN(opts.imputacion.iva), // 15 imputa IVA
    boolSN(opts.imputacion.ire), // 16 imputa IRE
    boolSN(opts.imputacion.irp), // 17 imputa IRP-RSP
    boolSN(opts.imputacion.noImputa), // 18 no imputa
    opts.asocNumero ?? '', // 19 número comprobante de compra asociado
    opts.asocTimbrado ?? '', // 20 timbrado comprobante asociado
  ]);
}

/**
 * Fila del registro de EGRESOS (tipo 4, 18 columnas), para lo que no es comprobante de
 * compra paraguayo: 202 comprobante del exterior legalizado y 207 extracto de tarjeta.
 *
 * - 202: identificación tributaria (17) obligatoria; no imputa IVA.
 * - 207: sin número ni identificación; pide número de cuenta/tarjeta y entidad, y es el
 *   único egreso que puede imputar IVA.
 *
 * Devuelve null si, sin IVA, el comprobante no imputa a nada: la SET rechaza esa fila,
 * y una empresa sin IRE ni IRP no tiene nada que informar en egresos.
 */
export function filaEgreso(opts: {
  tipoComprobante: number;
  fecha: Date | string | null;
  numero?: string;
  persona: PersonaMarangatu | null;
  total: number;
  imputacion: Imputacion;
  cuenta?: string | null;
  entidad?: string | null;
}): string | null {
  const esExtracto = opts.tipoComprobante === TIPO_COMPROBANTE.EXTRACTO_TARJETA;
  const iva = esExtracto && opts.imputacion.iva;
  const { ire, irp } = opts.imputacion;
  if (!iva && !ire && !irp) return null;
  const identificacion = esExtracto ? '' : (opts.persona?.ruc || opts.persona?.nro_documento || '').trim();
  return filaCsv([
    TIPO_REGISTRO.EGRESOS, // 1 tipo de registro
    opts.tipoComprobante, // 2 tipo de comprobante
    fmtFecha(opts.fecha), // 3 fecha
    esExtracto ? '' : opts.numero ?? '', // 4 número de comprobante
    esExtracto ? '' : TIPO_ID.IDENTIFICACION_TRIBUTARIA, // 5 tipo de identificación
    identificacion, // 6 número de identificación
    nombreRazonSocial(opts.persona?.razon_social), // 7 nombre o descripción
    montoEntero(Math.max(0, Math.round(opts.total))), // 8 monto total
    boolSN(iva), // 9 imputa IVA
    boolSN(ire), // 10 imputa IRE
    boolSN(irp), // 11 imputa IRP-RSP
    boolSN(opts.imputacion.noImputa), // 12 no imputa
    esExtracto ? (opts.cuenta ?? '').trim() : '', // 13 número de cuenta / tarjeta
    esExtracto ? (opts.entidad ?? '').trim() : '', // 14 banco / financiera / cooperativa
    '', // 15 número de empleador IPS
    '', // 16 especificar tipo de documento
    '', // 17 número del comprobante de compra asociado
    '', // 18 timbrado del comprobante de compra asociado
  ]);
}
