"""Tests for /draft-claims and /draft-abstract."""

from __future__ import annotations

from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from httpx import ASGITransport, AsyncClient

from app.api.drafting import ClaimItem, ClaimsList, _validate_claims_structure
from app.config import get_settings
from app.main import app

_SAMPLE_DESC = (
    "## Buluşun Ait Olduğu Teknik Alan\n\n"
    "Bu buluş, veri haberleşme güvenliği alanına ilişkin olup, "
    "özellikle post-kuantum kriptografi tabanlı ağ geçitleri ile ilgilidir. "
) * 4  # >= 200 char

_SAMPLE_INTAKE = {
    "technical_field": "Post-kuantum kriptografi.",
    "problem": "Klasik TLS kırılabilir.",
    "prior_art": "",
    "solution": "ML-DSA + X25519.",
    "novelty": "Tek handshake çift algoritma.",
    "advantages": "Kuantum dirençli.",
    "industrial_applicability": "Bankacılık.",
    "variations": "X448, ML-DSA-87.",
    "terminology": "ML-DSA.",
    "example_use": "Gateway(100) handshake.",
}


def _claims_payload() -> dict:
    return {
        "title": "PQC ağ geçidi",
        "description_markdown": _SAMPLE_DESC,
        "intake": _SAMPLE_INTAKE,
    }


def _abstract_payload() -> dict:
    return {"title": "PQC ağ geçidi", "description_markdown": _SAMPLE_DESC}


# ---------------- claims validation unit tests ----------------


def test_validate_claims_first_must_be_independent() -> None:
    from fastapi import HTTPException

    claims = [
        ClaimItem(number=1, type="dependent", parent=1, text="bad first claim here."),
    ]
    with pytest.raises(HTTPException) as exc:
        _validate_claims_structure(claims)
    assert exc.value.status_code == 502
    assert "bağımsız" in exc.value.detail.lower()


def test_validate_claims_numbering_must_be_sequential() -> None:
    from fastapi import HTTPException

    claims = [
        ClaimItem(number=1, type="independent", parent=None, text="ilk istem metni."),
        ClaimItem(number=3, type="dependent", parent=1, text="atlamış istem metni."),
    ]
    with pytest.raises(HTTPException) as exc:
        _validate_claims_structure(claims)
    assert "numaralandırma" in exc.value.detail.lower()


def test_validate_claims_dependent_must_reference_lower_number() -> None:
    from fastapi import HTTPException

    claims = [
        ClaimItem(number=1, type="independent", parent=None, text="ilk istem metni."),
        ClaimItem(number=2, type="dependent", parent=3, text="ileri referans — hatalı."),
    ]
    with pytest.raises(HTTPException) as exc:
        _validate_claims_structure(claims)
    assert "ileri numaraya" in exc.value.detail.lower()


def test_validate_claims_happy_path() -> None:
    claims = [
        ClaimItem(number=1, type="independent", parent=None, text="ilk istem metni."),
        ClaimItem(number=2, type="dependent", parent=1, text="ikinci istem metni."),
        ClaimItem(number=3, type="dependent", parent=1, text="üçüncü istem metni."),
    ]
    _validate_claims_structure(claims)  # no exception


# ---------------- claims endpoint tests ----------------


@pytest.mark.asyncio
async def test_draft_claims_returns_503_without_api_key() -> None:
    get_settings.cache_clear()
    with patch.dict("os.environ", {"ANTHROPIC_API_KEY": ""}, clear=False):
        get_settings.cache_clear()
        transport = ASGITransport(app=app)
        async with AsyncClient(transport=transport, base_url="http://test") as client:
            response = await client.post("/api/v1/llm/draft-claims", json=_claims_payload())
    assert response.status_code == 503
    get_settings.cache_clear()


@pytest.mark.asyncio
async def test_draft_claims_returns_structured_json() -> None:
    """Mock messages.parse: şemaya uygun claims döner, endpoint JSON olarak verir."""
    get_settings.cache_clear()

    mock_parsed = ClaimsList(
        claims=[
            ClaimItem(
                number=1,
                type="independent",
                parent=None,
                text="Bir post-kuantum ağ geçidi olup, özelliği; ML-DSA modülü (230) içermesidir.",
            ),
            ClaimItem(
                number=2,
                type="dependent",
                parent=1,
                text="İstem 1'e göre ağ geçidi, karakterize edici özelliği; X25519 paylaşımı yapmasıdır.",
            ),
        ]
    )

    fake_response = MagicMock()
    fake_response.parsed_output = mock_parsed
    fake_response.usage.input_tokens = 4800
    fake_response.usage.output_tokens = 320
    fake_response.usage.cache_creation_input_tokens = 0
    fake_response.usage.cache_read_input_tokens = 4370

    fake_client = MagicMock()
    fake_client.messages.parse = AsyncMock(return_value=fake_response)

    with patch.dict("os.environ", {"ANTHROPIC_API_KEY": "sk-ant-test"}, clear=False):
        get_settings.cache_clear()
        with patch("app.api.drafting.get_anthropic_client", return_value=fake_client):
            transport = ASGITransport(app=app)
            async with AsyncClient(transport=transport, base_url="http://test") as client:
                response = await client.post("/api/v1/llm/draft-claims", json=_claims_payload())

    assert response.status_code == 200
    body = response.json()
    assert body["model"] == "claude-sonnet-4-6"
    assert body["is_draft"] is True
    assert len(body["claims"]) == 2
    assert body["claims"][0]["type"] == "independent"
    assert body["claims"][1]["parent"] == 1
    assert body["usage"]["cache_read_input_tokens"] == 4370

    # Parse çağrısı doğru parametrelerle yapıldı mı
    call_kwargs = fake_client.messages.parse.call_args.kwargs
    assert call_kwargs["model"] == "claude-sonnet-4-6"
    assert call_kwargs["system"][0]["cache_control"] == {"type": "ephemeral"}
    assert call_kwargs["output_format"] is ClaimsList
    # Bağımsız istem TEK CÜMLE kuralı prompt'a yansıdı mı
    assert "BAĞIMSIZ" in call_kwargs["messages"][0]["content"]

    get_settings.cache_clear()


