/**
 * Cliente API — Timbrado de CFDI ante el PAC.
 */
import { api } from '$lib/api';

export type StampingMode = 'pruebas' | 'produccion';
export type StampStatus = 'pendiente' | 'timbrado' | 'error';

export interface InvoiceStamp {
	id: number;
	invoice_id: number;
	mode: StampingMode;
	status: StampStatus;
	uuid: string | null;
	stamped_at: string | null;
	pac_rfc: string | null;
	sat_cert_number: string | null;
	pac_code: number | null;
	/** Folios que le quedan a la cuenta del PAC según la última respuesta. */
	pac_balance: number | null;
	error_message: string | null;
	xml_file_key: string | null;
	created_at: string | null;
}

/**
 * El backend responde 422 con la lista COMPLETA de datos fiscales que faltan, en vez de
 * fallar en el primero. Se conserva como error propio para poder pintarla: aplanarla a un
 * string obligaría a capturar los faltantes de uno en uno.
 */
export class MissingFiscalDataError extends Error {
	readonly missing: string[];
	constructor(message: string, missing: string[]) {
		super(message);
		this.name = 'MissingFiscalDataError';
		this.missing = missing;
	}
}

function qp(companyId: number): string {
	return new URLSearchParams({ company_id: String(companyId) }).toString();
}

/** Extrae el error del cliente base, que para 422 entrega un objeto y no una cadena. */
function toError(raw: unknown, fallback: string): Error {
	if (raw && typeof raw === 'object') {
		const o = raw as { message?: unknown; missing?: unknown; pac_error?: unknown };
		if (Array.isArray(o.missing)) {
			return new MissingFiscalDataError(
				typeof o.message === 'string' ? o.message : 'Faltan datos fiscales para timbrar',
				o.missing.map(String)
			);
		}
		// Rechazo del PAC: el mensaje útil es el suyo, no el genérico.
		if (typeof o.pac_error === 'string' && o.pac_error) return new Error(o.pac_error);
		if (typeof o.message === 'string' && o.message) return new Error(o.message);
	}
	return new Error(typeof raw === 'string' && raw ? raw : fallback);
}

export const stampingAPI = {
	/**
	 * Timbra la factura. Es idempotente en el backend: si ya tiene timbre lo devuelve sin
	 * volver a llamar al PAC.
	 */
	stamp: async (id: number, companyId: number): Promise<InvoiceStamp> => {
		const res = await api.post<InvoiceStamp>(`/v1/fin/invoices/${id}/stamp?${qp(companyId)}`, {});
		if (res.error) throw toError(res.error, 'No se pudo timbrar la factura');
		return res.data as InvoiceStamp;
	},

	/** Timbre vigente, o `null` si la factura no está timbrada (404 esperado). */
	get: async (id: number, companyId: number): Promise<InvoiceStamp | null> => {
		const res = await api.get<InvoiceStamp>(`/v1/fin/invoices/${id}/stamp?${qp(companyId)}`);
		if (res.error) {
			if (res.status === 404) return null;
			throw toError(res.error, 'No se pudo consultar el timbre');
		}
		return res.data as InvoiceStamp;
	},

	/** URL firmada del XML timbrado. Caduca, así que se pide en el momento de abrirlo. */
	xmlUrl: async (id: number, companyId: number): Promise<string> => {
		const res = await api.get<{ url: string }>(`/v1/fin/invoices/${id}/stamp/xml-url?${qp(companyId)}`);
		if (res.error) throw toError(res.error, 'No se pudo obtener el XML');
		return (res.data as { url: string }).url;
	}
};
