"""Drafting endpoints — tarifname, istemler, özet.

Sprint 3:
- POST /api/v1/llm/draft-description: intake JSON → tarifname taslağı (Opus 4.7, SSE)
- POST /api/v1/llm/draft-claims: tarifname + intake → istemler JSON (Sonnet 4.6)
- POST /api/v1/llm/draft-abstract: tarifname → özet (Haiku 4.5, max 150 kelime)

Her endpoint TürkPatent sistem prompt'unu cache_control ile gönderir —
tekrar çağrılarda ~0.1x maliyet.

Model tiering (CLAUDE.md "LLM cost discipline"):
- Opus 4.7: tarifname (karmaşık akıl yürütme)
- Sonnet 4.6: istemler (yapı + dil, orta karmaşıklık)
- Haiku 4.5: özet (150 kelime altı sıkıştırma, basit)
"""

from __future__ import annotations

import json
from collections.abc import AsyncIterator

from anthropic import APIError
from fastapi import APIRouter, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field, ValidationError

from app.config import get_settings
from app.services.llm import (
    LLMNotConfiguredError,
    get_anthropic_client,
    load_turkpatent_system_prompt,
)

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


class InventionIntake(BaseModel):
    """Buluş anlatımı 10-soru formu — frontend'deki questions.ts ile aynı anahtarlar.

    Laravel tarafı null değerler gönderebilir (DB'de cast edilmeyen JSON alanları)
    ya da alan tamamen eksik olabilir; her iki durumda da boş string olarak kabul ederiz.
    """

    technical_field: str | None = Field(default="")
    problem: str | None = Field(default="")
    prior_art: str | None = Field(default="")
    solution: str | None = Field(default="")
    novelty: str | None = Field(default="")
    advantages: str | None = Field(default="")
    industrial_applicability: str | None = Field(default="")
    variations: str | None = Field(default="")
    terminology: str | None = Field(default="")
    example_use: str | None = Field(default="")


class DraftDescriptionRequest(BaseModel):
    title: str = Field(..., min_length=3, max_length=200)
    intake: InventionIntake
    reference_code: str | None = Field(default=None, max_length=50)
    jurisdiction: str = Field(default="TR", pattern=r"^(TR|EP|US|PCT)$")
    model: str | None = None
    max_tokens: int = Field(default=8000, ge=1000, le=16000)


def _sse(event: str, data: dict) -> str:
    return f"event: {event}\ndata: {json.dumps(data, ensure_ascii=False)}\n\n"


def _build_user_message(req: DraftDescriptionRequest) -> str:
    """Intake JSON'u Claude'a okunabilir yapılandırılmış kullanıcı mesajı olarak ver."""
    parts: list[str] = [
        f"Patent başlığı: {req.title}",
        f"Hedef ofis: {req.jurisdiction}",
    ]
    if req.reference_code:
        parts.append(f"İç referans: {req.reference_code}")
    parts.append("")
    parts.append("## Buluş Anlatımı (10-soru formu yanıtları)")
    parts.append("")

    labels = {
        "technical_field": "1. Teknik alan",
        "problem": "2. Çözülen problem",
        "prior_art": "3. Bilinen önceki çözümler",
        "solution": "4. Önerilen çözüm",
        "novelty": "5. Yenilik noktası",
        "advantages": "6. Teknik avantajlar",
        "industrial_applicability": "7. Endüstriyel uygulama alanı",
        "variations": "8. Alternatif uygulama biçimleri",
        "terminology": "9. Özel terimler / kısaltmalar",
        "example_use": "10. Somut örnek kullanım",
    }
    intake = req.intake.model_dump()
    for key, label in labels.items():
        value = (intake.get(key) or "").strip()
        parts.append(f"### {label}")
        parts.append(value if value else "_[kullanıcı tarafından tamamlanacaktır]_")
        parts.append("")

    parts.append("---")
    parts.append("")
    parts.append(
        "Yukarıdaki buluş anlatımından, TürkPatent formatına uygun 8-bölümlü "
        "bir tarifname **taslağı** üret. Markdown olarak yaz; bölüm başlıklarını "
        "`##` ile ver. Her bölümü 2-5 paragraf olacak şekilde doldur. "
        "Referans numaralarını parantez içinde tutarlı kullan. "
        "Eksik bilgi varsa `[kullanıcı tarafından tamamlanacaktır]` placeholder'ı koy — uydurma."
    )
    return "\n".join(parts)


