"""LLM-based contract analysis against a user-supplied checklist."""
import io
import json
import logging
import os
from pathlib import Path

import fitz  # PyMuPDF
from PIL import Image
import pytesseract
from openai import AsyncOpenAI

logger = logging.getLogger(__name__)

_client: AsyncOpenAI | None = None


def _get_client() -> AsyncOpenAI:
    global _client
    if _client is None:
        _client = AsyncOpenAI(
            base_url=os.getenv("LITELLM_BASE_URL", "http://host.docker.internal:4000/v1"),
            api_key=os.getenv("LITELLM_API_KEY", "sk-dummy"),
        )
    return _client


MODEL = os.getenv("LLM_MODEL", "anthropic/claude-sonnet-4-20250514")


SYSTEM_PROMPT = """Jste expert na české obchodní právo a pomáháte podnikateli posoudit smlouvu, kterou má podepsat.

Vaším úkolem je analyzovat smlouvu z pohledu PŘÍJEMCE smlouvy (toho, kdo ji má podepsat), nikoliv toho, kdo ji sepsal. Hledáte rizika, nevýhodná ustanovení a věci, na které by si měl dát příjemce pozor.

Text smlouvy obdržíte rozdělený podle stran — každá strana začíná značkou "--- Strana N ---". U každého citátu MUSÍTE uvést, na které straně se nachází.

Pro každý bod kontrolního seznamu posoudíte smlouvu a vrátíte JSON s následující strukturou:

{
  "items": [
    {
      "id": "id_polozky",
      "status": "ok" | "warning" | "problem" | "missing",
      "title": "Krátký nadpis nálezu (3-7 slov)",
      "summary": "1-3 věty vysvětlující nález z pohledu příjemce smlouvy",
      "excerpts": [
        {
          "text": "Přesný citát ze smlouvy (krátký, 5-15 slov, ideálně dohledatelný v PDF)",
          "comment": "Stručný komentář k úryvku — co znamená / proč je to relevantní",
          "page": <číslo strany, kde se citát nachází (celé číslo)>
        }
      ]
    }
  ],
  "overall_summary": "Celkové shrnutí 3-5 vět: hlavní rizika a hlavní výhody. ŽÁDNÁ doporučení co dělat.",
  "risk_level": "low" | "medium" | "high"
}

Pravidla:
- Status "ok" = ustanovení je vyvážené a bezpečné pro příjemce
- Status "warning" = stojí za pozornost, ne nutně problém
- Status "problem" = jasně nevýhodné nebo riskantní pro příjemce
- Status "missing" = bod se ve smlouvě nepokrývá (chybí ustanovení)
- Excerpts musí být DOSLOVNÉ citáty ze smlouvy (kvůli zvýraznění v PDF). Pokud nelze citovat (např. status=missing), vraťte prázdné pole [].
- KAŽDÝ citát MUSÍ obsahovat číslo strany ("page"). Stranu poznáte podle značky "--- Strana N ---" před textem.
- NEDÁVEJTE doporučení co s nálezem dělat — uživatel sám ví, co bude řešit. Pouze konstatujte fakta.
- Komentáře piště česky, věcně a stručně.
- Nevymýšlejte si — pokud něco ve smlouvě není, řekněte to.
- Vraťte POUZE JSON, žádný markdown, žádný text okolo."""


async def analyze_contract(pdf_path: Path, checklist_items: list[dict]) -> dict:
    """Run LLM analysis on a contract PDF against the given checklist."""
    text, used_ocr = _extract_text(pdf_path)
    if not text.strip():
        raise RuntimeError(
            "Z PDF se nepodařilo získat žádný text — ani standardním "
            "způsobem ani OCR. Zkuste lepší kvalitu skenu nebo PDF s textovou vrstvou."
        )

    checklist_block = "\n".join(
        f"- id={it['id']}: {it['label']} — {it.get('hint', '')}"
        for it in checklist_items
    )

    user_msg = f"""KONTROLNÍ SEZNAM (co posoudit):
{checklist_block}

TEXT SMLOUVY:
{text}

Vraťte JSON s analýzou každé položky kontrolního seznamu."""

    resp = await _get_client().chat.completions.create(
        model=MODEL,
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": user_msg},
        ],
        temperature=0.1,
        max_tokens=8000,
    )
    raw = (resp.choices[0].message.content or "").strip()
    logger.info("Analysis raw length: %d chars", len(raw))
    # Strip markdown fences if model wrapped it anyway
    raw = raw.removeprefix("```json").removeprefix("```").removesuffix("```").strip()
    try:
        data = json.loads(raw)
    except json.JSONDecodeError as e:
        logger.error("JSON parse failed: %s\n%s", e, raw[:1000])
        raise RuntimeError(f"Nepodařilo se zpracovat odpověď AI: {e}")
    # Tell the caller whether we had to OCR — UI can show a notice and
    # we'll skip in-page highlighting since the original PDF has no text layer.
    data["_used_ocr"] = used_ocr
    return data


def _extract_text(pdf_path: Path) -> tuple[str, bool]:
    """Extract text from PDF. Falls back to OCR if the PDF has no text layer.

    Returns (text, used_ocr).
    """
    doc = fitz.open(str(pdf_path))
    parts = []
    total_chars = 0
    for i, page in enumerate(doc):
        t = page.get_text()
        parts.append(f"--- Strana {i + 1} ---\n{t}")
        total_chars += len(t.strip())
    # Heuristic: <20 chars per page on average → looks like a scan
    needs_ocr = doc.page_count > 0 and (total_chars / max(doc.page_count, 1)) < 20
    if not needs_ocr:
        doc.close()
        return "\n\n".join(parts), False

    logger.info("PDF appears to be scanned (~%d chars across %d pages) — running OCR",
                total_chars, doc.page_count)
    parts = []
    for i, page in enumerate(doc):
        # Rasterize page at 250 DPI for OCR quality
        pix = page.get_pixmap(dpi=250, alpha=False)
        img = Image.frombytes("RGB", (pix.width, pix.height), pix.samples)
        try:
            ocr_text = pytesseract.image_to_string(img, lang="ces+eng")
        except pytesseract.TesseractError as e:
            logger.error("Tesseract failed on page %d: %s", i + 1, e)
            ocr_text = ""
        parts.append(f"--- Strana {i + 1} ---\n{ocr_text}")
    doc.close()
    return "\n\n".join(parts), True
