"""Layer 2:LangGraph 认知图的路由/结构化/兜底——用假模型,无需真实 LLM。"""

import pytest

from genesis.runtime.brain_schema import (
    ActionDecisionOut, ActionSpec, BootstrapOut, DialogueDecisionOut, ReflectOut,
    RelationDelta, SpeechSpec,
)
from genesis.runtime.graph_brain import build_cognition_graph


class _FakeStructured:
    """模拟 model.with_structured_output(schema):.invoke(msgs) 直接返回预置对象或抛错。"""

    def __init__(self, result=None, error: Exception | None = None):
        self._result = result
        self._error = error
        self.invoked_with = None

    def invoke(self, messages):
        self.invoked_with = messages
        if self._error:
            raise self._error
        return self._result


class _FakeModel:
    def __init__(self, result=None, error: Exception | None = None):
        self._structured = _FakeStructured(result, error)
        self.last_schema = None

    def with_structured_output(self, schema):
        self.last_schema = schema
        return self._structured


def _graph_for(result=None, error=None):
    model = _FakeModel(result, error)
    made = {}
    def make_model(**kw):
        made.update(kw)
        return model
    return build_cognition_graph(make_model), model, made


def test_routes_to_action_and_maps():
    out = ActionDecisionOut(intent="赶往灯塔", action=ActionSpec(kind="move", arg="灯塔"))
    graph, model, _ = _graph_for(result=out)
    res = graph.invoke({"mode": "action", "system": "s", "user": "u", "intent": "x"})
    dec = res["decision"]
    assert dec is not None
    assert dec.action_kind == "move" and dec.action_arg == "灯塔"
    assert model.last_schema is ActionDecisionOut       # 路由到 action 分支且用对 schema


def test_routes_to_dialogue_and_maps():
    out = DialogueDecisionOut(speech="我先走了", leave=True)
    graph, model, _ = _graph_for(result=out)
    res = graph.invoke({"mode": "dialogue", "system": "s", "user": "u",
                        "dialogue_with": "顾长风", "intent": "脱身"})
    dec = res["decision"]
    assert dec.speech == "我先走了" and dec.leave_dialogue is True
    assert dec.speech_to == "顾长风"
    assert model.last_schema is DialogueDecisionOut


def test_structured_failure_yields_none_for_fallback():
    graph, _, _ = _graph_for(error=RuntimeError("无函数调用支持"))
    res = graph.invoke({"mode": "action", "system": "s", "user": "u", "intent": "x"})
    assert res["decision"] is None
    assert "无函数调用" in res["error"]


def test_deep_and_max_tokens_passed_to_model_factory():
    out = ActionDecisionOut(action=ActionSpec(kind="idle"))
    graph, _, made = _graph_for(result=out)
    graph.invoke({"mode": "action", "system": "s", "user": "u", "deep": True, "max_tokens": 600})
    assert made.get("thinking") is True and made.get("max_tokens") == 600


# —— 通用 schema 路由的假模型(按 schema 返回不同对象)——
class _SchemaModel:
    def __init__(self, by_schema):
        self._by = by_schema

    def with_structured_output(self, schema):
        return _FakeStructured(self._by.get(schema))


def _graph_by_schema(by_schema):
    return build_cognition_graph(lambda **kw: _SchemaModel(by_schema))


def test_routes_to_bootstrap():
    graph = _graph_by_schema({BootstrapOut: BootstrapOut(goals=["去码头", "搜船屋", "回灯塔写手稿"])})
    res = graph.invoke({"mode": "bootstrap", "system": "s", "user": "u"})
    assert res["result"].goals == ["去码头", "搜船屋", "回灯塔写手稿"]


def test_routes_to_reflect():
    out = ReflectOut(insights=["顾长风可疑"], relations=[RelationDelta(name="顾长风", trust=-0.3)])
    graph = _graph_by_schema({ReflectOut: out})
    res = graph.invoke({"mode": "reflect", "system": "s", "user": "u"})
    assert res["result"].insights == ["顾长风可疑"]
    assert res["result"].relations[0].name == "顾长风"


def test_bootstrap_failure_yields_none():
    graph, _, _ = _graph_for(error=RuntimeError("boom"))
    res = graph.invoke({"mode": "bootstrap", "system": "s", "user": "u"})
    assert res["result"] is None
