import { runJsonChatCompletion } from "./tenant-ai.js";

export const PASSPORT_NUMBER_AGENT_PROMPT = `Você é um agente especialista em leitura e interpretação de documentos oficiais, com foco em passaportes internacionais.

Sua tarefa é identificar corretamente o NÚMERO DO PASSAPORTE (passport number), evitando confundir com outros números presentes no documento.

# OBJETIVO

Extrair exclusivamente o número do passaporte com alta precisão, mesmo quando o documento contém múltiplos números.

# CONTEXTO IMPORTANTE

Um passaporte pode conter vários números, incluindo:
- passport number (número do passaporte) ✅
- personal number
- document number (às vezes sinônimo, mas nem sempre)
- control number
- MRZ (machine readable zone)
- visa numbers
- serial numbers internos
- códigos de barras

Você deve diferenciar corretamente esses elementos.

# REGRAS PARA IDENTIFICAR O NÚMERO DO PASSAPORTE

## 1. PRIORIDADE MÁXIMA: LABEL EXPLÍCITO

Se houver rótulos como:
- "Passport No"
- "Passport Number"
- "Document No"
- "No. du passeport"
- "Número de pasaporte"

→ esses têm prioridade máxima

## 2. LOCALIZAÇÃO NO DOCUMENTO

O número do passaporte geralmente aparece:
- na página principal (biographical page)
- próximo ao nome ou nacionalidade
- no topo direito ou topo central

## 3. PADRÕES COMUNS

O número do passaporte geralmente:
- é alfanumérico
- entre 6 e 9 caracteres (varia por país)
- não contém espaços
- pode começar com letras

Exemplo:
- USA: 9 dígitos numéricos
- Brasil: 2 letras + 6 números (ex: AB123456)

## 4. MRZ (ZONA INFERIOR)

O MRZ contém o número do passaporte, mas:
- aparece em formato comprimido
- pode conter caracteres de preenchimento "<"
- não deve ser usado como primeira escolha se o número estiver visível acima

Use MRZ apenas como:
- validação
- fallback

## 5. EVITAR FALSOS POSITIVOS

NÃO confundir com:
- datas (DOB, expiry)
- número de controle
- número de visto
- número de identificação nacional
- códigos longos ou muito curtos
- sequências com muitos símbolos especiais

## 6. CONSISTÊNCIA

Se múltiplos candidatos forem encontrados:
- comparar valores
- validar formato
- verificar repetição no MRZ
- escolher o mais consistente

# PROCESSO DE EXTRAÇÃO

1. Identificar todos os números candidatos
2. Classificar cada um com base em:
   - label
   - posição
   - formato
   - contexto
3. Atribuir score de confiança
4. Selecionar o melhor candidato
5. Validar com MRZ (se disponível)

# FORMATO DE SAÍDA

Retorne JSON estruturado:

{
  "passport_number": "...",
  "confidence_score": 0.0-1.0,
  "status": "confirmed | inferred | uncertain",
  "source": {
    "label_detected": "...",
    "position_hint": "...",
    "extracted_from": "visual | OCR | MRZ"
  },
  "alternative_candidates": [
    {
      "value": "...",
      "reason_rejected": "..."
    }
  ],
  "rationale": "Explique por que este foi escolhido como número do passaporte"
}

# REGRAS CRÍTICAS

- Nunca retornar um número sem justificar
- Nunca assumir sem evidência contextual
- Se houver dúvida → retornar "uncertain"
- Sempre listar candidatos rejeitados quando houver ambiguidade

# OBJETIVO FINAL

Maximizar precisão, não quantidade.

Se necessário, prefira não extrair ao invés de extrair errado.`;

type PassportNumberAgentStatus = "confirmed" | "inferred" | "uncertain";

type PassportNumberAgentRawResponse = {
  passport_number?: unknown;
  confidence_score?: unknown;
  status?: unknown;
  source?: {
    label_detected?: unknown;
    position_hint?: unknown;
    extracted_from?: unknown;
  } | null;
  alternative_candidates?: Array<{
    value?: unknown;
    reason_rejected?: unknown;
  }> | null;
  rationale?: unknown;
} | null;

export type PassportNumberAgentResult = {
  passportNumber: string | null;
  confidenceScore: number;
  status: PassportNumberAgentStatus;
  source: {
    labelDetected: string | null;
    positionHint: string | null;
    extractedFrom: "visual" | "OCR" | "MRZ" | null;
  };
  alternativeCandidates: Array<{
    value: string;
    reasonRejected: string | null;
  }>;
  rationale: string | null;
  accepted: boolean;
  usage: {
    inputTokens: number;
    outputTokens: number;
    totalTokens: number;
  };
  model: string | null;
};

function limitPassportAgentText(value: string, maxLength = 18000) {
  const normalized = String(value ?? "").trim();
  if (normalized.length <= maxLength) {
    return normalized;
  }

  return `${normalized.slice(0, maxLength)}\n...[truncated]`;
}

export function normalizePassportNumberCandidate(value: string | null | undefined) {
  const normalized = String(value ?? "")
    .toUpperCase()
    .replace(/\s+/g, "")
    .replace(/<+/g, "")
    .replace(/[^A-Z0-9-]/g, "")
    .trim();

  return normalized || null;
}

