"""Tests for BaseAgent helpers and ToolUsingAgent."""

from __future__ import annotations

from unittest.mock import MagicMock

from openjarvis.agents._stubs import (
    AgentContext,
    AgentResult,
    BaseAgent,
    ToolUsingAgent,
)
from openjarvis.core.events import EventBus, EventType
from openjarvis.core.types import Conversation, Message, Role, ToolCall, ToolResult
from openjarvis.tools._stubs import BaseTool, ToolSpec

# ---------------------------------------------------------------------------
# Concrete subclass for testing
# ---------------------------------------------------------------------------


class _ConcreteAgent(BaseAgent):
    agent_id = "test_agent"

    def run(self, input, context=None, **kwargs):
        return AgentResult(content="test", turns=1)


class _ConcreteToolAgent(ToolUsingAgent):
    agent_id = "test_tool_agent"

    def run(self, input, context=None, **kwargs):
        return AgentResult(content="test", turns=1)


class _DummyTool(BaseTool):
    tool_id = "dummy"

    @property
    def spec(self) -> ToolSpec:
        return ToolSpec(
            name="dummy",
            description="Dummy tool.",
            parameters={"type": "object", "properties": {}},
        )

    def execute(self, **params) -> ToolResult:
        return ToolResult(tool_name="dummy", content="ok", success=True)


class _ConfirmTool(BaseTool):
    tool_id = "confirm"

    @property
    def spec(self) -> ToolSpec:
        return ToolSpec(
            name="confirm",
            description="Confirmation-required tool.",
            parameters={"type": "object", "properties": {}},
            requires_confirmation=True,
        )

    def execute(self, **params) -> ToolResult:
        return ToolResult(tool_name="confirm", content="confirmed", success=True)


# ---------------------------------------------------------------------------
# BaseAgent tests
# ---------------------------------------------------------------------------


class TestBaseAgentInit:
    def test_stores_engine_and_model(self):
        engine = MagicMock()
        agent = _ConcreteAgent(engine, "test-model")
        assert agent._engine is engine
        assert agent._model == "test-model"

    def test_default_params(self):
        engine = MagicMock()
        agent = _ConcreteAgent(engine, "m")
        assert agent._temperature == 0.7
        assert agent._max_tokens == 1024
        assert agent._bus is None

    def test_custom_params(self):
        bus = EventBus()
        engine = MagicMock()
        agent = _ConcreteAgent(
            engine,
            "m",
            bus=bus,
            temperature=0.1,
            max_tokens=256,
        )
        assert agent._temperature == 0.1
        assert agent._max_tokens == 256
        assert agent._bus is bus


class TestAcceptsTools:
    def test_base_agent_no_tools(self):
        assert _ConcreteAgent.accepts_tools is False

    def test_tool_using_agent_accepts_tools(self):
        assert _ConcreteToolAgent.accepts_tools is True


class TestEmitTurnStart:
    def test_with_bus(self):
        bus = EventBus(record_history=True)
        engine = MagicMock()
        agent = _ConcreteAgent(engine, "m", bus=bus)
        agent._emit_turn_start("hello")
        events = [e for e in bus.history if e.event_type == EventType.AGENT_TURN_START]
        assert len(events) == 1
        assert events[0].data["agent"] == "test_agent"
        assert events[0].data["input"] == "hello"

    def test_without_bus(self):
        engine = MagicMock()
        agent = _ConcreteAgent(engine, "m")
        # Should not raise
        agent._emit_turn_start("hello")


class TestEmitTurnEnd:
    def test_with_bus(self):
        bus = EventBus(record_history=True)
        engine = MagicMock()
        agent = _ConcreteAgent(engine, "m", bus=bus)
        agent._emit_turn_end(turns=3, custom="val")
        events = [e for e in bus.history if e.event_type == EventType.AGENT_TURN_END]
        assert len(events) == 1
        assert events[0].data["agent"] == "test_agent"
        assert events[0].data["turns"] == 3
        assert events[0].data["custom"] == "val"

    def test_without_bus(self):
        engine = MagicMock()
        agent = _ConcreteAgent(engine, "m")
        agent._emit_turn_end(turns=1)


class TestBuildMessages:
    def test_default_system_prompt_injected(self):
        engine = MagicMock()
        agent = _ConcreteAgent(engine, "m")
        messages = agent._build_messages("hello")
        assert len(messages) == 2
        assert messages[0].role == Role.SYSTEM
        assert "local" in messages[0].content.lower()
        assert messages[1].role == Role.USER
        assert messages[1].content == "hello"

    def test_explicit_system_prompt_overrides_default(self):
        engine = MagicMock()
        agent = _ConcreteAgent(engine, "m")
        messages = agent._build_messages("hello", system_prompt="Be helpful.")
        assert len(messages) == 2
        assert messages[0].role == Role.SYSTEM
        assert messages[0].content == "Be helpful."
        assert messages[1].role == Role.USER

    def test_empty_config_default_no_system_message(self, monkeypatch):
        from openjarvis.core.config import JarvisConfig

        empty_cfg = JarvisConfig()
        empty_cfg.agent.default_system_prompt = ""
        monkeypatch.setattr("openjarvis.agents._stubs.load_config", lambda: empty_cfg)

        engine = MagicMock()
        agent = _ConcreteAgent(engine, "m")
        messages = agent._build_messages("hello")
        assert len(messages) == 1
        assert messages[0].role == Role.USER

    def test_with_context(self):
        engine = MagicMock()
        agent = _ConcreteAgent(engine, "m")
        conv = Conversation()
        conv.add(Message(role=Role.USER, content="prev"))
        conv.add(Message(role=Role.ASSISTANT, content="reply"))
        ctx = AgentContext(conversation=conv)
        messages = agent._build_messages("new", ctx)
        # default system prompt + 2 context + 1 user
        assert messages[-1].content == "new"
        assert messages[-2].content == "reply"
        assert messages[-3].content == "prev"

    def test_with_system_prompt_and_context(self):
        engine = MagicMock()
        agent = _ConcreteAgent(engine, "m")
        conv = Conversation()
        conv.add(Message(role=Role.USER, content="prev"))
        ctx = AgentContext(conversation=conv)
        messages = agent._build_messages(
            "new",
            ctx,
            system_prompt="System.",
        )
        assert len(messages) == 3
        assert messages[0].role == Role.SYSTEM
        assert messages[0].content == "System."
        assert messages[1].content == "prev"
        assert messages[2].content == "new"


