// ADAPTADOR PARA THE FACTORY HKA VENEZUELA (imprenta digital autorizada).
//
// Estructura VALIDADA CONTRA EL SWAGGER REAL de su ambiente demo
// (demoemisionv2.thefactoryhka.com.ve/swagger, bajado 2026-07-30; la copia
// vive en referencia/hka_swagger_demo.json). Diferencias reales contra la
// wiki que ya quedaron corregidas aqui:
//   - identificacionDocumento/vendedor/comprador/totales van DENTRO de
//     `documentoElectronico.encabezado` (la wiki los pintaba en la raiz);
//     detallesItems si va en la raiz del documentoElectronico.
//   - comprador.pais es OBLIGATORIO ('VE').
//   - La autenticacion es POST /api/Autenticacion con {usuario, clave} y
//     devuelve un JWT (confirmado tocando el demo vivo: los 400 cantan los
//     campos). La respuesta de Emision: {codigo, mensaje, validaciones,
//     resultado}.
//
// ⚠ LO UNICO QUE FALTA para emitir de verdad:
//   1. USUARIO Y CLAVE del ambiente demo (pedirselos al contacto de la
//      alianza; sin eso no hay token).
//   2. El catalogo de codigos (tipoDocumento, tipoTransaccion, tipoProveedor,
//      codigoImpuesto, unidadMedida): defaults razonables aqui, el catalogo
//      lo manda el proveedor.
// Mientras eso no este, se trabaja con la imprenta de practica.
import type { Acuse, Imprenta } from './imprenta.js';
import type { ConfigImprenta } from './config.js';
import type { Asiento, DocumentoFiscal } from './tipos.js';

/** Codigos por defecto. Se sobreescriben cuando el proveedor confirme su catalogo. */
const TIPO_DOCUMENTO: Record<string, string> = {
  factura: '01',
  nota_debito: '02',
  nota_credito: '03',
};

const dos = (n: number) => Number(n.toFixed(2));

/** Letra del RIF o cedula: V, J, E, P, G. */
function tipoIdentificacion(id?: string | null): string {
  const s = (id ?? '').toUpperCase().replace(/[^A-Z]/g, '');
  return s.charAt(0) || 'V';
}

/**
 * El documento fiscal venezolano se emite en BOLIVARES, aunque el negocio
 * cobre en dolares. Por eso cada monto se convierte con la tasa que quedo
 * congelada en la venta; sin tasa no se puede emitir.
 */
function aBs(montoUsd: number, tasa: number): number {
  return dos(montoUsd * tasa);
}

export function armarPayloadHKA(asiento: Asiento): Record<string, unknown> {
  const doc: DocumentoFiscal = asiento.documento;
  const tasa = doc.tasa_bs;
  if (!tasa || tasa <= 0) {
    throw new Error('La venta no trae la tasa del dia y el documento fiscal va en bolivares');
  }
  const f = new Date(asiento.sellado_en);
  const fecha = `${String(f.getDate()).padStart(2, '0')}/${String(f.getMonth() + 1).padStart(2, '0')}/${f.getFullYear()}`;
  const hora = `${String(f.getHours()).padStart(2, '0')}:${String(f.getMinutes()).padStart(2, '0')}:${String(f.getSeconds()).padStart(2, '0')}`;

  const gravado = doc.por_alicuota.filter((a) => a.alicuota > 0).reduce((s, a) => s + a.base_usd, 0);
  const exento = doc.por_alicuota.filter((a) => a.alicuota === 0).reduce((s, a) => s + a.base_usd, 0);

  return {
    documentoElectronico: {
      // El contrato real (swagger del demo) exige este nivel: la wiki lo
      // pintaba plano y era mentira.
      encabezado: {
        identificacionDocumento: {
          tipoDocumento: TIPO_DOCUMENTO[asiento.tipo] ?? '01',
          numeroDocumento: String(asiento.numero),
          tipoProveedor: '1',
          tipoTransaccion: '1',
          fechaEmision: fecha,
          fechaVencimiento: fecha,
          horaEmision: hora,
          serie: asiento.serie,
          sucursal: '1',
          moneda: 'VES',
          transaccionId: doc.origen_id,
        },
        vendedor: {
          codigo: doc.emisor.rif,
          nombre: doc.emisor.razon_social,
          numCajero: asiento.equipo,
        },
        comprador: {
          tipoIdentificacion: tipoIdentificacion(doc.cliente.rif ?? doc.cliente.cedula),
          numeroIdentificacion: (doc.cliente.rif ?? doc.cliente.cedula ?? '').replace(/[^0-9]/g, '') || '000000000',
          razonSocial: doc.cliente.nombre || 'Consumidor Final',
          direccion: doc.cliente.direccion ?? 'No indicada',
          pais: 'VE', // obligatorio segun el contrato real
          telefono: [],
          correo: [],
        },
        totales: {
          nroItems: String(doc.renglones.length),
          montoGravadoTotal: aBs(gravado, tasa),
          montoExentoTotal: aBs(exento, tasa),
          subtotal: aBs(doc.base_usd, tasa),
          totalAPagar: aBs(doc.total_usd, tasa),
          totalIVA: aBs(doc.iva_usd, tasa),
          montoTotalConIVA: aBs(doc.total_usd, tasa),
          totalDescuento: 0,
          formasPago: doc.pago
            ? [{ descripcion: doc.pago.metodo, monto: aBs(doc.total_usd, tasa), moneda: 'VES' }]
            : [],
        },
      },
      detallesItems: doc.renglones.map((r, i) => ({
        numeroLinea: String(i + 1),
        codigoPLU: String(i + 1),
        descripcion: r.nombre,
        cantidad: r.cantidad,
        unidadMedida: 'UND',
        precioUnitario: aBs(r.precio_usd, tasa),
        descuentoMonto: 0,
        tasaIVA: r.alicuota,
        valorIVA: aBs(r.iva_usd, tasa),
        valorTotalItem: aBs(r.total_usd, tasa),
      })),
    },
  };
}

