"""
Agent pro extrakci a pojmenování naskenovaných PDF lékařských zpráv.
- Claude Vision API — bez OCR, správná čeština s diakritikou
- Ověření pacienta proti Medicus (KAR), fuzzy matching RČ
- Interaktivní schválení / oprava názvu
- Few-shot learning z uložených korekcí
"""

import base64
import gc
import io
import json
import os
import re
import shutil
import sys
import time
from pathlib import Path

# Windows: nastav stdout/stderr na UTF-8
if sys.platform == "win32":
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
    sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")

import anthropic
from pdf2image import convert_from_path

sys.path.insert(0, str(Path(__file__).parent.parent))
from Knihovny.najdi_dropbox import get_dropbox_root

POPPLER_PATH = r"C:/Poppler/Library/bin"
CORRECTIONS_FILE = Path(__file__).parent / "corrections.json"
_DROPBOX = Path(get_dropbox_root())
TO_PROCESS = _DROPBOX / r"Ordinace\Dokumentace_ke_zpracování\Ricoh Fi-8040\KeZpracování"
PROCESSED  = _DROPBOX / r"Ordinace\Dokumentace_ke_zpracování\Ricoh Fi-8040\Zpracováno"
DOKUMENTACE = _DROPBOX / r"Ordinace\Dokumentace_zpracovaná"


# ─── Konfigurace ──────────────────────────────────────────────────────────────

def _load_env():
    env_path = Path(__file__).parent.parent / ".env"
    if env_path.exists():
        for line in env_path.read_text(encoding="utf-8").splitlines():
            line = line.strip()
            if "=" in line and not line.startswith("#"):
                k, v = line.split("=", 1)
                os.environ[k.strip()] = v.strip()

_load_env()


# ─── Korekce (few-shot příklady) ──────────────────────────────────────────────

def load_corrections() -> list[dict]:
    if CORRECTIONS_FILE.exists():
        return json.loads(CORRECTIONS_FILE.read_text(encoding="utf-8"))
    return []

def save_correction(original: str, corrected: str):
    corrections = load_corrections()
    for c in corrections:
        if c["original"] == original and c["corrected"] == corrected:
            return
    corrections.append({"original": original, "corrected": corrected})
    CORRECTIONS_FILE.write_text(
        json.dumps(corrections, ensure_ascii=False, indent=2), encoding="utf-8"
    )
    print(f"  ✓ Korekce uložena ({len(corrections)} celkem)")

def build_corrections_prompt() -> str:
    corrections = load_corrections()
    if not corrections:
        return ""
    lines = ["Příklady korekcí z minulých běhů (uč se z nich):"]
    for c in corrections[-10:]:
        lines.append(f'  - špatně: "{c["original"]}"')
        lines.append(f'    správně: "{c["corrected"]}"')
    return "\n".join(lines) + "\n\n"


# ─── Kontrola duplicit ───────────────────────────────────────────────────────

def check_duplicates(rc: str, datum: str) -> list[str]:
    """
    Hledá v Dokumentace_zpracovaná soubory se stejným RČ a datem.
    Vrátí seznam názvů nalezených souborů.
    """
    if not DOKUMENTACE.exists():
        return []
    prefix = f"{rc} {datum}"
    return [f.name for f in DOKUMENTACE.iterdir() if f.name.startswith(prefix)]


# ─── Medicus ověření ──────────────────────────────────────────────────────────

def _medicus_connect():
    try:
        import fdb
        return fdb.connect(
            dsn=r"localhost:c:\medicus 3\data\medicus.fdb",
            user="SYSDBA", password="masterkey", charset="win1250"
        )
    except Exception as e:
        print(f"  [Medicus] Nepřipojeno: {e}")
        return None