class TestGenerate:
    def test_delegates_to_engine(self):
        engine = MagicMock()
        engine.generate.return_value = {"content": "hi"}
        agent = _ConcreteAgent(engine, "m", temperature=0.5, max_tokens=100)
        result = agent._generate([Message(role=Role.USER, content="hi")])
        assert result["content"] == "hi"
        engine.generate.assert_called_once()
        call_kwargs = engine.generate.call_args[1]
        assert call_kwargs["model"] == "m"
        assert call_kwargs["temperature"] == 0.5
        assert call_kwargs["max_tokens"] == 100

    def test_extra_kwargs(self):
        engine = MagicMock()
        engine.generate.return_value = {"content": "hi"}
        agent = _ConcreteAgent(engine, "m")
        agent._generate([Message(role=Role.USER, content="hi")], tools=["t"])
        call_kwargs = engine.generate.call_args[1]
        assert call_kwargs["tools"] == ["t"]


class TestMaxTurnsResult:
    def test_default_message(self):
        bus = EventBus(record_history=True)
        engine = MagicMock()
        agent = _ConcreteAgent(engine, "m", bus=bus)
        tr = [ToolResult(tool_name="t", content="x", success=True)]
        result = agent._max_turns_result(tr, turns=5)
        assert result.metadata["max_turns_exceeded"] is True
        assert result.turns == 5
        assert "Maximum turns" in result.content
        assert result.tool_results == tr
        # Should also emit turn end
        events = [e for e in bus.history if e.event_type == EventType.AGENT_TURN_END]
        assert len(events) == 1
        assert events[0].data["max_turns_exceeded"] is True

    def test_custom_content(self):
        engine = MagicMock()
        agent = _ConcreteAgent(engine, "m")
        result = agent._max_turns_result([], turns=3, content="custom msg")
        assert result.content == "custom msg"


class TestStripThinkTags:
    def test_full_think_block(self):
        text = "<think>internal reasoning</think>Answer here."
        assert BaseAgent._strip_think_tags(text) == "Answer here."

    def test_bare_closing_tag(self):
        text = "some reasoning</think>Answer here."
        assert BaseAgent._strip_think_tags(text) == "Answer here."

    def test_no_tags(self):
        text = "Just normal text."
        assert BaseAgent._strip_think_tags(text) == "Just normal text."

    def test_multiline_think(self):
        text = "<think>\nline1\nline2\n</think>\nFinal."
        assert BaseAgent._strip_think_tags(text) == "Final."

    def test_empty_after_strip(self):
        text = "<think>all thinking</think>"
        assert BaseAgent._strip_think_tags(text) == ""


# ---------------------------------------------------------------------------
# ToolUsingAgent tests
# ---------------------------------------------------------------------------


class TestToolUsingAgent:
    def test_creates_executor(self):
        engine = MagicMock()
        agent = _ConcreteToolAgent(engine, "m", tools=[_DummyTool()])
        assert agent._executor is not None
        assert len(agent._tools) == 1

    def test_default_max_turns(self):
        engine = MagicMock()
        agent = _ConcreteToolAgent(engine, "m")
        assert agent._max_turns == 10

    def test_custom_max_turns(self):
        engine = MagicMock()
        agent = _ConcreteToolAgent(engine, "m", max_turns=5)
        assert agent._max_turns == 5

    def test_empty_tools(self):
        engine = MagicMock()
        agent = _ConcreteToolAgent(engine, "m")
        assert agent._tools == []

    def test_inherits_base_helpers(self):
        bus = EventBus(record_history=True)
        engine = MagicMock()
        agent = _ConcreteToolAgent(engine, "m", bus=bus)
        agent._emit_turn_start("hi")
        events = [e for e in bus.history if e.event_type == EventType.AGENT_TURN_START]
        assert len(events) == 1

    def test_propagates_confirmation_settings_to_executor(self):
        engine = MagicMock()
        confirm = MagicMock(return_value=True)
        agent = _ConcreteToolAgent(
            engine,
            "m",
            tools=[_ConfirmTool()],
            interactive=True,
            confirm_callback=confirm,
        )

        result = agent._executor.execute(
            ToolCall(id="1", name="confirm", arguments="{}")
        )

        assert result.success is True
        confirm.assert_called_once()
