import { env } from "../../config/env.js";

export interface ReceiptAiProduct {
  name: string;
  brand: string;
  model: string;
  serialNumber: string;
  category: string;
  quantity: number;
  unitPrice: string | null;
  totalPrice: string | null;
  warrantyMonths: number | null;
  warrantyEvidence: string;
  sourceText: string;
  confidence: number;
}

export interface ReceiptAiAnalysis {
  purchaseDate?: string;
  vendor?: string;
  purchasePrice?: string;
  documentNumber?: string;
  rawText?: string;
  products: ReceiptAiProduct[];
  confidence: number;
  provider: "openai";
  model: string;
}

const receiptSchema = {
  type: "object",
  additionalProperties: false,
  required: ["purchaseDate", "vendor", "purchasePrice", "documentNumber", "rawText", "products", "confidence"],
  properties: {
    purchaseDate: { type: ["string", "null"] },
    vendor: { type: ["string", "null"] },
    purchasePrice: { type: ["string", "null"] },
    documentNumber: { type: ["string", "null"] },
    rawText: { type: "string" },
    confidence: { type: "number", minimum: 0, maximum: 1 },
    products: {
      type: "array",
      items: {
        type: "object",
        additionalProperties: false,
        required: ["name", "brand", "model", "serialNumber", "category", "quantity", "unitPrice", "totalPrice", "warrantyMonths", "warrantyEvidence", "sourceText", "confidence"],
        properties: {
          name: { type: "string" },
          brand: { type: "string" },
          model: { type: "string" },
          serialNumber: { type: "string" },
          category: { type: "string" },
          quantity: { type: "integer", minimum: 1, maximum: 999 },
          unitPrice: { type: ["string", "null"] },
          totalPrice: { type: ["string", "null"] },
          warrantyMonths: { type: ["integer", "null"], minimum: 1, maximum: 600 },
          warrantyEvidence: { type: "string" },
          sourceText: { type: "string" },
          confidence: { type: "number", minimum: 0, maximum: 1 },
        },
      },
    },
  },
} as const;

function optionalText(value: unknown): string | undefined {
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
}

function optionalMoney(value: unknown): string | null {
  const text = optionalText(value);
  return text ?? null;
}

function parseResponseText(payload: Record<string, unknown>): string {
  if (typeof payload.output_text === "string") return payload.output_text;
  const output = Array.isArray(payload.output) ? payload.output : [];
  for (const item of output) {
    if (!item || typeof item !== "object") continue;
    const content = Array.isArray((item as { content?: unknown }).content) ? (item as { content: unknown[] }).content : [];
    for (const part of content) {
      if (part && typeof part === "object" && typeof (part as { text?: unknown }).text === "string") {
        return (part as { text: string }).text;
      }
    }
  }
  throw new Error("openai_receipt_empty_response");
}

function wait(milliseconds: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
}

export function isReceiptAiEnabled(): boolean {
  return env.OPENAI_RECEIPT_AI_ENABLED && Boolean(env.OPENAI_API_KEY);
}

export async function analyzeReceiptWithOpenAi(input: {
  buffer: Buffer;
  mimeType: string;
  filename: string;
}): Promise<ReceiptAiAnalysis> {
  return analyzeReceiptPagesWithOpenAi([input]);
}

