"""Tarifname/istemler metinlerinden bahsedilen şekilleri çıkarma.

Akış:
  1. Regex ile "Şekil N" + "(NNN)" referansları yakala (unique liste)
  2. Tespit edilen her şekil için Claude Haiku'ya bağlamı gönder, şu yanıtları iste:
     - title: kısa başlık ("Sistem mimarisi")
     - description: 1-2 cümle açıklama
     - mermaid_suggestion: başlangıç noktası Mermaid kaynağı (boş olabilir)

Tek LLM çağrısı, structured output, ~$0.005/patent.
"""

from __future__ import annotations

import io
import re

from anthropic import APIError
from fastapi import APIRouter, File, HTTPException, UploadFile
from fastapi.responses import Response
from PIL import Image, ImageOps
from pydantic import BaseModel, Field

from app.services.llm import (
    LLMNotConfiguredError,
    get_anthropic_client,
)
from app.services.llm.usage_log import record_usage

router = APIRouter(prefix="/figures", tags=["figures"])

# "Şekil 1", "Şekil 2-A", "ŞEKİL 3" gibi varyasyonları yakala
_FIG_PATTERN = re.compile(
    r"""
    \b
    (?: Ş[eE][kK][iİ][lL] | ŞEKİL | FIG\.? | Figure )   # "Şekil" / "FIG" varyasyonları
    \s*
    (\d{1,2})                                           # numara 1-99
    (?: [-–][A-Za-z] )?                                 # opsiyonel "-A", "-B"
    \b
    """,
    re.VERBOSE | re.UNICODE,
)


class ExtractFiguresRequest(BaseModel):
    description: str = Field(
        default="",
        max_length=100_000,
        description="Tarifname taslağı (markdown)",
    )
    claims: str = Field(
        default="",
        max_length=50_000,
        description="İstemler tam metin (numaralı liste)",
    )
    title: str | None = Field(default=None, max_length=200)


class FigureSuggestion(BaseModel):
    number: int = Field(..., ge=1, le=99)
    title: str = Field(..., min_length=3, max_length=100)
    description: str = Field(..., min_length=10, max_length=500)
    mermaid_suggestion: str = Field(default="", max_length=4000)


class FiguresSuggestionSet(BaseModel):
    figures: list[FigureSuggestion] = Field(..., min_length=0, max_length=20)


class ExtractFiguresResponse(BaseModel):
    figures: list[FigureSuggestion]
    detected_numbers: list[int]
    source: str = Field(description="'ai' veya 'regex-only' veya 'fallback'")
    model: str | None = None


def _extract_figure_numbers(*texts: str) -> list[int]:
    """Tüm metinlerden benzersiz şekil numaralarını sıralı çıkar."""
    seen: set[int] = set()
    for text in texts:
        if not text:
            continue
        for m in _FIG_PATTERN.finditer(text):
            try:
                n = int(m.group(1))
                if 1 <= n <= 99:
                    seen.add(n)
            except (ValueError, IndexError):
                continue
    return sorted(seen)


SYSTEM_PROMPT = """Sen bir patent vekili asistanısın. Görevin: bir Türk \
patent başvurusunun tarifname ve istem metinlerinden, bahsedilen her \
şekil için kısa bir başlık, 1-2 cümle açıklama ve Mermaid diyagram \
önerisi üretmek.

KURAL­LAR:
1. Tarifnamede hangi numaralı şekiller bahsediliyorsa yalnızca onları \
ver. Uydurma.
2. Her şekil için:
   - title: 3-8 kelimelik Türkçe başlık (örn. "Sistem mimarisi", "İşlem akışı")
   - description: şeklin NE GÖSTERDİĞİNİ 1-2 cümleyle anlatan Türkçe özet
   - mermaid_suggestion: başlangıç noktası Mermaid kaynağı (flowchart TD, \
sequenceDiagram, classDiagram, vb.) — boş da bırakabilirsin, ama olabilirse \
koy (4-10 node yeter)
3. Mermaid blok'u içinde emoji, renk, stil YOK. Default mermaid siyah-beyaz \
render edilecek.
4. Referans numaraları (Şekil 1'deki (210) gibi) Mermaid'de "Kapı (210)" \
formatında parentheses ile verilir.

Çıktı: yalnızca structured JSON.
"""