def _lookup_by_rc(cur, rc_digits: str) -> dict | None:
    """Přesné vyhledání podle RČ (bez lomítka)."""
    cur.execute(
        "SELECT IDPAC, PRIJMENI, JMENO, RODCIS FROM KAR "
        "WHERE REPLACE(RODCIS, '/', '') = ?",
        (rc_digits,)
    )
    row = cur.fetchone()
    if row:
        return {"idpac": row[0], "prijmeni": row[1].strip(), "jmeno": row[2].strip(), "rodcis": row[3].strip()}
    return None

def _rc_candidates(rc: str) -> list[str]:
    """
    Generuje kandidáty RČ pro fuzzy matching (edit distance 1):
    - vynechání každé cifry (oprava extra znaku z OCR)
    - záměna podobně vypadajících číslic na každé pozici
    Vrátí unikátní seznam kandidátů bez původního RČ.
    """
    similar = {"0": "8", "8": "0", "1": "7", "7": "1", "5": "6", "6": "5", "3": "8"}
    candidates = set()

    # Vynechání jedné cifry (nejčastější OCR chyba — přebývající nula)
    for i in range(len(rc)):
        candidates.add(rc[:i] + rc[i+1:])

    # Záměna podobné cifry na každé pozici
    for i, ch in enumerate(rc):
        if ch in similar:
            candidates.add(rc[:i] + similar[ch] + rc[i+1:])

    candidates.discard(rc)
    return sorted(candidates)

def _rc_checksum_ok(rc: str) -> bool:
    """Ověří dělitelnost 11 pro 10místná RČ (platí pro narozené po 1.1.1954)."""
    digits = re.sub(r"\D", "", rc)
    if len(digits) == 10:
        return int(digits) % 11 == 0
    return True  # 9místná RČ nemají checksum

def verify_patient(rc_raw: str) -> dict:
    """
    Ověří pacienta v Medicus.
    Vrací:
      status: "ok" | "fuzzy" | "not_found" | "offline"
      patient: dict nebo None
      rc_corrected: opravené RČ (pokud fuzzy) nebo None
    """
    rc = re.sub(r"\D", "", rc_raw or "")
    if not rc:
        return {"status": "not_found", "patient": None, "rc_corrected": None}

    con = _medicus_connect()
    if con is None:
        return {"status": "offline", "patient": None, "rc_corrected": None}

    try:
        cur = con.cursor()

        # 1. Přesná shoda
        patient = _lookup_by_rc(cur, rc)
        if patient:
            return {"status": "ok", "patient": patient, "rc_corrected": None}

        # 2. Fuzzy matching — zkus kandidáty, preferuj ty s platným checksumem
        candidates = _rc_candidates(rc)
        matches = []
        for cand in candidates:
            p = _lookup_by_rc(cur, cand)
            if p:
                matches.append((cand, p))

        if not matches:
            return {"status": "not_found", "patient": None, "rc_corrected": None}

        # Seřaď: platný checksum na prvním místě
        matches.sort(key=lambda x: (0 if _rc_checksum_ok(x[0]) else 1))
        best_rc, best_patient = matches[0]
        return {"status": "fuzzy", "patient": best_patient, "rc_corrected": best_rc, "all_matches": matches}

    finally:
        con.close()


# ─── PDF → obrázek ────────────────────────────────────────────────────────────

def pdf_to_images(pdf_path: str) -> list:
    return convert_from_path(pdf_path, poppler_path=POPPLER_PATH, dpi=300)

def image_to_base64(image) -> str:
    buf = io.BytesIO()
    image.save(buf, format="JPEG", quality=95)
    return base64.standard_b64encode(buf.getvalue()).decode("utf-8")


# ─── Extrakce Claude Vision ───────────────────────────────────────────────────

