from types import SimpleNamespace

from armorer.agent import (
    BaseAgent,
    ManagedAgent,
    _build_tool_error_retry_input,
    _extract_tool_events,
    _enrich_payload_with_background_job,
    _is_lightweight_conversation,
)
from armorer.security.policy import PolicyGuard


def _make_agent() -> BaseAgent:
    agent = BaseAgent.__new__(BaseAgent)
    agent.provider = "ollama"
    agent.model_name = "armorer-qwen3.5-9b"
    agent._strong_local_model = ""
    agent._hosted_fallback_model = "gpt-4o-mini"
    agent._allow_hosted_fallback = False
    agent.policy_guard = PolicyGuard.default()
    agent._config_manager = None
    return agent


def test_execute_retries_and_recovers_from_empty_final_output(monkeypatch):
    agent = _make_agent()
    managed = ManagedAgent(name="test", system_prompt="test", tools=[], model="dummy")

    calls = {"count": 0, "inputs": []}

    def fake_run_with_agent(candidate, user_input, structured_output_model=None):
        calls["count"] += 1
        calls["inputs"].append(user_input)
        if calls["count"] == 1:
            return SimpleNamespace(
                final_output='{"message": ""}',
                raw_responses=[],
                to_input_list=lambda: [{"role": "user", "content": "what agents do i have installed?"}],
            )
        return SimpleNamespace(
            final_output="You have 1 installed agent: openclaw.",
            raw_responses=[],
            to_input_list=lambda: [],
        )

    monkeypatch.setattr(agent, "_run_with_agent", fake_run_with_agent)

    result = agent.execute(managed, "what agents do i have installed?")

    assert calls["count"] == 2
    assert isinstance(calls["inputs"][1], list)
    assert "did not produce a usable final answer" in calls["inputs"][1][-1]["content"]
    assert "internal tools" in calls["inputs"][1][-1]["content"]
    assert "openclaw" in result


def test_execute_suggests_different_model_after_repeated_empty_outputs(monkeypatch):
    agent = _make_agent()
    managed = ManagedAgent(name="test", system_prompt="test", tools=[], model="dummy")

    def fake_run_with_agent(candidate, user_input, structured_output_model=None):
        return SimpleNamespace(
            final_output='{"message": ""}',
            raw_responses=[],
            to_input_list=lambda: [{"role": "user", "content": "show me the config for openclaw"}],
        )

    monkeypatch.setattr(agent, "_run_with_agent", fake_run_with_agent)

    result = agent.execute(managed, "show me the config for openclaw")

    assert "failed to produce a usable final answer" in result
    assert "armorer config" in result


def test_lightweight_conversation_detection():
    assert _is_lightweight_conversation("hi")
    assert _is_lightweight_conversation("hello")
    assert _is_lightweight_conversation("who are you?")
    assert not _is_lightweight_conversation("what agents do i have installed?")


def test_execute_retries_unknown_tool_error_with_correction(monkeypatch):
    agent = _make_agent()
    managed = ManagedAgent(name="test", system_prompt="test", tools=[], model="dummy")

    calls = {"count": 0, "inputs": []}

    def fake_run_with_agent(candidate, user_input, structured_output_model=None):
        calls["count"] += 1
        calls["inputs"].append(user_input)
        if calls["count"] == 1:
            raise RuntimeError("Tool job_response not found in agent armorer:core:task")
        return SimpleNamespace(
            final_output="I can uninstall `openclaw` for you. Confirm and I will proceed.",
            raw_responses=[],
            to_input_list=lambda: [],
        )

    monkeypatch.setattr(agent, "_run_with_agent", fake_run_with_agent)

    result = agent.execute(managed, "uninstall openclaw")

    assert calls["count"] == 2
    assert isinstance(calls["inputs"][1], list)
    assert "Do not invent tool names" in calls["inputs"][1][-1]["content"]
    assert "openclaw" in result


def test_build_tool_error_retry_input_explains_track_job_output_contract():
    corrected = _build_tool_error_retry_input(
        "install openclaw",
        "Tool job_response not found in agent armorer:core:task",
        1,
    )

    assert isinstance(corrected, list)
    assert "`track_job` is not a callable tool" in corrected[-1]["content"]
    assert '"ui_action":{"type":"track_job"' in corrected[-1]["content"]