@pytest.mark.asyncio
async def test_draft_claims_rejects_invalid_structure_from_llm() -> None:
    """LLM bozuk numaralama döndürürse endpoint 502 ile reddeder."""
    get_settings.cache_clear()

    # İstem 1'i bağımlı yapıyoruz — validator 502 atmalı
    bad_parsed = ClaimsList(
        claims=[
            ClaimItem(
                number=1,
                type="dependent",
                parent=1,
                text="yanlış ilk istem — bağımlı olmamalı.",
            ),
        ]
    )
    fake_response = MagicMock()
    fake_response.parsed_output = bad_parsed
    fake_response.usage.input_tokens = 0
    fake_response.usage.output_tokens = 0

    fake_client = MagicMock()
    fake_client.messages.parse = AsyncMock(return_value=fake_response)

    with patch.dict("os.environ", {"ANTHROPIC_API_KEY": "sk-ant-test"}, clear=False):
        get_settings.cache_clear()
        with patch("app.api.drafting.get_anthropic_client", return_value=fake_client):
            transport = ASGITransport(app=app)
            async with AsyncClient(transport=transport, base_url="http://test") as client:
                response = await client.post("/api/v1/llm/draft-claims", json=_claims_payload())

    assert response.status_code == 502
    assert "bağımsız" in response.json()["detail"].lower()
    get_settings.cache_clear()


# ---------------- abstract endpoint tests ----------------


@pytest.mark.asyncio
async def test_draft_abstract_returns_word_count() -> None:
    get_settings.cache_clear()

    fake_text_block = MagicMock()
    fake_text_block.type = "text"
    fake_text_block.text = "Buluş, bir post-kuantum ağ geçidi sunar. " * 3

    fake_response = MagicMock()
    fake_response.content = [fake_text_block]
    fake_response.usage.input_tokens = 5000
    fake_response.usage.output_tokens = 100
    fake_response.usage.cache_creation_input_tokens = 0
    fake_response.usage.cache_read_input_tokens = 4370

    fake_client = MagicMock()
    fake_client.messages.create = AsyncMock(return_value=fake_response)

    with patch.dict("os.environ", {"ANTHROPIC_API_KEY": "sk-ant-test"}, clear=False):
        get_settings.cache_clear()
        with patch("app.api.drafting.get_anthropic_client", return_value=fake_client):
            transport = ASGITransport(app=app)
            async with AsyncClient(transport=transport, base_url="http://test") as client:
                response = await client.post("/api/v1/llm/draft-abstract", json=_abstract_payload())

    assert response.status_code == 200
    body = response.json()
    assert body["model"] == "claude-haiku-4-5"
    assert body["is_draft"] is True
    assert body["word_count"] > 0
    assert body["within_limit"] is True
    assert "abstract" in body

    get_settings.cache_clear()


@pytest.mark.asyncio
async def test_draft_abstract_flags_over_150_words() -> None:
    """Haiku 150 kelime limitini aşarsa within_limit=false döner (hata değil uyarı)."""
    get_settings.cache_clear()

    long_text = "kelime " * 200  # 200 kelime
    fake_text_block = MagicMock(type="text", text=long_text)
    fake_response = MagicMock()
    fake_response.content = [fake_text_block]
    fake_response.usage.input_tokens = 1
    fake_response.usage.output_tokens = 1
    fake_response.usage.cache_creation_input_tokens = 0
    fake_response.usage.cache_read_input_tokens = 0

    fake_client = MagicMock()
    fake_client.messages.create = AsyncMock(return_value=fake_response)

    with patch.dict("os.environ", {"ANTHROPIC_API_KEY": "sk-ant-test"}, clear=False):
        get_settings.cache_clear()
        with patch("app.api.drafting.get_anthropic_client", return_value=fake_client):
            transport = ASGITransport(app=app)
            async with AsyncClient(transport=transport, base_url="http://test") as client:
                response = await client.post("/api/v1/llm/draft-abstract", json=_abstract_payload())

    body = response.json()
    assert response.status_code == 200
    assert body["word_count"] == 200
    assert body["within_limit"] is False
    get_settings.cache_clear()
