"""Talks to an LLM via OpenRouter to read medical documents (images/PDF).

OpenRouter gives access to many providers through one API key/bill, so the
model can be swapped for whatever is currently the cheapest capable option
(OPENROUTER_MODEL) instead of being locked into one provider's pricing.

Shared hosting without root access can't install a system OCR binary
(Tesseract), so we still send the document straight to the model instead of
running OCR ourselves. For PDFs specifically, OpenRouter's default behavior
only extracts an *existing* text layer — useless for a scanned/photographed
prescription with no text layer — so we explicitly request the "native" PDF
engine, which hands the file to a model that can read it directly (Gemini,
Claude, etc. all support this through OpenRouter).
"""
import base64
import json

from flask import current_app
from openai import OpenAI

from app.models import CONDITION_CATEGORIES

_CATEGORY_LIST = ", ".join(f'"{c}"' for c in CONDITION_CATEGORIES)

EXTRACTION_SYSTEM_PROMPT = f"""Eres un asistente clínico que extrae información de documentos \
médicos (recetas, órdenes de laboratorio, epicrisis, resultados de exámenes, etc.).

Devuelve EXCLUSIVAMENTE un JSON válido (sin texto adicional, sin markdown) con este esquema:
{{
  "events": [
    {{
      "event_type": "diagnosis" | "medication" | "allergy" | "lab_result" | "procedure" | "vaccination" | "other",
      "description": "descripción breve y clara en español",
      "event_date": "YYYY-MM-DD" | null,
      "condition_category": una de [{_CATEGORY_LIST}] o null,
      "doctor_name": "nombre completo del médico que firma/prescribe, si aparece" | null,
      "doctor_license": "número de matrícula/licencia del médico, si aparece" | null,
      "health_entity_name": "nombre de la clínica, hospital, EPS/aseguradora o laboratorio emisor, si aparece" | null,
      "medication_generic_name": "principio activo o nombre genérico del medicamento (solo si event_type es medication)" | null,
      "medication_brand_name": "nombre comercial del medicamento, si aparece" | null,
      "medication_manufacturer": "laboratorio fabricante, si aparece en el documento" | null
    }}
  ]
}}

Reglas:
- Usa solo información que esté explícitamente en el documento. No inventes datos ni nombres.
- "condition_category" solo aplica a eventos relacionados con un diagnóstico o condición de salud;
  si el evento es un medicamento, resultado de laboratorio, etc. sin diagnóstico asociado, usa null.
- Los campos de médico, entidad de salud y medicamento van vacíos (null) si el documento no los menciona.
- Si el documento no es legible o no contiene información médica útil, devuelve {{"events": []}}.
- Sé conciso: cada "description" debe ser una frase, no un párrafo.
"""

SUMMARY_SYSTEM_PROMPT = """Eres un asistente clínico que redacta resúmenes de historia \
médica para pacientes, en español, claros y fáciles de entender (sin tecnicismos \
innecesarios). Organiza el resumen en secciones cortas: Diagnósticos activos, \
Medicamentos actuales, Alergias, Antecedentes relevantes (procedimientos, hospitalizaciones), \
Vacunas, y Exámenes recientes. Si una sección no tiene datos, omítela. Máximo 300 palabras."""


def _client() -> OpenAI:
    api_key = current_app.config["OPENROUTER_API_KEY"]
    if not api_key:
        raise RuntimeError("OPENROUTER_API_KEY no está configurada.")
    return OpenAI(
        base_url="https://openrouter.ai/api/v1",
        api_key=api_key,
        default_headers={
            "HTTP-Referer": current_app.config.get("OPENROUTER_SITE_URL") or "https://memodoc.example",
            "X-Title": "Memodoc",
        },
    )


def _document_content_part(file_path: str, mime_type: str) -> dict:
    with open(file_path, "rb") as f:
        data = base64.standard_b64encode(f.read()).decode("utf-8")

    if mime_type == "application/pdf":
        return {
            "type": "file",
            "file": {"filename": "documento.pdf", "file_data": f"data:{mime_type};base64,{data}"},
        }
    return {"type": "image_url", "image_url": {"url": f"data:{mime_type};base64,{data}"}}


def extract_medical_data(file_path: str, mime_type: str) -> dict:
    """Sends one document to the configured model and returns {"events": [...]}."""
    client = _client()
    model = current_app.config["OPENROUTER_MODEL"]

    extra_body = {}
    if mime_type == "application/pdf":
        extra_body["plugins"] = [{"id": "file-parser", "pdf": {"engine": "native"}}]

    completion = client.chat.completions.create(
        model=model,
        max_tokens=2000,
        messages=[
            {"role": "system", "content": EXTRACTION_SYSTEM_PROMPT},
            {
                "role": "user",
                "content": [
                    _document_content_part(file_path, mime_type),
                    {
                        "type": "text",
                        "text": "Extrae los eventos médicos de este documento siguiendo el esquema indicado.",
                    },
                ],
            },
        ],
        extra_body=extra_body,
    )

    raw_text = completion.choices[0].message.content or ""
    return _parse_json_response(raw_text)


def generate_summary(patient_name: str, age, events: list[dict]) -> str:
    """events: list of dicts with event_type, description, event_date (as strings)."""
    client = _client()
    model = current_app.config["OPENROUTER_MODEL"]

    events_text = json.dumps(events, ensure_ascii=False, indent=2)
    age_text = f"{age} años" if age is not None else "edad desconocida"

    completion = client.chat.completions.create(
        model=model,
        max_tokens=1000,
        messages=[
            {"role": "system", "content": SUMMARY_SYSTEM_PROMPT},
            {
                "role": "user",
                "content": (
                    f"Paciente: {patient_name} ({age_text}).\n\n"
                    f"Eventos médicos registrados (JSON):\n{events_text}\n\n"
                    "Redacta el resumen de historia médica siguiendo las instrucciones del sistema."
                ),
            },
        ],
    )

    return (completion.choices[0].message.content or "").strip()


def _parse_json_response(raw_text: str) -> dict:
    raw_text = raw_text.strip()
    # Some models wrap JSON in ```json fences despite instructions; strip defensively.
    if raw_text.startswith("```"):
        raw_text = raw_text.strip("`")
        if raw_text.lower().startswith("json"):
            raw_text = raw_text[4:]
        raw_text = raw_text.strip()

    data = json.loads(raw_text)
    if "events" not in data or not isinstance(data["events"], list):
        raise ValueError("Respuesta del modelo sin la clave 'events' esperada.")
    return data
