// ADAPTADOR DE LA IMPRENTA PROPIA (Gustito Sello).
//
// Cuando el negocio trabaja con NUESTRA imprenta digital, la Estacion le habla
// por aca. Es el mismo camino que usa cualquier sistema de terceros contra
// /api/emitir: no hay puerta privilegiada, y eso es a proposito (si un dia el
// SENIAT pregunta si la imprenta trata distinto a su propio software, la
// respuesta es no, y se puede demostrar).
//
// Si el cliente esta suspendido o la llave fue revocada, la imprenta responde
// "no autorizado" y el documento se queda en la cola, sellado en el libro pero
// sin numero de control. El negocio no pierde la venta y no puede seguir
// imprimiendo facturas validas.
import type { Acuse, Imprenta } from './imprenta.js';
import type { ConfigImprenta } from './config.js';
import type { ModeloFactura } from './factura.js';
import type { Asiento } from './tipos.js';

const SELLO_POR_DEFECTO = 'https://sello.gustitoxpress.app';

/** Bolivares con dos decimales. La factura tiene que ir en Bs, no en dolares. */
function aBs(montoUsd: number, tasa: number | null): number {
  if (!tasa || !Number.isFinite(tasa) || tasa <= 0) {
    throw new Error('Falta la tasa del dia: sin ella no se puede expresar el documento en bolivares');
  }
  return Math.round(montoUsd * tasa * 100) / 100;
}

export class ImprentaGustito implements Imprenta {
  readonly nombre = 'Gustito Sello (imprenta digital)';
  readonly real = true;
  private readonly url: string;
  private readonly llave: string;
  /** El modelo de factura del negocio: la imprenta lo guarda junto al documento
   *  para que el PDF del correo salga con la cara del negocio. */
  private readonly marca: ModeloFactura | null;

  constructor(cfg: ConfigImprenta, marca?: ModeloFactura | null) {
    this.url = (cfg.url?.trim() || SELLO_POR_DEFECTO).replace(/\/+$/, '');
    const llave = cfg.token?.trim() ?? '';
    if (!llave) throw new Error('Falta la llave que entrega la imprenta');
    this.llave = llave;
    this.marca = marca && (marca.mensaje_pie || marca.logo_url || marca.color) ? marca : null;
  }

  /**
   * Toca la puerta sin emitir: la imprenta responde si la llave sirve y a
   * nombre de quien esta registrada.
   */
  async probar(): Promise<{ ok: boolean; detalle: string }> {
    try {
      const r = await fetch(`${this.url}/api/emitir`, {
        method: 'POST',
        headers: { authorization: `Bearer ${this.llave}`, 'content-type': 'application/json' },
        body: JSON.stringify({ accion: 'saludo' }),
      });
      if (r.status === 401) return { ok: false, detalle: 'La imprenta no reconoce esa llave. Pidele una nueva a tu proveedor.' };
      // Cualquier respuesta que no sea 401 quiere decir que la puerta se abrio
      // (un 400 por cuerpo incompleto es esperado: no estamos emitiendo nada).
      return { ok: true, detalle: 'La imprenta reconoce la llave de este negocio.' };
    } catch (e) {
      return { ok: false, detalle: 'No se pudo hablar con la imprenta: ' + (e as Error).message };
    }
  }

  async emitir(asiento: Asiento): Promise<Acuse> {
    const d = asiento.documento;
    const tasa = d.tasa_bs;

    const r = await fetch(`${this.url}/api/emitir`, {
      method: 'POST',
      headers: { authorization: `Bearer ${this.llave}`, 'content-type': 'application/json' },
      body: JSON.stringify({
        rif: d.emisor.rif,
        serie: asiento.serie,
        tipo: asiento.tipo,
        numero_documento: asiento.numero,
        // El hash del asiento identifica el documento sin ambiguedad: si la
        // Estacion reintenta, la imprenta devuelve el mismo numero de control
        // en vez de gastar otro.
        hash: asiento.hash,
        fecha: asiento.sellado_en,
        base_bs: aBs(d.base_usd, tasa),
        iva_bs: aBs(d.iva_usd, tasa),
        total_bs: aBs(d.total_usd, tasa),
        documento: d,
        // La cara del negocio para el PDF del correo. Va APARTE del documento
        // sellado: la marca no forma parte del contenido fiscal.
        ...(this.marca ? { marca: this.marca } : {}),
      }),
    });

    const cuerpo = (await r.json().catch(() => ({}))) as {
      numero_control?: number;
      /** La forma impresa del art. 30: "00-00001234". */
      numero_control_texto?: string;
      sello?: string;
      ya_estaba?: boolean;
      error?: string;
      fecha_asignacion?: string | null;
      rango?: { desde?: number; hasta?: number } | null;
      imprenta?: {
        rif?: string;
        razon_social?: string;
        providencia?: string | null;
        providencia_fecha?: string | null;
      } | null;
    };

    if (!r.ok) {
      // 401 es el breaker: cliente suspendido o llave revocada. Se distingue a
      // proposito de una caida de red, porque no se arregla reintentando.
      if (r.status === 401) {
        throw new Error('RECHAZO: la imprenta no autoriza a este emisor. Comunicate con tu proveedor.');
      }
      if (r.status === 400) {
        throw new Error(`RECHAZO: ${cuerpo.error ?? 'la imprenta no acepto el documento'}`);
      }
      throw new Error(cuerpo.error ?? `La imprenta respondio ${r.status}`);
    }

    if (!Number.isFinite(cuerpo.numero_control)) {
      throw new Error('La imprenta no devolvio numero de control');
    }

    return {
      // Se imprime la forma que dicta la imprenta (art. 30: "00-00001234").
      // Si la puerta es vieja y no la manda, va el numero pelado.
      numeroControl: cuerpo.numero_control_texto ?? String(cuerpo.numero_control),
      numeroDocumento: String(asiento.numero),
      // La fecha que dicta la imprenta: es ella quien asigna (art. 7 num. 15).
      // Si no la manda (version vieja de la puerta), vale la de este momento.
      fechaAsignacion: cuerpo.fecha_asignacion ?? new Date().toISOString(),
      sello: cuerpo.sello,
      rango:
        cuerpo.rango && Number.isFinite(cuerpo.rango.desde) && Number.isFinite(cuerpo.rango.hasta)
          ? { desde: String(cuerpo.rango.desde), hasta: String(cuerpo.rango.hasta) }
          : null,
      imprentaFiscal:
        cuerpo.imprenta?.rif && cuerpo.imprenta?.razon_social
          ? {
              rif: cuerpo.imprenta.rif,
              razon_social: cuerpo.imprenta.razon_social,
              providencia: cuerpo.imprenta.providencia ?? null,
              providencia_fecha: cuerpo.imprenta.providencia_fecha ?? null,
            }
          : null,
      crudo: cuerpo,
    };
  }
}