async def _stream_description(req: DraftDescriptionRequest) -> AsyncIterator[str]:
    try:
        client = get_anthropic_client()
    except LLMNotConfiguredError as exc:
        yield _sse("error", {"message": str(exc)})
        return

    # Tarifname drafting — en karmaşık görev, Opus 4.7
    model = req.model or "claude-opus-4-7"
    system_prompt = load_turkpatent_system_prompt()
    user_message = _build_user_message(req)

    try:
        async with client.messages.stream(
            model=model,
            max_tokens=req.max_tokens,
            system=[
                {
                    "type": "text",
                    "text": system_prompt,
                    "cache_control": {"type": "ephemeral"},
                }
            ],
            messages=[{"role": "user", "content": user_message}],
            # Opus 4.7: adaptive thinking (SKILL.md defaults)
            thinking={"type": "adaptive"},
        ) as stream:
            yield _sse(
                "start",
                {
                    "model": model,
                    "jurisdiction": req.jurisdiction,
                    "title": req.title,
                },
            )

            async for event in stream:
                if event.type == "content_block_delta":
                    if event.delta.type == "text_delta":
                        yield _sse("delta", {"text": event.delta.text})
                    # thinking_delta şu an emit edilmiyor (Opus 4.7 default display=omitted)
                elif event.type == "content_block_start":
                    block_type = getattr(event.content_block, "type", None)
                    if block_type == "thinking":
                        yield _sse("thinking_start", {})
                    elif block_type == "text":
                        yield _sse("text_start", {})

            final = await stream.get_final_message()
            yield _sse(
                "end",
                {
                    "stop_reason": final.stop_reason,
                    "usage": {
                        "input_tokens": final.usage.input_tokens,
                        "output_tokens": final.usage.output_tokens,
                        "cache_creation_input_tokens": getattr(
                            final.usage, "cache_creation_input_tokens", 0
                        )
                        or 0,
                        "cache_read_input_tokens": getattr(
                            final.usage, "cache_read_input_tokens", 0
                        )
                        or 0,
                    },
                },
            )
    except APIError as exc:
        yield _sse(
            "error",
            {
                "message": f"Anthropic API hatası: {exc.message}",
                "status": exc.status_code,
            },
        )


@router.post("/draft-description")
async def draft_description(request: DraftDescriptionRequest) -> StreamingResponse:
    """Buluş anlatımı JSON'undan TürkPatent tarifname taslağı üretir.

    Output: Markdown, 8 TürkPatent bölümü içerir. Her AI çıktısı "taslak"tır —
    son inceleme vekile aittir.
    """
    settings = get_settings()
    if not settings.anthropic_api_key:
        raise HTTPException(
            status_code=503,
            detail="LLM servisi konfigüre edilmemiş (ANTHROPIC_API_KEY yok).",
        )

    return StreamingResponse(
        _stream_description(request),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "X-Accel-Buffering": "no",
        },
    )


# ---------------------------------------------------------------------------
# /draft-claims — Sonnet 4.6 + structured output
# ---------------------------------------------------------------------------


class ClaimItem(BaseModel):
    """Tek bir istem kalemı."""

    number: int = Field(..., ge=1, le=200)
    type: str = Field(..., pattern=r"^(independent|dependent)$")
    parent: int | None = Field(default=None, ge=1)
    text: str = Field(..., min_length=10, max_length=5000)


class ClaimsList(BaseModel):
    """Claude'dan yapılandırılmış çıktı olarak alınacak kök."""

    claims: list[ClaimItem] = Field(..., min_length=1, max_length=50)


class DraftClaimsRequest(BaseModel):
    title: str = Field(..., min_length=3, max_length=200)
    description_markdown: str = Field(..., min_length=200, max_length=80000)
    intake: InventionIntake
    # 8000 default — Sonnet 4.6 adaptive thinking token'ı üst tarafta kullanır,
    # 30+ istemi rahat üretmesi için geniş tut. Eskiden 4000'di — truncate oluyordu.
    max_tokens: int = Field(default=8000, ge=800, le=16000)
    model: str | None = None