def extract_patient_info(pdf_path: str) -> dict:
    pdf_path = Path(pdf_path)
    if not pdf_path.exists():
        raise FileNotFoundError(f"Soubor nenalezen: {pdf_path}")

    print(f"\nNačítám: {pdf_path.name}")
    suffix = pdf_path.suffix.lower()
    if suffix in (".jpg", ".jpeg", ".png"):
        from PIL import Image
        img = Image.open(pdf_path)
        image_b64 = image_to_base64(img)
        img.close()
    else:
        images = pdf_to_images(str(pdf_path))
        image_b64 = image_to_base64(images[0])
        del images
        gc.collect()

    prompt = (
        build_corrections_prompt() +
        "Toto je naskenovaná lékařská zpráva v češtině. "
        "Vrať JSON s těmito poli:\n"
        "- \"jmeno\": celé jméno pacienta (příjmení + jméno + případný titul)\n"
        "- \"rodne_cislo\": rodné číslo pacienta BEZ lomítka (pouze číslice)\n"
        "- \"datum_zpravy\": datum zprávy ve formátu YYYY-MM-DD\n"
        "- \"typ_dokumentu\": typ dokumentu — "
        "\"LZ {oddělení}\" = ambulantní/lékařská zpráva (např. \"LZ chirurgie\", \"LZ kardiologie\", \"LZ plicní\", \"LZ ORL\"); "
        "\"PZ {oddělení}\" = propouštěcí zpráva z hospitalizace (např. \"PZ interna\", \"PZ neurologie\"). "
        "Jiné typy: \"Laboratoř\", \"CT břicha\", \"MRI páteře\", \"kolonoskopie\", "
        "\"operační protokol oční\", \"poukaz FT\", \"diagnostická mamografie\" atd.\n"
        "- \"poznamka\": krátká klinická poznámka česky, max 80 znaků\n"
        "- \"nazev_souboru\": název souboru ve formátu "
        "\"{rodne_cislo} {datum_zpravy} {Příjmení}, {Jméno} [{typ_dokumentu}] [{poznamka}].pdf\" "
        "(jméno bez titulu, RČ bez lomítka)\n"
        "- \"rotace\": o kolik stupňů CCW je třeba otočit obrázek aby byl text čitelně na výšku nebo šířku "
        "(hodnoty: 0, 90, 180, 270). Pokud je text již správně orientovaný, vrať 0.\n\n"
        "Pokud pole nenajdeš, použij null. Nepiš nic jiného než JSON."
    )

    print("  Volám Claude Vision API...")
    client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=400,
        messages=[{
            "role": "user",
            "content": [
                {"type": "image", "source": {"type": "base64", "media_type": "image/jpeg", "data": image_b64}},
                {"type": "text", "text": prompt},
            ],
        }],
    )

    usage = response.usage
    cost_input = usage.input_tokens * 3 / 1_000_000
    cost_output = usage.output_tokens * 15 / 1_000_000
    print(f"  Tokeny: {usage.input_tokens} in + {usage.output_tokens} out = ${cost_input + cost_output:.4f}")

    raw = response.content[0].text.strip()
    if raw.startswith("```"):
        raw = raw.split("```")[1]
        if raw.startswith("json"):
            raw = raw[4:]
    try:
        return json.loads(raw.strip())
    except json.JSONDecodeError:
        print(f"  VAROVÁNÍ: nelze parsovat JSON: {raw!r}")
        return {"nazev_souboru": None, "raw": raw}


# ─── Interaktivní schválení ───────────────────────────────────────────────────

def sanitize_filename(name: str) -> str:
    return re.sub(r'[<>:"/\\|?*]', '', name)


