import { z } from "zod";
import type { ContractAiExtractionOutput, ContractRiskClause } from "@/lib/contracts/types";

const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
const DEFAULT_OPENAI_MODEL = "gpt-4.1-mini";
const DEFAULT_TIMEOUT_MS = 60_000;
const DEFAULT_MAX_TEXT_CHARS = 45_000;

const ContractExtractionResponseSchema = z.object({
  fields: z
    .object({
      title: z.string().nullable().optional(),
      counterpartyEntity: z.string().nullable().optional(),
      contractNumber: z.string().nullable().optional(),
      contractType: z.string().nullable().optional(),
      startDate: z.string().nullable().optional(),
      endDate: z.string().nullable().optional(),
      totalAmount: z.union([z.number(), z.string()]).nullable().optional(),
      currency: z.string().nullable().optional(),
      description: z.string().nullable().optional(),
    })
    .nullable()
    .optional(),
  deliverables: z
    .array(
      z.object({
        title: z.string().nullable().optional(),
        dueDate: z.string().nullable().optional(),
        amountLinked: z.union([z.number(), z.string()]).nullable().optional(),
        notes: z.string().nullable().optional(),
      }),
    )
    .nullable()
    .optional(),
  paymentMilestones: z
    .array(
      z.object({
        concept: z.string().nullable().optional(),
        paymentDate: z.string().nullable().optional(),
        amount: z.union([z.number(), z.string()]).nullable().optional(),
      }),
    )
    .nullable()
    .optional(),
  riskClauses: z
    .array(
      z.object({
        title: z.string().nullable().optional(),
        snippet: z.string().nullable().optional(),
        riskLevel: z.enum(["low", "medium", "high"]).nullable().optional(),
      }),
    )
    .nullable()
    .optional(),
});

type OpenAiChatCompletionResponse = {
  choices?: Array<{
    message?: {
      content?: string | null;
    };
  }>;
  model?: string;
  usage?: {
    prompt_tokens?: number;
    completion_tokens?: number;
    total_tokens?: number;
  };
  error?: {
    message?: string;
  };
};

function parsePositiveInteger(value: string | undefined, fallback: number) {
  const parsed = Number.parseInt((value ?? "").trim(), 10);
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
}

function getOpenAiApiKey() {
  return process.env.OPENAI_API_KEY?.trim() || process.env.API_KEY?.trim() || process.env.api_key?.trim() || "";
}

function compactText(value: string, maxLength = 600) {
  const compacted = value.replace(/\s+/g, " ").trim();
  if (!compacted) {
    return null;
  }

  return compacted.length > maxLength ? compacted.slice(0, maxLength) : compacted;
}

function normalizeIsoDate(value: unknown): string | null {
  if (!value || typeof value !== "string") {
    return null;
  }

  const compact = value.trim();
  if (!compact) {
    return null;
  }

  const parsed = new Date(compact);
  if (Number.isNaN(parsed.getTime())) {
    return null;
  }

  return parsed.toISOString();
}

function normalizeAmount(value: unknown): number | null {
  if (typeof value === "number") {
    return Number.isFinite(value) ? Number(value.toFixed(2)) : null;
  }

  if (typeof value !== "string") {
    return null;
  }

  const normalized = value
    .replace(/[^\d.,-]/g, "")
    .replace(/\.(?=.*\.)/g, "")
    .replace(/,/g, ".");

  const parsed = Number.parseFloat(normalized);
  if (!Number.isFinite(parsed)) {
    return null;
  }

  return Number(parsed.toFixed(2));
}

function clampText(fullText: string) {
  const maxChars = parsePositiveInteger(process.env.OPENAI_CONTRACT_MAX_CHARS, DEFAULT_MAX_TEXT_CHARS);

  if (fullText.length <= maxChars) {
    return fullText;
  }

  return `${fullText.slice(0, maxChars)}\n\n[TEXT_TRUNCATED_TO_${maxChars}_CHARS]`;
}

