"""W21B-w5 STEP5-B: light-FP provider preflight + response-guard tests.

Deterministic only — no real network. The fail-closed preflight (bundle shape +
env) is exercised directly; the response guards are exercised with a fake
``litellm`` module injected into ``sys.modules`` (the provider lazy-imports it).
"""
from __future__ import annotations

import sys
import types

import pytest

from app.modules.pipeline.floor_plan_light_prompt import (
    build_light_fp_llm_prompt_bundle,
)
from app.modules.pipeline.floor_plan_light_prompt_provider import (
    FloorPlanLightProviderError,
    litellm_light_fp_provider,
)

_NUMBERED = [
    {"number": 1, "label": "living", "base_layer_decision": "base_structural_unit"},
    {"number": 2, "label": "door", "base_layer_decision": "base_opening"},
]


def _bundle():
    return build_light_fp_llm_prompt_bundle(_NUMBERED)


# ──────────────────────────── preflight ────────────────────────────
def test_non_dict_bundle():
    with pytest.raises(FloorPlanLightProviderError):
        litellm_light_fp_provider(prompt_bundle="nope")  # type: ignore[arg-type]


def test_missing_system():
    b = _bundle()
    b["system"] = ""
    with pytest.raises(FloorPlanLightProviderError):
        litellm_light_fp_provider(prompt_bundle=b)


def test_missing_schema():
    b = _bundle()
    b["schema"] = None
    with pytest.raises(FloorPlanLightProviderError):
        litellm_light_fp_provider(prompt_bundle=b)


def test_missing_api_key(monkeypatch):
    # [2026-08-01] 키 유무의 권위가 환경변수에서 슬롯 브로커로 옮겼다 —
    # 보조 슬롯만 있어도 '있음'이므로 env 만 지우면 fail-closed 가 아니다.
    monkeypatch.delenv("OPENAI_API_KEY", raising=False)
    monkeypatch.setattr(
        "app.core.config.settings.openai_api_key", "", raising=False)
    monkeypatch.setattr(
        "app.core.config.settings.openai_api_key_secondary", "",
        raising=False)
    with pytest.raises(FloorPlanLightProviderError) as ei:
        litellm_light_fp_provider(prompt_bundle=_bundle())
    assert "OPENAI_API_KEY" in str(ei.value)


# ──────────────────────────── response guards (fake litellm) ────────────────────────────
class _Msg:
    def __init__(self, content, refusal=None):
        self.content = content
        self.refusal = refusal


class _Choice:
    def __init__(self, content, finish_reason="stop", refusal=None):
        self.message = _Msg(content, refusal=refusal)
        self.finish_reason = finish_reason


class _Resp:
    def __init__(self, choices):
        self.choices = choices


def _inject_litellm(monkeypatch, *, choices):
    fake = types.ModuleType("litellm")

    def completion(**kwargs):
        return _Resp(choices)

    fake.completion = completion  # type: ignore[attr-defined]
    monkeypatch.setitem(sys.modules, "litellm", fake)
    monkeypatch.setenv("OPENAI_API_KEY", "sk-test")


def test_valid_response_parses(monkeypatch):
    _inject_litellm(
        monkeypatch,
        choices=[_Choice('{"selected_markers": [], "room_schematic_prompt": "x"}')],
    )
    out = litellm_light_fp_provider(prompt_bundle=_bundle())
    assert out == {"selected_markers": [], "room_schematic_prompt": "x"}


def test_bad_finish_reason(monkeypatch):
    _inject_litellm(
        monkeypatch,
        choices=[_Choice('{"a": 1}', finish_reason="length")],
    )
    with pytest.raises(FloorPlanLightProviderError) as ei:
        litellm_light_fp_provider(prompt_bundle=_bundle())
    assert "finish_reason" in str(ei.value)


def test_refusal(monkeypatch):
    _inject_litellm(
        monkeypatch,
        choices=[_Choice("content", refusal="I cannot")],
    )
    with pytest.raises(FloorPlanLightProviderError) as ei:
        litellm_light_fp_provider(prompt_bundle=_bundle())
    assert "refusal" in str(ei.value)


def test_empty_content(monkeypatch):
    _inject_litellm(monkeypatch, choices=[_Choice("")])
    with pytest.raises(FloorPlanLightProviderError):
        litellm_light_fp_provider(prompt_bundle=_bundle())


def test_non_json_content(monkeypatch):
    _inject_litellm(monkeypatch, choices=[_Choice("not json{")])
    with pytest.raises(FloorPlanLightProviderError) as ei:
        litellm_light_fp_provider(prompt_bundle=_bundle())
    assert "JSON" in str(ei.value)


def test_empty_choices(monkeypatch):
    _inject_litellm(monkeypatch, choices=[])
    with pytest.raises(FloorPlanLightProviderError):
        litellm_light_fp_provider(prompt_bundle=_bundle())
