// EL ENLACE CON GUSTITO EN LA NUBE.
//
// La Estacion corre dentro del negocio, en 127.0.0.1: la nube no la puede
// llamar. Asi que es ella la que HALA: "que ventas cerradas faltan por sellar?".
//
// Por que hala en vez de que Gustito empuje:
//   * No hace falta abrir un puerto del local hacia afuera (eso seria un hueco
//     de seguridad en la computadora de un restaurante).
//   * Si se cae el internet, la cola se acumula en la nube y se vacia sola
//     cuando vuelve. No se pierde una venta.
//   * Si Gilberto le baja el breaker, esto deja de responder y la Estacion se
//     entera en el siguiente latido.
//
// FRONTERA: lo que llega de la nube es una VENTA (que se vendio y cuanto se
// cobro). El desglose de impuestos, la numeracion y el sellado se hacen aca.
import type { VentaEntrante } from './tipos.js';

/** Una venta esperando en la cola de la nube. */
export interface VentaDeNube {
  cola_id: string;
  tipo: 'pedido' | 'cuenta';
  origen_id: string;
  venta: {
    codigo: string;
    fecha: string;
    tipo_entrega: string;
    mesa: string | null;
    metodo_pago: string | null;
    tasa_bs: number | null;
    total_usd: number;
    costo_delivery_usd: number;
    propina_usd?: number;
    origen?: string | null;
    comprador: {
      nombre: string | null;
      rif: string | null;
      razon_social: string | null;
      direccion: string | null;
      correo: string | null;
      telefono: string | null;
    };
    renglones: { nombre: string; cantidad: number; precio_usd: number }[];
  };
}

export interface DatosLatido {
  nombre?: string;
  version?: string;
  equipo?: string;
  pendientes?: number;
  ultimo_documento?: number;
  libro_integro?: boolean;
}

/** Una solicitud de anulacion esperando en la nube: el portal pide, aca se emite. */
export interface AnulacionDeNube {
  anulacion_id: string;
  tipo: 'pedido' | 'cuenta';
  origen_id: string;
  motivo: string;
  pedida_por: string;
}

/**
 * De venta de Gustito a venta del modulo fiscal.
 * La ALICUOTA la pone la Estacion, no la nube: Gustito no sabe de impuestos y
 * asi tiene que quedar. Si manana cambia el IVA, se cambia aca y en un solo
 * sitio, sin tocar Gustito.
 */
export function aVentaEntrante(item: VentaDeNube, alicuota: number): VentaEntrante {
  const v = item.venta;
  const canal: VentaEntrante['canal'] =
    v.origen === 'caja' ? 'caja'
    : v.tipo_entrega === 'delivery' ? 'delivery'
    : v.tipo_entrega === 'pickup' ? 'pickup'
    : 'mesa';

  const otros: NonNullable<VentaEntrante['otros_cargos']> = [];
  if (v.costo_delivery_usd > 0) {
    otros.push({ nombre: 'Servicio de entrega', monto_usd: v.costo_delivery_usd, alicuota });
  }

  return {
    // El tipo va adelante para que una cuenta de mesa y un pedido nunca choquen
    // aunque compartieran el mismo uuid.
    origen_id: `${item.tipo}:${item.origen_id}`,
    canal,
    cliente: {
      nombre: v.comprador.razon_social?.trim() || v.comprador.nombre?.trim() || 'Consumidor Final',
      rif: v.comprador.rif,
      direccion: v.comprador.direccion,
    },
    renglones: v.renglones.map((r) => ({
      nombre: r.nombre,
      cantidad: r.cantidad,
      precio_usd: r.precio_usd,
      alicuota,
    })),
    otros_cargos: otros.length ? otros : undefined,
    tasa_bs: v.tasa_bs,
    pago: v.metodo_pago ? { metodo: v.metodo_pago } : null,
  };
}

export class EnlaceNube {
  private readonly url: string;
  private readonly llave: string;
  private readonly rif: string;

  constructor(op: { url: string; llave: string; rif: string }) {
    this.url = op.url.replace(/\/+$/, '');
    this.llave = op.llave;
    this.rif = op.rif;
  }

  private async pedir(accion: string, datos: Record<string, unknown> = {}): Promise<unknown> {
    const r = await fetch(`${this.url}/api/estacion`, {
      method: 'POST',
      headers: { authorization: `Bearer ${this.llave}`, 'content-type': 'application/json' },
      body: JSON.stringify({ accion, rif: this.rif, datos }),
    });
    const cuerpo = (await r.json().catch(() => ({}))) as { error?: string };
    if (!r.ok) {
      if (r.status === 401) {
        throw new Error('RECHAZO: la nube no autoriza a esta estacion. Comunicate con tu proveedor.');
      }
      throw new Error((cuerpo as { error?: string }).error ?? `La nube respondio ${r.status}`);
    }
    return cuerpo;
  }