def _build_claims_user_message(req: DraftClaimsRequest) -> str:
    intake = req.intake.model_dump()
    return (
        f"Patent başlığı: {req.title}\n\n"
        "## Tarifname taslağı\n\n"
        f"{req.description_markdown}\n\n"
        "## Buluş anlatımı (özet)\n"
        f"- Teknik alan: {intake.get('technical_field') or '—'}\n"
        f"- Çözüm: {intake.get('solution') or '—'}\n"
        f"- Yenilik: {intake.get('novelty') or '—'}\n"
        f"- Varyasyonlar: {intake.get('variations') or '—'}\n\n"
        "---\n\n"
        "Yukarıdaki tarifname ve anlatımdan TürkPatent formatına uygun istem taslağı üret.\n"
        "Kurallar:\n"
        "1. İstem 1 BAĞIMSIZ ve TEK CÜMLE olmalı.\n"
        "2. Numaralar 1'den başlar, atlanmaz, sürekli artar.\n"
        "3. Bağımlı istemler yalnızca daha düşük numaralı isteme referans verir.\n"
        "4. 'Tercihen', 'opsiyonel', 'genellikle' yumuşatıcıları yasak.\n"
        "5. Referans numaraları tarifname ile tutarlı olmalı.\n"
        "6. Bağımsız istem buluşun ÖZÜNÜ kapsamalı — dar da geniş de tutmayın.\n\n"
        "Çıktı: yalnızca `claims` alanı içeren JSON obje, her kalemde "
        "`number`, `type` (independent|dependent), `parent` (bağımlılar için), `text`."
    )


@router.post("/draft-claims")
async def draft_claims(request: DraftClaimsRequest) -> dict:
    """Tarifname + intake → yapılandırılmış istemler JSON array.

    Sonnet 4.6 + output_config ile şema-doğrulanmış JSON döner.
    Stream kullanılmaz — istemler kısa, full response bekletmek UI için daha temiz.
    """
    settings = get_settings()
    if not settings.anthropic_api_key:
        raise HTTPException(
            status_code=503,
            detail="LLM servisi konfigüre edilmemiş (ANTHROPIC_API_KEY yok).",
        )

    try:
        client = get_anthropic_client()
    except LLMNotConfiguredError as exc:
        raise HTTPException(status_code=503, detail=str(exc)) from exc

    model = request.model or "claude-sonnet-4-6"
    system_prompt = load_turkpatent_system_prompt()

    try:
        # messages.parse() Pydantic model'i şemaya çevirip validate eder
        response = await client.messages.parse(
            model=model,
            max_tokens=request.max_tokens,
            system=[
                {
                    "type": "text",
                    "text": system_prompt,
                    "cache_control": {"type": "ephemeral"},
                }
            ],
            messages=[{"role": "user", "content": _build_claims_user_message(request)}],
            output_format=ClaimsList,
            # thinking="disabled" — istemler düz structural çıktı; thinking
            # gereksiz yere output budget'ı yer, ortada JSON truncate olur
            thinking={"type": "disabled"},
        )
    except APIError as exc:
        raise HTTPException(
            status_code=502,
            detail=f"Anthropic API hatası: {exc.message}",
        ) from exc
    except ValidationError as exc:
        # Model max_tokens'a takıldıysa veya şemaya uymadıysa JSON truncate olur.
        import logging

        logging.getLogger(__name__).warning(
            "Claims schema validation failed: %s", exc.errors()[:3]
        )
        raise HTTPException(
            status_code=502,
            detail=(
                "Claude JSON çıktısı şemaya uymadı (muhtemelen max_tokens aşıldı). "
                f"Detay: {exc.errors()[:2]}"
            ),
        ) from exc

    parsed: ClaimsList | None = response.parsed_output
    if parsed is None:
        raise HTTPException(
            status_code=502,
            detail="Claude yapılandırılmış çıktı üretemedi (parse_output None).",
        )

    if response.stop_reason == "max_tokens":
        # JSON parse etti ama yine de truncate olmuş olabilir
        raise HTTPException(
            status_code=502,
            detail=(
                "Claude max_tokens limitine ulaştı; istem üretimi eksik kalmış olabilir. "
                "max_tokens değerini artırın."
            ),
        )

    _validate_claims_structure(parsed.claims)

    return {
        "claims": [c.model_dump() for c in parsed.claims],
        "usage": {
            "input_tokens": response.usage.input_tokens,
            "output_tokens": response.usage.output_tokens,
            "cache_creation_input_tokens": getattr(response.usage, "cache_creation_input_tokens", 0)
            or 0,
            "cache_read_input_tokens": getattr(response.usage, "cache_read_input_tokens", 0) or 0,
        },
        "model": model,
        "is_draft": True,
    }


