import sys
from types import SimpleNamespace
from unittest.mock import Mock

from langchain_core.messages import AIMessage, HumanMessage

sys.modules.setdefault("trafilatura", Mock())

from app.agents.graph import _build_system_messages, _run_sub_commander
from app.agents.state import AgentRole


def _base_state(message: str, user_llm_config: dict | None = None) -> dict:
    return {
        "messages": [HumanMessage(content=message)],
        "user_id": "u1",
        "conversation_id": "c1",
        "current_agent": AgentRole.MASTER,
        "active_agents": [AgentRole.MASTER],
        "current_sub_commander": None,
        "active_sub_commanders": [],
        "sub_commander_trace": [],
        "pending_tasks": [],
        "completed_tasks": [],
        "tool_calls": [],
        "last_tool_result": None,
        "action_results": [],
        "created_entities": [],
        "tool_strategy_used": None,
        "provider_capabilities": None,
        "fallback_parse_error": None,
        "knowledge_context": None,
        "graph_context": None,
        "schedule_context_summary": None,
        "plan": None,
        "plan_steps": [],
        "analysis_report": None,
        "final_response": None,
        "should_respond": True,
        "memory_context": "memory context",
        "current_datetime_context": "CURRENT_TIME: 2026-03-28T12:00:00+08:00",
        "current_datetime_reference": {
            "current_time_iso": "2026-03-28T12:00:00+08:00",
            "current_date_iso": "2026-03-28",
            "timezone": "UTC",
        },
        "user_llm_config": user_llm_config,
    }


class FakeTool:
    def __init__(self, name: str, result: str):
        self.name = name
        self.result = result
        self.invocations: list[dict] = []

    def invoke(self, args: dict):
        self.invocations.append(args)
        return self.result


class SingleSystemMessageLLM:
    def __init__(self):
        self.calls = 0
        self.system_message_counts: list[int] = []
        self._jarvis_provider_capabilities = SimpleNamespace(
            provider="minimax",
            supports_native_tools=False,
            preferred_tool_strategy="json_fallback",
        )

    async def ainvoke(self, messages):
        self.calls += 1
        self.system_message_counts.append(
            sum(1 for message in messages if getattr(message, "type", None) == "system")
        )
        if self.system_message_counts[-1] != 1:
            raise AssertionError(
                f"expected exactly one system message, got {self.system_message_counts[-1]}"
            )
        if self.calls == 1:
            return AIMessage(
                content=(
                    '{"mode":"tool_call","tool_calls":[{"name":"create_reminder",'
                    '"arguments":{"title":"blanket","reminder_at":"\\u660e\\u5929 09:00"}}]}'
                )
            )
        return AIMessage(content="created reminder for blanket")


def test_build_system_messages_includes_structured_continuity_summary():
    state = _base_state("创建")
    state["pending_action"] = {
        "type": "schedule_creation",
        "summary": "为周报安排明天下午提醒",
        "status": "pending",
    }
    state["routing_decision"] = {
        "target_agent": AgentRole.SCHEDULE_PLANNER.value,
        "reason": "continue_pending_action",
    }
    state["continuity_state"] = {"status": "fresh"}

    messages = _build_system_messages(
        state,
        "manager prompt",
        AgentRole.SCHEDULE_PLANNER,
        "schedule_planning",
    )

    system_text = "\n\n".join(str(getattr(message, "content", "")) for message in messages)
    assert "pending_action" in system_text
    assert "schedule_creation" in system_text
    assert "continue_pending_action" in system_text
    assert "为周报安排明天下午提醒" in system_text


def test_build_system_messages_skips_structured_continuity_when_pending_action_is_not_pending():
    state = _base_state("创建")
    state["pending_action"] = {
        "type": "schedule_creation",
        "summary": "为周报安排明天下午提醒",
        "status": "completed",
    }
    state["routing_decision"] = {
        "target_agent": AgentRole.SCHEDULE_PLANNER.value,
        "reason": "continue_pending_action",
    }
    state["continuity_state"] = {"status": "fresh"}

    messages = _build_system_messages(
        state,
        "manager prompt",
        AgentRole.SCHEDULE_PLANNER,
        "schedule_planning",
    )

    system_text = "\n\n".join(str(getattr(message, "content", "")) for message in messages)
    assert "structured_continuity" not in system_text
    assert "continue_pending_action" not in system_text


def test_build_system_messages_skips_structured_continuity_when_routing_reason_is_not_continuation():
    state = _base_state("创建")
    state["pending_action"] = {
        "type": "schedule_creation",
        "summary": "为周报安排明天下午提醒",
        "status": "pending",
    }
    state["routing_decision"] = {
        "target_agent": AgentRole.SCHEDULE_PLANNER.value,
        "reason": "initial_schedule_detection",
    }
    state["continuity_state"] = {"status": "fresh"}

    messages = _build_system_messages(
        state,
        "manager prompt",
        AgentRole.SCHEDULE_PLANNER,
        "schedule_planning",
    )

    system_text = "\n\n".join(str(getattr(message, "content", "")) for message in messages)
    assert "structured_continuity" not in system_text
    assert "continue_pending_action" not in system_text