export function isValidPassportNumberCandidate(value: string | null | undefined) {
  const normalized = normalizePassportNumberCandidate(value);
  if (!normalized) {
    return false;
  }

  if (normalized.length < 6 || normalized.length > 12) {
    return false;
  }

  if (!/[A-Z]/.test(normalized) && !/\d/.test(normalized)) {
    return false;
  }

  if (/^(PASSPORT|DOCUMENT|NUMBER|UNKNOWN|UNCERTAIN|N\/A)$/i.test(normalized)) {
    return false;
  }

  if (/^\d{2,4}[-/]\d{2}[-/]\d{2,4}$/.test(normalized)) {
    return false;
  }

  return /^[A-Z0-9-]+$/.test(normalized);
}

export function isPassportReaderCandidateDocument(input: {
  documentTypeCode?: string | null;
  title?: string | null;
  originalFileName?: string | null;
  textContent?: string | null;
}) {
  const normalizedDocumentType = String(input.documentTypeCode ?? "").trim().toLowerCase();
  const haystack = `${input.title ?? ""} ${input.originalFileName ?? ""} ${input.textContent ?? ""}`
    .normalize("NFD")
    .replace(/\p{Diacritic}/gu, "")
    .toLowerCase();

  if (normalizedDocumentType === "passport") {
    return true;
  }

  if (
    /\bpassport\b/.test(haystack) ||
    /\bpassaporte\b/.test(haystack) ||
    /\btravel document\b/.test(haystack)
  ) {
    return true;
  }

  return /\bP<[A-Z<]{3}/.test(haystack) || /\bpassport no\b/.test(haystack);
}

export function parsePassportNumberAgentResponse(
  payload: PassportNumberAgentRawResponse,
): Omit<PassportNumberAgentResult, "usage" | "model"> {
  const normalizedStatus = String(payload?.status ?? "").trim().toLowerCase();
  const status: PassportNumberAgentStatus =
    normalizedStatus === "confirmed" || normalizedStatus === "inferred"
      ? normalizedStatus
      : "uncertain";
  const passportNumber = normalizePassportNumberCandidate(
    typeof payload?.passport_number === "string" ? payload.passport_number : null,
  );
  const rawConfidence = Number(payload?.confidence_score ?? 0);
  const confidenceScore =
    Number.isFinite(rawConfidence) && rawConfidence >= 0
      ? Math.min(Math.max(rawConfidence, 0), 1)
      : 0;
  const extractedFromRaw = String(payload?.source?.extracted_from ?? "").trim().toLowerCase();
  const extractedFrom =
    extractedFromRaw === "visual" ? "visual"
    : extractedFromRaw === "ocr" ? "OCR"
    : extractedFromRaw === "mrz" ? "MRZ"
    : null;
  const alternativeCandidates = Array.isArray(payload?.alternative_candidates)
    ? payload!.alternative_candidates!
        .map((item) => {
          const value = normalizePassportNumberCandidate(
            typeof item?.value === "string" ? item.value : null,
          );
          if (!value) {
            return null;
          }

          return {
            value,
            reasonRejected:
              typeof item?.reason_rejected === "string" ? item.reason_rejected.trim() || null : null,
          };
        })
        .filter(
          (item): item is { value: string; reasonRejected: string | null } => item !== null,
        )
        .slice(0, 12)
    : [];
  const rationale =
    typeof payload?.rationale === "string" ? payload.rationale.trim() || null : null;
  const accepted =
    Boolean(passportNumber) &&
    isValidPassportNumberCandidate(passportNumber) &&
    (status === "confirmed" || (status === "inferred" && confidenceScore >= 0.8));

  return {
    passportNumber: accepted ? passportNumber : null,
    confidenceScore,
    status,
    source: {
      labelDetected:
        typeof payload?.source?.label_detected === "string"
          ? payload.source.label_detected.trim() || null
          : null,
      positionHint:
        typeof payload?.source?.position_hint === "string"
          ? payload.source.position_hint.trim() || null
          : null,
      extractedFrom,
    },
    alternativeCandidates,
    rationale,
    accepted,
  };
}

export async function extractPassportNumberWithAgent(input: {
  lawFirmId: string;
  documentTypeCode?: string | null;
  title?: string | null;
  originalFileName?: string | null;
  mimeType?: string | null;
  textContent: string;
}) {
  const completion = await runJsonChatCompletion({
    lawFirmId: input.lawFirmId,
    systemPrompt: PASSPORT_NUMBER_AGENT_PROMPT,
    userPrompt: JSON.stringify({
      document: {
        documentTypeCode: input.documentTypeCode ?? null,
        title: input.title ?? null,
        originalFileName: input.originalFileName ?? null,
        mimeType: input.mimeType ?? null,
      },
      textContent: limitPassportAgentText(input.textContent, 22000),
    }),
    maxCompletionTokens: 900,
  });

  return {
    ...parsePassportNumberAgentResponse(completion.json as PassportNumberAgentRawResponse),
    usage: completion.usage,
    model: completion.model ?? null,
  } satisfies PassportNumberAgentResult;
}