def _rename_dialog(nazev: str, info_lines: list[str]) -> str | None:
    """
    Tkinter dialog pro schválení / opravu názvu souboru.
    Vrátí finální název (s .pdf) nebo None = přeskočit.
    """
    import tkinter as tk

    result = {"value": None}

    root = tk.Tk()
    root.tk.call("encoding", "system", "utf-8")
    root.title("Schválení názvu souboru")
    root.resizable(True, False)
    root.attributes("-topmost", True)

    pad = {"padx": 12, "pady": 6}

    # Informační sekce
    frame_info = tk.Frame(root, bg="#f0f0f0", bd=1, relief="sunken")
    frame_info.pack(fill="x", **pad)
    for line in info_lines:
        color = "#b00000" if line.startswith("⚠") else "#004080" if line.startswith("✓") else "#333"
        tk.Label(frame_info, text=line, anchor="w", bg="#f0f0f0",
                 fg=color, font=("Segoe UI", 10)).pack(fill="x", padx=8, pady=1)

    # Pole pro název (bez .pdf)
    tk.Label(root, text="Název souboru (bez .pdf):", anchor="w",
             font=("Segoe UI", 9, "bold")).pack(fill="x", padx=12, pady=(10, 2))

    nazev_bez = nazev[:-4] if nazev and nazev.endswith(".pdf") else (nazev or "")
    var = tk.StringVar(value=nazev_bez)
    entry = tk.Entry(root, textvariable=var, font=("Segoe UI", 10), width=90)
    entry.pack(fill="x", padx=12, pady=(0, 10))
    entry.icursor(tk.END)
    entry.focus_set()

    # Tlačítka
    frame_btn = tk.Frame(root)
    frame_btn.pack(pady=(0, 12))

    def schvalit(event=None):
        result["value"] = var.get().strip()
        root.destroy()

    def preskocit(event=None):
        result["value"] = None
        root.destroy()

    tk.Button(frame_btn, text="✓  Schválit  (Enter)", command=schvalit,
              bg="#2a7a2a", fg="white", font=("Segoe UI", 10, "bold"),
              padx=16, pady=6).pack(side="left", padx=8)
    tk.Button(frame_btn, text="✗  Přeskočit  (Esc)", command=preskocit,
              bg="#7a2a2a", fg="white", font=("Segoe UI", 10),
              padx=16, pady=6).pack(side="left", padx=8)

    root.bind("<Return>", schvalit)
    root.bind("<Escape>", preskocit)

    # Vystředit okno na obrazovce
    root.update_idletasks()
    w, h = root.winfo_width(), root.winfo_height()
    sw, sh = root.winfo_screenwidth(), root.winfo_screenheight()
    root.geometry(f"+{(sw - w) // 2}+{(sh - h) // 2}")

    root.mainloop()
    return result["value"]


def print_verification(verif: dict, rc_from_scan: str):
    """Vypíše výsledek ověření proti Medicus."""
    status = verif["status"]
    patient = verif.get("patient")

    if status == "ok":
        print(f"  ✓ Medicus: {patient['prijmeni']} {patient['jmeno']}  |  RČ {patient['rodcis']}")
    elif status == "fuzzy":
        rc_corr = verif["rc_corrected"]
        print(f"  ⚠ Medicus: RČ ze skenu '{rc_from_scan}' nenalezeno")
        print(f"    → Nalezen podobný pacient: {patient['prijmeni']} {patient['jmeno']}  |  RČ {patient['rodcis']}")
        print(f"    → Pravděpodobná oprava RČ: {rc_from_scan}  →  {rc_corr}  (OCR chyba)")
        if len(verif.get("all_matches", [])) > 1:
            print(f"    → Další shody: {[m[0] for m in verif['all_matches'][1:]]}")
    elif status == "not_found":
        print(f"  ✗ Medicus: RČ '{rc_from_scan}' nenalezeno ani při fuzzy hledání")
    elif status == "offline":
        print(f"  — Medicus: nedostupný (offline), ověření přeskočeno")