export class ImprentaHKA implements Imprenta {
  readonly real = true;
  readonly nombre: string;
  private readonly cfg: ConfigImprenta;
  private token: string | null = null;

  constructor(cfg: ConfigImprenta) {
    this.cfg = cfg;
    this.nombre = cfg.nombre || 'The Factory HKA';
    this.token = cfg.token ?? null;
  }

  /** Token bearer. Si el proveedor entrego uno fijo, se usa ese. */
  private async autenticar(): Promise<string> {
    if (this.token) return this.token;
    const url = `${(this.cfg.url ?? '').replace(/\/$/, '')}/api/Autenticacion`;
    const r = await fetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ usuario: this.cfg.usuario, clave: this.cfg.clave }),
    });
    // Ojo: HKA responde HTTP 200 aunque rechace las credenciales, con el
    // motivo adentro ({"codigo":403,"mensaje":"Usuario/Clave incorrectos"}).
    // Sin leer el cuerpo, el negocio veria un "no devolvio token" que no le
    // dice nada; asi ve exactamente lo que dijo la imprenta.
    const j = (await r.json().catch(() => ({}))) as {
      token?: string;
      mensaje?: string;
      codigo?: string | number;
      resultado?: { token?: string };
    };
    const t = j.token ?? j.resultado?.token;
    if (!t) {
      const motivo = j.mensaje ? `${j.mensaje}${j.codigo ? ` (codigo ${j.codigo})` : ''}` : `HTTP ${r.status}`;
      throw new Error(`La imprenta no acepto las credenciales: ${motivo}`);
    }
    this.token = t;
    return t;
  }

  /**
   * Toca la puerta sin emitir nada.
   *
   * Dos caminos, porque hay imprentas que entregan usuario y clave y otras que
   * entregan el token ya hecho:
   *   - Con usuario y clave: se pide el token. Si lo entrega, sirven.
   *   - Con token fijo: NO basta con tenerlo, hay que PROBARLO contra la
   *     imprenta (un token vencido se ve igual de bonito que uno bueno). Se
   *     consulta el ultimo documento emitido, que no emite ni cambia nada:
   *     si responde 401, el token no sirve; cualquier otra respuesta quiere
   *     decir que la puerta se abrio.
   */
  async probar(): Promise<{ ok: boolean; detalle: string }> {
    const raiz = (this.cfg.url ?? '').replace(/\/$/, '');
    if (!raiz) return { ok: false, detalle: 'Falta la direccion de la imprenta.' };
    const fijo = this.cfg.token?.trim();
    try {
      if (fijo) {
        const r = await fetch(`${raiz}/api/UltimoDocumento`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${fijo}` },
          body: JSON.stringify({}),
        });
        if (r.status === 401) {
          return { ok: false, detalle: 'La imprenta no acepta ese token: puede estar vencido o mal copiado. Pidele uno nuevo a tu proveedor.' };
        }
        const j = (await r.json().catch(() => ({}))) as { mensaje?: string };
        this.token = fijo;
        return {
          ok: true,
          detalle: `La imprenta reconoce el token${j.mensaje ? ` (respondio: ${j.mensaje})` : ''}. Ya puede emitir documentos.`,
        };
      }
      this.token = null;
      const t = await this.autenticar();
      return { ok: true, detalle: `La imprenta acepto el usuario y la clave, y entrego su token (${t.slice(0, 12)}...).` };
    } catch (e) {
      return { ok: false, detalle: (e as Error).message };
    }
  }

  async emitir(asiento: Asiento): Promise<Acuse> {
    const token = await this.autenticar();
    const url = `${(this.cfg.url ?? '').replace(/\/$/, '')}/api/Emision`;
    const r = await fetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
      body: JSON.stringify(armarPayloadHKA(asiento)),
    });
    const j = (await r.json().catch(() => ({}))) as {
      codigo?: string;
      mensaje?: string;
      validaciones?: string[];
      resultado?: { numeroControl?: string; numeroDocumento?: string; fechaAsignacion?: string };
    };
    if (!r.ok || (j.codigo && j.codigo !== '200')) {
      // Si el token vencio, se pide uno nuevo la proxima vez.
      if (r.status === 401) this.token = null;
      // Las validaciones son el detalle campo por campo: oro para corregir.
      const detalle = j.validaciones?.length ? ` [${j.validaciones.join('; ')}]` : '';
      throw new Error(`La imprenta rechazo el documento: ${j.mensaje ?? `HTTP ${r.status}`}${detalle}`);
    }
    const res = j.resultado ?? {};
    if (!res.numeroControl) throw new Error('La imprenta acepto el documento pero no devolvio numero de control');
    return {
      numeroControl: res.numeroControl,
      numeroDocumento: res.numeroDocumento ?? String(asiento.numero),
      fechaAsignacion: res.fechaAsignacion ?? new Date().toISOString(),
      crudo: j,
    };
  }
}