@router.post("/extract")
async def extract_figures(req: ExtractFiguresRequest) -> ExtractFiguresResponse:
    """Metinlerden bahsedilen şekillerin listesini üret."""
    detected = _extract_figure_numbers(req.description, req.claims)

    if not detected:
        return ExtractFiguresResponse(
            figures=[],
            detected_numbers=[],
            source="regex-only",
        )

    try:
        client = get_anthropic_client()
    except LLMNotConfiguredError:
        # Fallback: en azından numaraları döndür, title/description boş
        return ExtractFiguresResponse(
            figures=[
                FigureSuggestion(
                    number=n,
                    title=f"Şekil {n}",
                    description="Açıklama AI olmadan üretilemedi.",
                    mermaid_suggestion="",
                )
                for n in detected
            ],
            detected_numbers=detected,
            source="fallback",
        )

    # User message — regex ile bulduğumuz numaraları + metinleri ver
    nums_str = ", ".join(f"Şekil {n}" for n in detected)
    ctx = f"""Tespit edilen şekil numaraları: {nums_str}

## Tarifname metni
{req.description[:40000] if req.description else '(boş)'}

## İstemler
{req.claims[:20000] if req.claims else '(boş)'}

Yukarıdaki metinlerde yalnızca şu numaralı şekiller bahsedilmiştir: {nums_str}.
Her biri için title, description, mermaid_suggestion üret.
"""

    try:
        resp = await client.messages.parse(
            model="claude-haiku-4-5",
            max_tokens=4000,
            system=[
                {
                    "type": "text",
                    "text": SYSTEM_PROMPT,
                    "cache_control": {"type": "ephemeral"},
                }
            ],
            messages=[{"role": "user", "content": ctx}],
            output_format=FiguresSuggestionSet,
            thinking={"type": "disabled"},
        )
        record_usage(
            model="claude-haiku-4-5",
            usage=resp.usage,
            endpoint="figure_extraction",
        )
        parsed: FiguresSuggestionSet = resp.parsed_output  # type: ignore[assignment]
        # Claude detected dışı numara uydurmuş olabilir — filtrele
        detected_set = set(detected)
        filtered = [f for f in parsed.figures if f.number in detected_set]
        # Eksik olanları otomatik doldur
        present_numbers = {f.number for f in filtered}
        for n in detected:
            if n not in present_numbers:
                filtered.append(
                    FigureSuggestion(
                        number=n,
                        title=f"Şekil {n}",
                        description="Bu şekil tarifnamede bahsedilmiş ama AI detay üretmedi.",
                        mermaid_suggestion="",
                    )
                )
        filtered.sort(key=lambda f: f.number)

        return ExtractFiguresResponse(
            figures=filtered,
            detected_numbers=detected,
            source="ai",
            model="claude-haiku-4-5",
        )
    except APIError as e:
        raise HTTPException(status_code=502, detail=f"Anthropic API error: {e}") from e
    except Exception as e:  # noqa: BLE001
        raise HTTPException(status_code=500, detail=f"Figure extraction failed: {e}") from e


# Maksimum yüklenen görsel boyutu — TürkPatent A4 şekil için 2400×3000px yeter
_MAX_DIMENSION = 3000
# B/W threshold — ortalama 128/255 (rengi kaybetmeden siyah-beyaz ayıklaması)
_BW_THRESHOLD = 140


@router.post("/convert-bw")
async def convert_to_bw(file: UploadFile = File(...)) -> Response:
    """Yüklenen PNG/JPG'yi TürkPatent uyumlu siyah-beyaza çevir ve PNG olarak dön.

    İşlem:
      1. Görseli yükle, EXIF rotasyonunu normalize et
      2. Grayscale'a çevir
      3. Threshold ile pure B/W (1-bit değil — 8-bit gray, ama sadece 0 ve 255)
         Not: Pure 1-bit render sonucu TürkPatent için kabul edilir ama metin
         kenarlarında aliasing olur. Grayscale threshold daha temiz.
      4. Boyut sınırı — çok büyükse oransal küçült
      5. PNG olarak byte stream döndür
    """
    if not file.content_type or not file.content_type.startswith("image/"):
        raise HTTPException(status_code=400, detail="Geçersiz dosya tipi")

    raw = await file.read()
    if len(raw) > 5 * 1024 * 1024:  # Laravel tarafında 2MB limit var, biz 5 kabul
        raise HTTPException(status_code=413, detail="Dosya 5 MB üzerinde")

    try:
        img = Image.open(io.BytesIO(raw))
        img = ImageOps.exif_transpose(img)  # telefon fotoğraflarındaki rotasyon
    except Exception as e:  # noqa: BLE001
        raise HTTPException(status_code=400, detail=f"Görsel açılamadı: {e}") from e

    # Alpha channel varsa beyaz arka planla düzleştir (mermaid PDF'lerinde önemli)
    if img.mode in ("RGBA", "LA"):
        bg = Image.new("RGB", img.size, (255, 255, 255))
        bg.paste(img, mask=img.split()[-1])
        img = bg
    elif img.mode != "RGB":
        img = img.convert("RGB")

    # Grayscale
    gray = img.convert("L")

    # Boyut sınırı
    w, h = gray.size
    if max(w, h) > _MAX_DIMENSION:
        scale = _MAX_DIMENSION / max(w, h)
        gray = gray.resize((int(w * scale), int(h * scale)), Image.LANCZOS)

    # Threshold — her pikseli 0 veya 255'e yuvarla
    bw = gray.point(lambda p: 255 if p > _BW_THRESHOLD else 0, "L")

    out = io.BytesIO()
    bw.save(out, format="PNG", optimize=True)
    png_bytes = out.getvalue()

    return Response(content=png_bytes, media_type="image/png")