def test_extract_tool_events_supports_function_call_items():
    res = SimpleNamespace(
        new_items=[
            SimpleNamespace(
                type="function_call",
                name="start_install_agent_job",
                call_id="call-1",
            ),
            SimpleNamespace(
                type="function_call_output",
                call_id="call-1",
                output={"ok": True, "job_id": "job-123", "message": "Installing openclaw..."},
            ),
        ]
    )

    events = _extract_tool_events(res)

    assert events[0]["tool"] == "start_install_agent_job"
    assert events[1]["tool"] == "start_install_agent_job"
    assert events[1]["output"]["job_id"] == "job-123"


def test_extract_tool_events_supports_to_input_item_wrappers():
    class WrappedItem:
        def __init__(self, payload):
            self._payload = payload

        def to_input_item(self):
            return dict(self._payload)

    res = SimpleNamespace(
        new_items=[
            WrappedItem(
                {
                    "type": "function_call",
                    "name": "start_uninstall_agent_job",
                    "call_id": "call-1",
                }
            ),
            WrappedItem(
                {
                    "type": "function_call_output",
                    "call_id": "call-1",
                    "output": {"ok": True, "job_id": "job-456", "message": "Uninstalling goliclaw..."},
                }
            ),
        ]
    )

    events = _extract_tool_events(res)

    assert events[0]["tool"] == "start_uninstall_agent_job"
    assert events[1]["tool"] == "start_uninstall_agent_job"
    assert events[1]["output"]["job_id"] == "job-456"


def test_execute_returns_track_job_payload_when_function_call_output_has_job(monkeypatch):
    agent = _make_agent()
    managed = ManagedAgent(name="test", system_prompt="test", tools=[], model="dummy")

    def fake_run_with_agent(candidate, user_input, structured_output_model=None):
        return SimpleNamespace(
            final_output="",
            raw_responses=[],
            new_items=[
                SimpleNamespace(
                    type="function_call",
                    name="start_install_agent_job",
                    call_id="call-1",
                ),
                SimpleNamespace(
                    type="function_call_output",
                    call_id="call-1",
                    output={"ok": True, "job_id": "job-123", "message": "Installing openclaw..."},
                ),
            ],
            to_input_list=lambda: [],
        )

    monkeypatch.setattr(agent, "_run_with_agent", fake_run_with_agent)

    result = agent.execute(managed, "install openclaw")

    assert isinstance(result, dict)
    assert result["message"] == "Installing openclaw..."
    assert result["ui_action"]["type"] == "track_job"
    assert result["ui_action"]["job_id"] == "job-123"


def test_execute_returns_track_job_payload_from_sdk_wrappers(monkeypatch):
    agent = _make_agent()
    managed = ManagedAgent(name="test", system_prompt="test", tools=[], model="dummy")

    class WrappedItem:
        def __init__(self, payload):
            self._payload = payload

        def to_input_item(self):
            return dict(self._payload)

    def fake_run_with_agent(candidate, user_input, structured_output_model=None):
        return SimpleNamespace(
            final_output="",
            raw_responses=[],
            new_items=[
                WrappedItem(
                    {
                        "type": "function_call",
                        "name": "start_uninstall_agent_job",
                        "call_id": "call-1",
                    }
                ),
                WrappedItem(
                    {
                        "type": "function_call_output",
                        "call_id": "call-1",
                        "output": {"ok": True, "job_id": "job-456", "message": "Uninstalling goliclaw..."},
                    }
                ),
            ],
            to_input_list=lambda: [],
        )

    monkeypatch.setattr(agent, "_run_with_agent", fake_run_with_agent)

    result = agent.execute(managed, "uninstall goliclaw")

    assert isinstance(result, dict)
    assert result["message"] == "Uninstalling goliclaw..."
    assert result["ui_action"]["type"] == "track_job"
    assert result["ui_action"]["job_id"] == "job-456"


def test_enrich_payload_with_background_job_injects_track_job():
    payload = {"message": "Installing openclaw... Started in the background."}
    events = [
        {
            "type": "tool_output",
            "tool": "start_install_agent_job",
            "output": {"ok": True, "job_id": "job-123", "message": "Installing openclaw..."},
        }
    ]

    enriched = _enrich_payload_with_background_job(payload, events)

    assert enriched["ui_action"]["type"] == "track_job"
    assert enriched["ui_action"]["job_id"] == "job-123"
    assert enriched["message"] == "Installing openclaw... Started in the background."