def test_build_system_messages_skips_structured_continuity_when_routing_decision_missing():
    state = _base_state("创建")
    state["pending_action"] = {
        "type": "schedule_creation",
        "summary": "为周报安排明天下午提醒",
    }
    state["routing_decision"] = None

    messages = _build_system_messages(
        state,
        "manager prompt",
        AgentRole.SCHEDULE_PLANNER,
        "schedule_planning",
    )

    system_text = "\n\n".join(str(getattr(message, "content", "")) for message in messages)
    assert "pending_action" not in system_text
    assert "schedule_creation" not in system_text
    assert "为周报安排明天下午提醒" not in system_text


def test_build_system_messages_skips_stale_structured_continuity_for_unrelated_new_request():
    state = _base_state(
        "帮我搜索 Rust 异步 trait 最佳实践",
        {
            "provider": "openai",
            "model": "MiniMax-M2.7-highspeed",
            "base_url": "https://api.minimaxi.com/v1",
        },
    )
    state["current_agent"] = AgentRole.SCHEDULE_PLANNER
    state["pending_action"] = {
        "type": "schedule_creation",
        "summary": "为周报安排明天下午提醒",
        "status": "pending",
    }
    state["routing_decision"] = {
        "target_agent": AgentRole.SCHEDULE_PLANNER.value,
        "reason": "continue_pending_action",
    }
    state["continuity_state"] = {
        "status": "stale",
        "override_reason": "new_explicit_request",
    }

    messages = _build_system_messages(
        state,
        "manager prompt",
        AgentRole.SCHEDULE_PLANNER,
        "schedule_planning",
    )

    system_text = "\n\n".join(str(getattr(message, "content", "")) for message in messages)
    assert "structured_continuity" not in system_text
    assert "pending_action" not in system_text
    assert "continue_pending_action" not in system_text


def test_build_system_messages_uses_role_scoped_context_instead_of_raw_memory_blob():
    state = _base_state("帮我搜索 Rust 异步 trait 最佳实践")
    state["memory_context"] = "【用户记忆】\n- 用户喜欢燕麦拿铁。\n\n【之前对话摘要】\n[对话摘要1] 之前聊过提醒。\n\n【知识大脑】\n- Rust Async: trait object 需要 pin。"
    state["schedule_context_summary"] = "【用户记忆】\n- 用户喜欢燕麦拿铁。\n\n【之前对话摘要】\n[对话摘要1] 之前聊过提醒。"
    state["knowledge_context"] = "【知识大脑】\n- Rust Async: trait object 需要 pin。"
    state["analysis_report"] = "【之前对话摘要】\n[对话摘要1] 之前聊过提醒。\n\n【知识大脑】\n- Rust Async: trait object 需要 pin。"

    messages = _build_system_messages(
        state,
        "manager prompt",
        AgentRole.LIBRARIAN,
        "librarian_retrieval",
    )

    system_text = "\n\n".join(str(getattr(message, "content", "")) for message in messages)
    assert "角色上下文" in system_text
    assert "【知识大脑】" in system_text
    assert "Rust Async" in system_text
    assert "用户喜欢燕麦拿铁" not in system_text
    assert "记忆上下文" not in system_text


def test_build_system_messages_keeps_fresh_structured_continuity_for_matching_followup():
    state = _base_state(
        "创建",
        {
            "provider": "openai",
            "model": "MiniMax-M2.7-highspeed",
            "base_url": "https://api.minimaxi.com/v1",
        },
    )
    state["current_agent"] = AgentRole.SCHEDULE_PLANNER
    state["pending_action"] = {
        "type": "schedule_creation",
        "summary": "为周报安排明天下午提醒",
        "status": "pending",
    }
    state["routing_decision"] = {
        "target_agent": AgentRole.SCHEDULE_PLANNER.value,
        "reason": "continue_pending_action",
    }
    state["continuity_state"] = {
        "status": "fresh",
    }

    messages = _build_system_messages(
        state,
        "manager prompt",
        AgentRole.SCHEDULE_PLANNER,
        "schedule_planning",
    )

    system_text = "\n\n".join(str(getattr(message, "content", "")) for message in messages)
    assert "pending_action" in system_text
    assert "continue_pending_action" in system_text


async def test_run_sub_commander_coalesces_system_messages_for_openai_compatible_provider(
    monkeypatch,
):
    fake_llm = SingleSystemMessageLLM()
    fake_tool = FakeTool("create_reminder", "created reminder: blanket @ tomorrow 09:00")

    monkeypatch.setattr("app.agents.graph._get_llm_for_state", lambda state: fake_llm)
    monkeypatch.setitem(
        __import__("app.agents.graph", fromlist=["SUB_COMMANDER_TOOLSETS"]).SUB_COMMANDER_TOOLSETS,
        "schedule_planning",
        [fake_tool],
    )

    state = _base_state(
        "给我设置明天的提醒，提醒我收被子",
        {
            "provider": "openai",
            "model": "MiniMax-M2.7-highspeed",
            "base_url": "https://api.minimaxi.com/v1",
        },
    )
    state["current_agent"] = AgentRole.SCHEDULE_PLANNER

    result = await _run_sub_commander(
        state,
        AgentRole.SCHEDULE_PLANNER,
        "manager prompt",
        "给我设置明天的提醒，提醒我收被子",
        use_tools=True,
    )

    assert fake_llm.system_message_counts == [1, 1]
    assert result["tool_strategy_used"] == "json_fallback"
    assert fake_tool.invocations == [{"title": "blanket", "reminder_at": "2026-03-29T09:00:00"}]
    assert result["final_response"] == "created reminder for blanket"