export async function analyzeReceiptPagesWithOpenAi(inputs: Array<{
  buffer: Buffer;
  mimeType: string;
  filename: string;
}>): Promise<ReceiptAiAnalysis> {
  if (!isReceiptAiEnabled()) throw new Error("openai_receipt_not_configured");
  if (inputs.length === 0 || inputs.length > 10) throw new Error("openai_receipt_invalid_page_count");

  const fileContent = inputs.map((input) => {
    const dataUrl = `data:${input.mimeType};base64,${input.buffer.toString("base64")}`;
    return input.mimeType === "application/pdf"
      ? { type: "input_file", filename: input.filename, file_data: dataUrl }
      : { type: "input_image", image_url: dataUrl, detail: "high" };
  });
  const requestBody = JSON.stringify({
      model: env.OPENAI_RECEIPT_MODEL,
      input: [{
        role: "developer",
        content: [{ type: "input_text", text: "Extrae datos de una factura o ticket español. No inventes datos: usa cadena vacía o null si no son legibles. documentNumber es el número de factura, pedido o ticket (sin usar referencias de producto). Incluye todos los productos comprados, pero excluye envío, descuentos, impuestos y totales como productos. Para cada producto, conserva la descripción completa; brand puede aparecer como marca, fabricante, firma o proveedor de marca; model puede aparecer como modelo, referencia, ref., SKU, código, artículo, EAN o número de parte. Usa serialNumber solo para número de serie, IMEI o identificador único del equipo. Los importes se devuelven con punto decimal y sin símbolo de moneda. category debe ser una de: electronics, mobile, appliance, computer, clothing, vehicle, furniture, tool, toy, digital, secondhand, other." }],
      }, {
        role: "developer",
        content: [{ type: "input_text", text: "Completa warrantyMonths solo cuando el documento indique expresamente una garantía comercial o plazo de ese producto. Incluye una prueba breve en warrantyEvidence. No infieras garantías por normativa ni por categoría." }],
      }, {
        role: "developer",
        content: [{ type: "input_text", text: "Safety rule: products must contain only purchased item rows. Never return tax, VAT, fees, recycling charges, shipping, discounts, headers, totals, subtotals, payment rows, or terms as a product. sourceText is mandatory and must be the exact visible item row copied from the document; omit the product if that evidence cannot be copied exactly. rawText is a literal transcription only, never a summary or inference." }],
      }, {
        role: "user",
        content: fileContent,
      }],
      text: {
        format: {
          type: "json_schema",
          name: "ticket_factura",
          strict: true,
          schema: receiptSchema,
        },
      },
  });
  let response: Response | null = null;
  for (let attempt = 0; attempt < 2; attempt += 1) {
    response = await fetch("https://api.openai.com/v1/responses", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${env.OPENAI_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: requestBody,
    });
    if (response.ok || ![408, 409, 429, 500, 502, 503, 504].includes(response.status) || attempt === 1) break;
    await wait(500 * (attempt + 1));
  }
  if (!response) throw new Error("openai_receipt_no_response");
  if (!response.ok) {
    throw new Error(`openai_receipt_http_${response.status}`);
  }
  const raw = JSON.parse(parseResponseText(await response.json() as Record<string, unknown>)) as Record<string, unknown>;
  const rawProducts = Array.isArray(raw.products) ? raw.products : [];
  return {
    purchaseDate: optionalText(raw.purchaseDate),
    vendor: optionalText(raw.vendor),
    purchasePrice: optionalText(raw.purchasePrice),
    documentNumber: optionalText(raw.documentNumber),
    rawText: optionalText(raw.rawText),
    confidence: typeof raw.confidence === "number" ? raw.confidence : 0,
    provider: "openai",
    model: env.OPENAI_RECEIPT_MODEL,
    products: rawProducts.map((item) => {
      const product = item && typeof item === "object" ? item as Record<string, unknown> : {};
      return {
        name: optionalText(product.name) ?? "",
        brand: optionalText(product.brand) ?? "",
        model: optionalText(product.model) ?? "",
        serialNumber: optionalText(product.serialNumber) ?? "",
        category: optionalText(product.category) ?? "other",
        quantity: typeof product.quantity === "number" && Number.isInteger(product.quantity) && product.quantity > 0 ? product.quantity : 1,
        unitPrice: optionalMoney(product.unitPrice),
        totalPrice: optionalMoney(product.totalPrice),
        warrantyMonths: typeof product.warrantyMonths === "number" && Number.isInteger(product.warrantyMonths) && product.warrantyMonths > 0 ? Math.min(product.warrantyMonths, 600) : null,
        warrantyEvidence: optionalText(product.warrantyEvidence) ?? "",
        sourceText: optionalText(product.sourceText) ?? "",
        confidence: typeof product.confidence === "number" ? product.confidence : 0,
      };
    }).filter((product) => product.name || product.brand || product.model),
  };
}