  /** Ventas cerradas que faltan por sellar. Las marca como tomadas. */
  async halar(limite = 25): Promise<VentaDeNube[]> {
    const r = await this.pedir('pendientes', { limite });
    return Array.isArray(r) ? (r as VentaDeNube[]) : [];
  }

  /** Ya la selle: aca esta su numero. */
  async confirmar(op: {
    cola_id: string;
    numero_documento: number;
    numero_control?: number | string | null;
    token?: string | null;
  }): Promise<void> {
    await this.pedir('confirmar', {
      cola_id: op.cola_id,
      numero_documento: op.numero_documento,
      numero_control: op.numero_control ?? null,
      token: op.token ?? null,
    });
  }

  /** No pude con esta. Vuelve a la cola con su motivo. */
  async reportarError(cola_id: string, error: string): Promise<void> {
    await this.pedir('error', { cola_id, error });
  }

  /**
   * Sigo viva, y esto es lo que tengo.
   *
   * Es la llamada BARATA: de aca sale cuantas ventas hay esperando, y solo si
   * hay alguna se hace la pesada. Un negocio sin ventas nuevas no mueve una
   * sola fila en la nube.
   *
   * Tambien responde cada cuanto volver a preguntar. Lo decide la nube a
   * proposito: asi se puede ajustar el ritmo de todos sin actualizar el
   * programa instalado en cada negocio.
   */
  async latido(datos: DatosLatido): Promise<{
    pendientes_en_nube: number;
    anulaciones_en_nube: number;
    volverEnSegundos: number;
    serial: string | null;
  }> {
    const r = (await this.pedir('latido', datos as Record<string, unknown>)) as {
      pendientes_en_nube?: number;
      anulaciones_en_nube?: number;
      volver_en_segundos?: number;
      serial?: string;
    };
    return {
      pendientes_en_nube: r.pendientes_en_nube ?? 0,
      anulaciones_en_nube: r.anulaciones_en_nube ?? 0,
      volverEnSegundos: r.volver_en_segundos ?? 20,
      // El serial de la estacion: lo asigna la imprenta, viaja en el latido.
      serial: r.serial?.trim() || null,
    };
  }

  /** Solicitudes de anulacion que esperan su nota de credito. Las marca tomadas. */
  async anulaciones(limite = 20): Promise<AnulacionDeNube[]> {
    const r = await this.pedir('anulaciones', { limite });
    return Array.isArray(r) ? (r as AnulacionDeNube[]) : [];
  }

  /** El resultado de una solicitud: la nota emitida, o su problema. */
  async anulacionResultado(op: {
    anulacion_id: string;
    ok: boolean;
    numero_nota?: number | null;
    error?: string | null;
  }): Promise<void> {
    await this.pedir('anulacion_resultado', {
      anulacion_id: op.anulacion_id,
      ok: op.ok,
      numero_nota: op.numero_nota ?? null,
      error: op.error ?? null,
    });
  }

  /** ¿Hasta cual asiento tiene guardado la imprenta? El que recibe es el que sabe. */
  async respaldoHasta(): Promise<{ ultimo_seq: number; lineas: number }> {
    const r = (await this.pedir('respaldo_hasta')) as { ultimo_seq?: number; lineas?: number };
    return { ultimo_seq: r.ultimo_seq ?? 0, lineas: r.lineas ?? 0 };
  }

  /** Manda lineas del respaldo, ya cifradas. Repetir una no hace daño. */
  async respaldoGuardar(lineas: unknown[]): Promise<{ guardadas: number; ultimo_seq: number }> {
    const r = (await this.pedir('respaldo_guardar', { lineas })) as { guardadas?: number; ultimo_seq?: number };
    return { guardadas: r.guardadas ?? 0, ultimo_seq: r.ultimo_seq ?? 0 };
  }

  /** Baja el respaldo guardado, para levantar el libro en una maquina nueva. */
  async respaldoBajar(desde = 0, limite = 200): Promise<{ seq: number; iv: string; tag: string; datos: string }[]> {
    const r = (await this.pedir('respaldo_bajar', { desde, limite })) as {
      lineas?: { seq: number; iv: string; tag: string; datos: string }[];
    };
    return r.lineas ?? [];
  }

  /** Red de seguridad: recoge ventas cerradas que nunca entraron a la cola. */
  async rescatar(horas = 72): Promise<number> {
    const r = (await this.pedir('rescatar', { horas })) as { rescatadas?: number };
    return r.rescatadas ?? 0;
  }
}
