import json
import pytest
import respx
import httpx

from app import pipeline
from app.models import TarotInterpretRequest, TarotCardDraw


@pytest.fixture(autouse=True)
def _patch_key(monkeypatch):
    monkeypatch.setattr(pipeline, "ANTHROPIC_API_KEY", "test-key")


def _req():
    return TarotInterpretRequest(
        spread_type="three_card",
        category="연애",
        question="Q",
        cards=[
            TarotCardDraw(position="과거", card_id="the-fool", reversed=False),
            TarotCardDraw(position="현재", card_id="the-magician", reversed=False),
            TarotCardDraw(position="미래", card_id="the-empress", reversed=True),
        ],
        cards_reference="...",
    )


def _valid_response_json():
    return {
        "summary": "흐름이 있음",
        "cards": [
            {"position": "과거", "card": "the-fool", "reversed": False,
             "interpretation": "...", "advice": "...",
             "evidence": {"card_meaning_used": "...", "position_logic": "...", "category_lens": "..."}},
            {"position": "현재", "card": "the-magician", "reversed": False,
             "interpretation": "...", "advice": "...",
             "evidence": {"card_meaning_used": "...", "position_logic": "...", "category_lens": "..."}},
            {"position": "미래", "card": "the-empress", "reversed": True,
             "interpretation": "...", "advice": "...",
             "evidence": {"card_meaning_used": "...", "position_logic": "...", "category_lens": "..."}},
        ],
        "interactions": [{"type": "synergy", "between": ["the-fool", "the-magician"], "explanation": "..."}],
        "advice": "...",
        "warning": None,
        "confidence": "medium",
    }


def _claude_envelope(text: str, in_tok=100, out_tok=200, stop_reason="end_turn"):
    return {
        "content": [{"type": "text", "text": text}],
        "usage": {"input_tokens": in_tok, "output_tokens": out_tok},
        "stop_reason": stop_reason,
    }


@respx.mock
async def test_interpret_success():
    respx.post("https://api.anthropic.com/v1/messages").mock(
        return_value=httpx.Response(200, json=_claude_envelope(json.dumps(_valid_response_json())))
    )
    result = await pipeline.interpret(_req())
    assert result["reroll_count"] == 0
    assert result["model"] == pipeline.TAROT_MODEL
    assert result["tokens_in"] == 100
    assert result["cost_usd"] > 0


@respx.mock
async def test_interpret_codeblock_stripped():
    text = "```json\n" + json.dumps(_valid_response_json()) + "\n```"
    respx.post("https://api.anthropic.com/v1/messages").mock(
        return_value=httpx.Response(200, json=_claude_envelope(text))
    )
    result = await pipeline.interpret(_req())
    assert "interpretation_json" in result


@respx.mock
async def test_interpret_reroll_then_success():
    valid = json.dumps(_valid_response_json())
    invalid = json.dumps({"summary": "...", "cards": [], "interactions": [], "advice": "", "confidence": "medium"})
    respx.post("https://api.anthropic.com/v1/messages").mock(
        side_effect=[
            httpx.Response(200, json=_claude_envelope(invalid)),
            httpx.Response(200, json=_claude_envelope(valid)),
        ]
    )
    result = await pipeline.interpret(_req())
    assert result["reroll_count"] == 1


@respx.mock
async def test_interpret_reroll_fail_raises():
    invalid = json.dumps({"summary": "...", "cards": [], "interactions": [], "advice": "", "confidence": "medium"})
    respx.post("https://api.anthropic.com/v1/messages").mock(
        return_value=httpx.Response(200, json=_claude_envelope(invalid))
    )
    with pytest.raises(pipeline.TarotError):
        await pipeline.interpret(_req())


@respx.mock
async def test_interpret_http_error():
    respx.post("https://api.anthropic.com/v1/messages").mock(
        return_value=httpx.Response(500, text="boom")
    )
    with pytest.raises(pipeline.TarotError):
        await pipeline.interpret(_req())


def test_calc_cost():
    cost = pipeline.calc_cost(1_000_000, 1_000_000)
    assert cost == pipeline.TAROT_COST_INPUT_PER_M + pipeline.TAROT_COST_OUTPUT_PER_M


@respx.mock
async def test_interpret_truncated_then_success():
    """1차 응답이 max_tokens에서 잘림 → 2차에서 정상 JSON 반환."""
    truncated_text = '{"summary": "흐름이 있음", "cards": [{"position": "과거", "card": "the-fool", "reversed": false, "interpretation": "끝나지 않은 문장'
    valid = json.dumps(_valid_response_json())
    respx.post("https://api.anthropic.com/v1/messages").mock(
        side_effect=[
            httpx.Response(200, json=_claude_envelope(truncated_text, stop_reason="max_tokens")),
            httpx.Response(200, json=_claude_envelope(valid)),
        ]
    )
    result = await pipeline.interpret(_req())
    assert result["reroll_count"] == 1
    assert "interpretation_json" in result


@respx.mock
async def test_interpret_truncated_twice_raises():
    """두 번 모두 max_tokens 잘림 → TarotError, 메시지에 'max_tokens' 포함."""
    truncated_text = '{"summary": "...", "cards": [{"position":'
    respx.post("https://api.anthropic.com/v1/messages").mock(
        return_value=httpx.Response(
            200, json=_claude_envelope(truncated_text, stop_reason="max_tokens")
        )
    )
    with pytest.raises(pipeline.TarotError) as exc_info:
        await pipeline.interpret(_req())
    assert "max_tokens" in str(exc_info.value)