def interactive_rename(pdf_path: Path, info: dict, verif: dict) -> bool:
    """
    Otevře tkinter dialog pro schválení / opravu názvu.
    Schválený soubor přesune do Processed/ a smaže z ToProcess/.
    """
    rc = re.sub(r"\D", "", verif["patient"]["rodcis"] if verif.get("patient") else info.get("rodne_cislo") or "")
    datum = info.get("datum_zpravy") or ""
    duplicity = check_duplicates(rc, datum)

    # Oprava RČ při fuzzy matchi
    nazev = info.get("nazev_souboru")
    if verif["status"] == "fuzzy" and verif.get("rc_corrected") and nazev:
        rc_scan = re.sub(r"\D", "", info.get("rodne_cislo") or "")
        nazev = nazev.replace(rc_scan, verif["rc_corrected"], 1)
        print(f"  → Název aktualizován s opraveným RČ")

    # Sestavení info řádků pro dialog
    rc_from_scan = re.sub(r"\D", "", info.get("rodne_cislo") or "")
    status = verif["status"]
    patient = verif.get("patient")
    info_lines = []
    if status == "ok":
        info_lines.append(f"✓ Medicus: {patient['prijmeni']} {patient['jmeno']}  |  RČ {patient['rodcis']}")
    elif status == "fuzzy":
        info_lines.append(f"⚠ RČ ze skenu '{rc_from_scan}' → opraveno na {verif['rc_corrected']}")
        info_lines.append(f"  Pacient: {patient['prijmeni']} {patient['jmeno']}  |  RČ {patient['rodcis']}")
    elif status == "not_found":
        info_lines.append(f"✗ RČ '{rc_from_scan}' nenalezeno v Medicus")
    else:
        info_lines.append("— Medicus nedostupný (offline)")
    if duplicity:
        info_lines.append(f"⚠ DUPLICITA: {', '.join(duplicity)}")

    print()
    print("─" * 70)
    if nazev:
        print(f"  Navržený název:  {nazev}")
    print("  Otevírám dialog...")

    odpoved = _rename_dialog(nazev or "", info_lines)

    if odpoved is None:
        print("  Přeskočeno.")
        return False

    if not odpoved.endswith(".pdf"):
        odpoved += ".pdf"
    final_name = sanitize_filename(odpoved)

    if nazev and nazev != final_name:
        save_correction(nazev, final_name)

    if not final_name or final_name == ".pdf":
        print("  Název je prázdný, přeskakuji.")
        return False

    dest = PROCESSED / final_name
    if dest.exists():
        print(f"  VAROVÁNÍ: '{final_name}' již existuje v Processed, přeskakuji.")
        return False

    for attempt in range(5):
        try:
            if pdf_path.suffix.lower() in (".jpg", ".jpeg", ".png"):
                from jpg_to_pdf import image_to_pdf
                image_to_pdf(pdf_path, dest, rotate_ccw=info.get("rotace") or 0)
            else:
                shutil.copy2(pdf_path, dest)
            break
        except PermissionError:
            if attempt < 4:
                time.sleep(0.5)
            else:
                raise

    pdf_path.unlink()
    print(f"  ✓ Uloženo: Processed/{final_name}")
    return True


# ─── Hlavní logika ────────────────────────────────────────────────────────────

def process_file(pdf_path: Path):
    info = extract_patient_info(str(pdf_path))

    rc_from_scan = re.sub(r"\D", "", info.get("rodne_cislo") or "")
    print(f"  Ověřuji v Medicus (RČ: {rc_from_scan})...")
    verif = verify_patient(rc_from_scan)
    print_verification(verif, rc_from_scan)

    interactive_rename(pdf_path, info, verif)

def process_folder(folder: Path):
    pdf_files = sorted(f for f in folder.iterdir()
                       if f.suffix.lower() in (".pdf", ".jpg", ".jpeg", ".png"))
    if not pdf_files:
        print(f"Žádná PDF nenalezena v: {folder}")
        return

    print(f"Nalezeno {len(pdf_files)} PDF soubor(ů).\n")
    for pdf_file in pdf_files:
        try:
            process_file(pdf_file)
        except Exception as e:
            print(f"  CHYBA: {e}")

    print("\nHotovo.")


if __name__ == "__main__":
    if len(sys.argv) > 1:
        target = Path(sys.argv[1])
    else:
        target = TO_PROCESS

    PROCESSED.mkdir(exist_ok=True)
    TO_PROCESS.mkdir(exist_ok=True)

    if target.is_file() and target.suffix.lower() in (".pdf", ".jpg", ".jpeg", ".png"):
        process_file(target)
    elif target.is_dir():
        process_folder(target)
    else:
        print("Použití: python extract_patient_info.py [soubor.pdf nebo složka]")
        sys.exit(1)
