"""openrouter_vlm_client — 기록 의무(왕복==기록)·shape 이상·fail-closed.

대역은 **외부 API(openai SDK)와 기록 헬퍼**만 — extract_json·검증·교정
루프·기록 호출 지점은 실물 코드가 돈다. 핵심 계약(Codex BLOCK-1): 유료
왕복(create 호출) 수 == record_provider_call 수 — 성공·400 폴백·스키마
재질의·응답 shape 이상 전부. 기록 없는 유료 왕복은 "이 판정을 산 적
없다"로 읽혀 resume 이중 지출의 창이 된다.
"""
from __future__ import annotations

from typing import Any, Dict, List

import pytest

import app.modules.llm.openrouter_vlm_client as orc
from app.core.config import settings

SCHEMA = {
    "type": "object", "additionalProperties": False,
    "properties": {"winner": {"type": "string"}},
    "required": ["winner"],
}


class _FakeResp:
    def __init__(self, content):
        self.choices = [type("C", (), {
            "message": type("M", (), {"content": content})()})()]
        self.usage = None


class _NoChoicesResp:
    def __init__(self):
        self.choices = []
        self.usage = None


class _FakeClient:
    """행동 시퀀스 대역 — 각 원소는 응답 객체 또는 raise 할 예외."""

    def __init__(self, behaviors: List[Any]):
        self._behaviors = list(behaviors)
        self.calls: List[Dict[str, Any]] = []
        outer = self

        class _Completions:
            def create(self, **kw):
                outer.calls.append(kw)
                b = outer._behaviors.pop(0)
                if isinstance(b, Exception):
                    raise b
                return b

        self.chat = type("Chat", (), {"completions": _Completions()})()


def _wire(monkeypatch, behaviors: List[Any]):
    fake = _FakeClient(behaviors)
    monkeypatch.setattr(settings, "openrouter_api_key", "k")
    monkeypatch.setattr(orc, "_client", lambda: fake)
    records: List[Dict[str, Any]] = []

    import app.modules.llm.image_tracer as tracer

    def fake_record(**kw):
        records.append(kw)
        return None

    monkeypatch.setattr(tracer, "record_provider_call", fake_record)
    return fake, records


def test_success_one_roundtrip_one_record(monkeypatch):
    fake, records = _wire(monkeypatch, [_FakeResp('{"winner": "A"}')])
    out = orc.ask_openrouter_structured("t", "SYS", [], SCHEMA, model="m/x")
    assert out == {"winner": "A"}
    assert len(fake.calls) == 1 == len(records)
    assert records[0]["status"] == "success"
    assert records[0]["provider"] == "openrouter"
    assert records[0]["model"] == "m/x"


def test_response_format_400_fallback_records_both(monkeypatch):
    fake, records = _wire(monkeypatch, [
        Exception("Error code: 400 - response_format unsupported"),
        _FakeResp('{"winner": "B"}'),
    ])
    out = orc.ask_openrouter_structured("t", "SYS", [], SCHEMA, model="m/x")
    assert out == {"winner": "B"}
    assert len(fake.calls) == 2 == len(records)
    assert [r["status"] for r in records] == ["error", "success"]
    # 첫 시도만 response_format 동봉, 폴백은 미동봉
    assert "response_format" in fake.calls[0]
    assert "response_format" not in fake.calls[1]


def test_schema_violation_feedback_retry_records_each(monkeypatch):
    fake, records = _wire(monkeypatch, [
        _FakeResp('{"loser": "A"}'),
        _FakeResp('{"winner": "A"}'),
    ])
    out = orc.ask_openrouter_structured("t", "SYS", [], SCHEMA, model="m/x")
    assert out == {"winner": "A"}
    assert len(fake.calls) == 2 == len(records)
    assert [r["status"] for r in records] == ["error", "success"]


def test_unexpected_shape_records_then_raises(monkeypatch):
    """Codex BLOCK-1 재현: choices=[] — 유료 왕복 기록 0건인 채 raise 되던
    창. 기록 정확히 1건 후 원 예외 전파가 계약이다."""
    fake, records = _wire(monkeypatch, [_NoChoicesResp()])
    with pytest.raises(IndexError):
        orc.ask_openrouter_structured("t", "SYS", [], SCHEMA, model="m/x")
    assert len(fake.calls) == 1 == len(records)
    assert records[0]["status"] == "error"
    assert "unexpected response shape" in records[0]["error"]


def test_non_string_content_records_then_raises(monkeypatch):
    fake, records = _wire(monkeypatch, [_FakeResp(["not", "a", "string"])])
    with pytest.raises(TypeError):
        orc.ask_openrouter_structured("t", "SYS", [], SCHEMA, model="m/x")
    assert len(fake.calls) == 1 == len(records)
    assert records[0]["status"] == "error"


def test_missing_key_fail_closed(monkeypatch):
    monkeypatch.setattr(settings, "openrouter_api_key", "")
    with pytest.raises(orc.OpenRouterNotConfigured):
        orc.ask_openrouter_structured("t", "SYS", [], SCHEMA, model="m/x")


def test_invalid_local_schema_records_then_raises(monkeypatch):
    """Codex 재확인 BLOCK 재현: jsonschema.SchemaError(로컬 스키마 불량)는
    ValidationError 의 subclass 가 아니라 재질의 catch 를 지나쳐 기록 0건
    으로 탈출하던 창. error 기록 1건 후 전파(재질의 없음)가 계약이다."""
    from jsonschema import SchemaError

    fake, records = _wire(monkeypatch, [_FakeResp("{}")])
    with pytest.raises(SchemaError):
        orc.ask_openrouter_structured(
            "t", "SYS", [], {"type": "bogus"}, model="m/x")
    assert len(fake.calls) == 1 == len(records)
    assert records[0]["status"] == "error"
    assert "unexpected post-response failure" in records[0]["error"]
