from __future__ import annotations

import types

import pytest

import abductio_core.adapters.openai_llm as m


class DummyClient:
    def __init__(self, response):
        self._response = response

    def complete_json(self, *, system: str, user: str):
        return self._response


def test_openai_client_requires_api_key(monkeypatch) -> None:
    monkeypatch.delenv("OPENAI_API_KEY", raising=False)
    with pytest.raises(RuntimeError):
        m.OpenAIJsonClient(model="gpt-4.1-mini")


def test_openai_client_import_error(monkeypatch) -> None:
    monkeypatch.setenv("OPENAI_API_KEY", "test")

    def _fail_import(name):
        raise ImportError("missing")

    monkeypatch.setattr(m.importlib, "import_module", _fail_import)
    with pytest.raises(RuntimeError):
        m.OpenAIJsonClient(model="gpt-4.1-mini")


def test_validate_evaluation_error_branches() -> None:
    with pytest.raises(RuntimeError):
        m._validate_evaluation({"p": "x", "A": 1, "B": 1, "C": 1, "D": 1, "evidence_refs": "r"})
    with pytest.raises(RuntimeError):
        m._validate_evaluation({"p": 0.1, "A": True, "B": 1, "C": 1, "D": 1, "evidence_refs": "r"})
    with pytest.raises(RuntimeError):
        m._validate_evaluation({"p": 0.1, "A": "x", "B": 1, "C": 1, "D": 1, "evidence_refs": "r"})
    with pytest.raises(RuntimeError):
        m._validate_evaluation({"p": 0.1, "A": 3, "B": 1, "C": 1, "D": 1, "evidence_refs": "r"})
    with pytest.raises(RuntimeError):
        m._validate_evaluation({"p": 0.1, "A": 1, "B": 1, "C": 1, "D": 1, "evidence_refs": "(empty)"})


def test_validate_slot_decomposition_error_branches() -> None:
    m._validate_slot_decomposition({"ok": False})
    with pytest.raises(RuntimeError):
        m._validate_slot_decomposition({"ok": True})
    with pytest.raises(RuntimeError):
        m._validate_slot_decomposition(
            {"ok": True, "children": [{"child_id": "c1", "statement": "s"}, {"child_id": "c2", "statement": "s"}], "type": "BAD"}
        )
    with pytest.raises(RuntimeError):
        m._validate_slot_decomposition({"ok": True, "children": [], "type": "AND"})
    with pytest.raises(RuntimeError):
        m._validate_slot_decomposition({"ok": True, "children": ["x", "y"], "type": "OR"})
    with pytest.raises(RuntimeError):
        m._validate_slot_decomposition({"ok": True, "children": [{"child_id": "c1"}], "type": "AND", "coupling": 0.8})
    with pytest.raises(RuntimeError):
        m._validate_slot_decomposition({"ok": True, "children": [{"child_id": "c1", "statement": "s"}, {"child_id": "c2", "statement": "s"}], "type": "AND", "coupling": "x"})
    with pytest.raises(RuntimeError):
        m._validate_slot_decomposition({"ok": True, "children": [{"child_id": "c1", "statement": "s"}, {"child_id": "c2", "statement": "s"}], "type": "AND", "coupling": 0.1})
    with pytest.raises(RuntimeError):
        m._validate_slot_decomposition({"ok": True, "children": ["x", {"child_id": "c1", "statement": "s"}], "type": "AND", "coupling": 0.8})
    with pytest.raises(RuntimeError):
        m._validate_slot_decomposition({"ok": True, "children": [{"statement": "s"}, {"child_id": "c2", "statement": "s"}], "type": "AND", "coupling": 0.8})
    with pytest.raises(RuntimeError):
        m._validate_slot_decomposition({"ok": True, "children": [{"child_id": "c1"}, {"child_id": "c2"}], "type": "AND", "coupling": 0.8})
    with pytest.raises(RuntimeError):
        m._validate_slot_decomposition({"ok": True, "children": [{"child_id": "c1", "statement": "s", "role": "BAD"}, {"child_id": "c2", "statement": "s"}], "type": "AND", "coupling": 0.8})


def test_decomposer_slot_and_root_paths() -> None:
    client = DummyClient({"ok": True, "type": "AND", "coupling": 0.8, "children": [{"child_id": "c1", "statement": "s"}, {"child_id": "c2", "statement": "s"}]})
    port = m.OpenAIDecomposerPort(client=client, required_slots_hint=["feasibility"], claim="c", root_statements={"H1": "root"})
    slot_out = port.decompose("H1:feasibility")
    assert slot_out["children"]

    client2 = DummyClient("not-a-dict")
    port2 = m.OpenAIDecomposerPort(client=client2, required_slots_hint=["feasibility"])
    root_out = port2.decompose("H1")
    assert root_out["ok"] is True
    assert "feasibility_statement" in root_out

    client3 = DummyClient("not-a-dict")
    port3 = m.OpenAIDecomposerPort(client=client3, required_slots_hint=["feasibility"])
    slot_out2 = port3.decompose("H1:feasibility")
    assert slot_out2["children"]


def test_evaluator_paths_and_non_dict_response() -> None:
    client = DummyClient({"p": 0.6, "A": 1, "B": 1, "C": 1, "D": 1, "evidence_refs": "ref"})
    port = m.OpenAIEvaluatorPort(client=client, claim="c", root_statements={"H1": "root"})
    out = port.evaluate("H1:feasibility")
    assert out["p"] == 0.6

    bad = DummyClient("not-a-dict")
    port_bad = m.OpenAIEvaluatorPort(client=bad)
    with pytest.raises(RuntimeError):
        port_bad.evaluate("H1:feasibility")