def _validate_claims_structure(claims: list[ClaimItem]) -> None:
    """TürkPatent kuralları: ilk istem bağımsız + sürekli numara + geri-referans."""
    if not claims:
        raise HTTPException(status_code=502, detail="Claude hiç istem üretmedi.")

    if claims[0].type != "independent":
        raise HTTPException(
            status_code=502,
            detail="İstem 1 bağımsız olmalı; üretilen yapı TürkPatent kuralına uymuyor.",
        )

    # Numaralar 1'den başlayıp sırayla artmalı
    for expected_num, claim in enumerate(claims, start=1):
        if claim.number != expected_num:
            raise HTTPException(
                status_code=502,
                detail=(
                    f"İstem numaralandırması bozuk — sırayla 1,2,3... olmalı "
                    f"(beklenen {expected_num}, gelen {claim.number})."
                ),
            )

    # Bağımlı istemler yalnızca kendilerinden düşük numaralı isteme referans verebilir
    for claim in claims:
        if claim.type == "dependent":
            if claim.parent is None:
                raise HTTPException(
                    status_code=502,
                    detail=f"Bağımlı istem {claim.number} parent alanı taşımıyor.",
                )
            if claim.parent >= claim.number:
                raise HTTPException(
                    status_code=502,
                    detail=(
                        f"Bağımlı istem {claim.number} kendine veya ileri numaraya "
                        f"(parent={claim.parent}) referans veriyor."
                    ),
                )


# ---------------------------------------------------------------------------
# /draft-abstract — Haiku 4.5, max 150 kelime
# ---------------------------------------------------------------------------


class DraftAbstractRequest(BaseModel):
    title: str = Field(..., min_length=3, max_length=200)
    description_markdown: str = Field(..., min_length=200, max_length=80000)
    max_tokens: int = Field(default=400, ge=100, le=1000)
    model: str | None = None


@router.post("/draft-abstract")
async def draft_abstract(request: DraftAbstractRequest) -> dict:
    """Tarifnameden TürkPatent özeti (max 150 kelime) üretir.

    Haiku 4.5 — format kontrolü + sıkıştırma görevi, en ucuz model yeter.
    """
    settings = get_settings()
    if not settings.anthropic_api_key:
        raise HTTPException(
            status_code=503,
            detail="LLM servisi konfigüre edilmemiş (ANTHROPIC_API_KEY yok).",
        )
    try:
        client = get_anthropic_client()
    except LLMNotConfiguredError as exc:
        raise HTTPException(status_code=503, detail=str(exc)) from exc

    model = request.model or "claude-haiku-4-5"
    system_prompt = load_turkpatent_system_prompt()

    user_message = (
        f"Patent başlığı: {request.title}\n\n"
        "## Tarifname\n\n"
        f"{request.description_markdown}\n\n"
        "---\n\n"
        "Yukarıdaki tarifnameden TürkPatent kurallarına uygun **özet** üret.\n"
        "- Tek paragraf, alt başlıksız.\n"
        "- **Maksimum 150 kelime** (sıkı limit).\n"
        "- İstem dili tekrarlanmasın.\n"
        "- Yalnızca özet metnini döndür, başka açıklama yazma."
    )

    try:
        response = await client.messages.create(
            model=model,
            max_tokens=request.max_tokens,
            system=[
                {
                    "type": "text",
                    "text": system_prompt,
                    "cache_control": {"type": "ephemeral"},
                }
            ],
            messages=[{"role": "user", "content": user_message}],
        )
    except APIError as exc:
        raise HTTPException(
            status_code=502,
            detail=f"Anthropic API hatası: {exc.message}",
        ) from exc

    # İlk text block'u al
    abstract_text = ""
    for block in response.content:
        if getattr(block, "type", None) == "text":
            abstract_text = block.text
            break
    abstract_text = abstract_text.strip()

    word_count = len(abstract_text.split())
    return {
        "abstract": abstract_text,
        "word_count": word_count,
        "within_limit": word_count <= 150,
        "usage": {
            "input_tokens": response.usage.input_tokens,
            "output_tokens": response.usage.output_tokens,
            "cache_creation_input_tokens": getattr(response.usage, "cache_creation_input_tokens", 0)
            or 0,
            "cache_read_input_tokens": getattr(response.usage, "cache_read_input_tokens", 0) or 0,
        },
        "model": model,
        "is_draft": True,
    }