function extractJsonObject(raw: string) {
  const trimmed = raw.trim();

  try {
    return JSON.parse(trimmed) as unknown;
  } catch {
    // Continue with alternative parsing.
  }

  const noFence = trimmed
    .replace(/^```json\s*/i, "")
    .replace(/^```\s*/i, "")
    .replace(/\s*```$/i, "")
    .trim();

  try {
    return JSON.parse(noFence) as unknown;
  } catch {
    // Continue with slicing.
  }

  const firstBrace = noFence.indexOf("{");
  const lastBrace = noFence.lastIndexOf("}");

  if (firstBrace >= 0 && lastBrace > firstBrace) {
    return JSON.parse(noFence.slice(firstBrace, lastBrace + 1)) as unknown;
  }

  throw new Error("La respuesta de IA no contiene JSON valido.");
}

function inferContractNumber(text: string) {
  const match = text.match(/(?:contrato|numero\s+de\s+contrato|expediente)\s*(?:no\.?|num(?:ero)?\.?|:)\s*([a-z0-9\-_/]{4,40})/i);
  return compactText(match?.[1] ?? "", 80);
}

function inferAmount(text: string) {
  const match = text.match(/(?:monto\s+total|importe\s+total|total\s+a\s+pagar)\s*[:\-]?\s*\$?\s*([\d.,]{4,20})/i);
  return normalizeAmount(match?.[1] ?? null);
}

function inferCounterparty(text: string) {
  const match = text.match(/(?:entidad\s+contratante|dependencia|contratante)\s*[:\-]\s*([^\n]{4,160})/i);
  return compactText(match?.[1] ?? "", 220);
}

function inferTitle(text: string) {
  const lines = text
    .split(/\r?\n/)
    .map((line) => line.trim())
    .filter(Boolean);

  const contractLine = lines.find((line) => /\bcontrato\b/i.test(line));
  return compactText(contractLine ?? lines[0] ?? "Contrato sin titulo", 220);
}

function inferContractType(text: string) {
  if (/obra\s+publica/i.test(text)) {
    return "Obra publica";
  }

  if (/servicio/i.test(text)) {
    return "Servicios";
  }

  if (/adquisicion/i.test(text)) {
    return "Adquisiciones";
  }

  return "General";
}

function inferDeliverables(text: string) {
  const lines = text.split(/\r?\n/);
  const rows = lines
    .filter((line) => /entregable|hito|entrega/i.test(line))
    .slice(0, 12)
    .map((line) => ({
      title: compactText(line, 220) ?? "Entregable detectado",
      dueDate: null,
      amountLinked: null,
      notes: null,
    }));

  return rows;
}

function inferPaymentMilestones(text: string) {
  const lines = text.split(/\r?\n/);
  const rows = lines
    .filter((line) => /pago|anticipo|estimacion|factura/i.test(line))
    .slice(0, 12)
    .map((line) => ({
      concept: compactText(line, 220) ?? "Hito de pago detectado",
      paymentDate: null,
      amount: null,
    }));

  return rows;
}

function inferRiskClauses(text: string): ContractRiskClause[] {
  const lines = text.split(/\r?\n/);
  const catalog: Array<{ key: string; title: string; riskLevel: "medium" | "high" }> = [
    { key: "penalizacion", title: "Penalizaciones economicas", riskLevel: "high" },
    { key: "rescision", title: "Causal de rescision", riskLevel: "high" },
    { key: "garantia", title: "Garantias y fianzas", riskLevel: "medium" },
    { key: "retencion", title: "Retencion de pagos", riskLevel: "high" },
    { key: "sancion", title: "Sanciones administrativas", riskLevel: "high" },
  ];

  const matches: ContractRiskClause[] = [];

  for (const item of catalog) {
    const foundLine = lines.find((line) => line.toLowerCase().includes(item.key));

    if (!foundLine) {
      continue;
    }

    matches.push({
      title: item.title,
      snippet: compactText(foundLine, 320) ?? item.title,
      riskLevel: item.riskLevel,
      requiresLegalValidation: true,
    });
  }

  return matches;
}

function buildRegexFallback(fullText: string): ContractAiExtractionOutput {
  const riskClauses = inferRiskClauses(fullText);

  return {
    fields: {
      title: inferTitle(fullText),
      counterpartyEntity: inferCounterparty(fullText),
      contractNumber: inferContractNumber(fullText),
      contractType: inferContractType(fullText),
      startDate: null,
      endDate: null,
      totalAmount: inferAmount(fullText),
      currency: "MXN",
      description: compactText(fullText, 800),
    },
    deliverables: inferDeliverables(fullText),
    paymentMilestones: inferPaymentMilestones(fullText),
    riskClauses,
    warnings: [
      {
        code: "CONTRACT_EXTRACTION_FALLBACK",
        message: "Extraccion parcial con heuristicas. Requiere validacion legal.",
      },
    ],
    engine: "regex_fallback",
    model: null,
    usage: null,
  };
}

function normalizeAiOutput(payload: z.infer<typeof ContractExtractionResponseSchema>): ContractAiExtractionOutput {
  const fallback = buildRegexFallback("");

  const fields = payload.fields ?? {};
  const deliverables = Array.isArray(payload.deliverables) ? payload.deliverables : [];
  const paymentMilestones = Array.isArray(payload.paymentMilestones) ? payload.paymentMilestones : [];
  const riskClauses = Array.isArray(payload.riskClauses) ? payload.riskClauses : [];

  return {
    fields: {
      title: compactText(fields.title ?? "", 220),
      counterpartyEntity: compactText(fields.counterpartyEntity ?? "", 220),
      contractNumber: compactText(fields.contractNumber ?? "", 120),
      contractType: compactText(fields.contractType ?? "", 120),
      startDate: normalizeIsoDate(fields.startDate),
      endDate: normalizeIsoDate(fields.endDate),
      totalAmount: normalizeAmount(fields.totalAmount),
      currency: compactText(fields.currency ?? "", 10) ?? "MXN",
      description: compactText(fields.description ?? "", 1200),
    },
    deliverables: deliverables
      .map((item) => ({
        title: compactText(item.title ?? "", 220),
        dueDate: normalizeIsoDate(item.dueDate),
        amountLinked: normalizeAmount(item.amountLinked),
        notes: compactText(item.notes ?? "", 500),
      }))
      .filter((item): item is { title: string; dueDate: string | null; amountLinked: number | null; notes: string | null } => Boolean(item.title)),
    paymentMilestones: paymentMilestones
      .map((item) => ({
        concept: compactText(item.concept ?? "", 220),
        paymentDate: normalizeIsoDate(item.paymentDate),
        amount: normalizeAmount(item.amount),
      }))
      .filter((item): item is { concept: string; paymentDate: string | null; amount: number | null } => Boolean(item.concept)),
    riskClauses: riskClauses
      .map((item) => ({
        title: compactText(item.title ?? "", 160),
        snippet: compactText(item.snippet ?? "", 320),
        riskLevel: item.riskLevel ?? "medium",
        requiresLegalValidation: true,
      }))
      .filter((item): item is ContractRiskClause => Boolean(item.title) && Boolean(item.snippet)),
    warnings: fallback.warnings,
    engine: "openai_json_object",
    model: null,
    usage: null,
  };
}

async function callOpenAi(fullText: string) {
  const apiKey = getOpenAiApiKey();

  if (!apiKey) {
    throw new Error("No se encontro API key para OpenAI.");
  }

  const model = process.env.OPENAI_CONTRACT_MODEL?.trim() || DEFAULT_OPENAI_MODEL;
  const timeoutMs = parsePositiveInteger(process.env.OPENAI_CONTRACT_TIMEOUT_MS, DEFAULT_TIMEOUT_MS);
  const baseUrl = (process.env.OPENAI_API_BASE_URL?.trim() || DEFAULT_OPENAI_BASE_URL).replace(/\/+$/, "");

  const systemPrompt = [
    "Eres analista juridico especializado en contratos publicos mexicanos.",
    "Responde unicamente JSON valido.",
    "Si no hay certeza en un campo usa null.",
    "Identifica riesgos contractuales y marca enfoque conservador.",
  ].join(" ");

  const userPrompt = [
    "Extrae campos del contrato y devuelve JSON con claves: fields, deliverables, paymentMilestones, riskClauses.",
    "fields: title,counterpartyEntity,contractNumber,contractType,startDate,endDate,totalAmount,currency,description.",
    "deliverables: [{title,dueDate,amountLinked,notes}]",
    "paymentMilestones: [{concept,paymentDate,amount}]",
    "riskClauses: [{title,snippet,riskLevel}]",
    "Texto:",
    clampText(fullText),
  ].join("\n\n");

  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const response = await fetch(`${baseUrl}/chat/completions`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${apiKey}`,
      },
      body: JSON.stringify({
        model,
        temperature: 0,
        response_format: {
          type: "json_object",
        },
        messages: [
          { role: "system", content: systemPrompt },
          { role: "user", content: userPrompt },
        ],
      }),
      signal: controller.signal,
    });

    const payload = (await response.json().catch(() => ({}))) as OpenAiChatCompletionResponse;

    if (!response.ok) {
      throw new Error(payload.error?.message ?? `OpenAI error ${response.status}`);
    }

    const content = payload.choices?.[0]?.message?.content;

    if (!content) {
      throw new Error("OpenAI no devolvio contenido util.");
    }

    const parsed = ContractExtractionResponseSchema.parse(extractJsonObject(content));
    const normalized = normalizeAiOutput(parsed);

    return {
      ...normalized,
      engine: "openai_json_object" as const,
      model: payload.model ?? model,
      usage: {
        promptTokens: payload.usage?.prompt_tokens ?? null,
        completionTokens: payload.usage?.completion_tokens ?? null,
        totalTokens: payload.usage?.total_tokens ?? null,
      },
      warnings: [
        {
          code: "LEGAL_VALIDATION_REQUIRED",
          message: "Resultado automatizado. Requiere validacion legal antes de uso formal.",
        },
      ],
    };
  } finally {
    clearTimeout(timer);
  }
}

export async function extractContractWithAi(fullText: string): Promise<ContractAiExtractionOutput> {
  const cleaned = fullText.trim();

  if (!cleaned) {
    return buildRegexFallback(fullText);
  }

  try {
    return await callOpenAi(cleaned);
  } catch (error) {
    const fallback = buildRegexFallback(cleaned);
    fallback.warnings.push({
      code: "OPENAI_CONTRACT_EXTRACTION_FAILED",
      message: error instanceof Error ? error.message : "No fue posible usar IA para extraer el contrato.",
    });
    return fallback;
  }
}
