"""Tests for the main Agent class."""

from __future__ import annotations

import pytest

from agent_scout import __version__
from agent_scout.agent import Agent
from agent_scout.db.models import AutonomyLevel, Task, TaskStatus
from agent_scout.events import Event, EventBus, EventType
from agent_scout.react_loop import Action, ActionType

# --- Mock implementations (shared with react_loop tests) ---


class MockLLM:
    def __init__(self, script: list[tuple[str, Action]]) -> None:
        self._script = list(script)
        self._call_count = 0

    async def think(self, task, steps, available_tools):
        if self._call_count < len(self._script):
            thought, action = self._script[self._call_count]
            self._call_count += 1
            return thought, action
        return "Done.", Action(action_type=ActionType.FINISH, message="Complete")

    async def reflect(self, task, steps, obs):
        return "ok"


class MockTools:
    def __init__(self, results: dict[str, str] | None = None):
        self._results = results or {}

    async def execute(self, tool_name, tool_input):
        return self._results.get(tool_name, f"Result from {tool_name}")

    def get_available_tools(self):
        return [{"name": "web_search", "description": "Search the web"}]


class MockUser:
    def __init__(self, responses: list[str] | None = None):
        self._responses = list(responses or ["yes"])
        self._idx = 0

    async def ask(self, message):
        if self._idx < len(self._responses):
            resp = self._responses[self._idx]
            self._idx += 1
            return resp
        return "ok"


@pytest.fixture
def bus():
    return EventBus()


# --- Tests ---


def test_version():
    assert __version__ == "0.1.0"


async def test_agent_completes_task(bus: EventBus):
    llm = MockLLM(
        [
            (
                "Searching.",
                Action(
                    action_type=ActionType.TOOL_CALL,
                    tool_name="web_search",
                    tool_input={"q": "flights"},
                ),
            ),
            (
                "Found it.",
                Action(
                    action_type=ActionType.FINISH,
                    message="Best: $450",
                    result={"price": 450},
                ),
            ),
        ]
    )
    tools = MockTools(results={"web_search": "BA $450"})
    agent = Agent(llm, tools, bus)

    task = Task(description="Find flights NYC to London")
    result = await agent.run_task(task)

    assert result.status == TaskStatus.COMPLETED
    assert result.result == {"price": 450}
    assert result.error is None


async def test_agent_fails_on_max_iterations(bus: EventBus):
    # LLM never finishes
    script = [
        (
            f"Step {i}",
            Action(
                action_type=ActionType.TOOL_CALL,
                tool_name="web_search",
                tool_input={},
            ),
        )
        for i in range(10)
    ]
    llm = MockLLM(script)
    tools = MockTools()
    agent = Agent(llm, tools, bus, max_iterations=3)

    task = Task(description="Infinite task")
    result = await agent.run_task(task)

    assert result.status == TaskStatus.FAILED
    assert "Max iterations" in result.error


async def test_agent_emits_lifecycle_events(bus: EventBus):
    received: list[Event] = []

    async def handler(event: Event):
        received.append(event)

    bus.subscribe(handler, event_type=None)

    llm = MockLLM([("Done.", Action(action_type=ActionType.FINISH, message="ok"))])
    tools = MockTools()
    agent = Agent(llm, tools, bus)

    task = Task(description="Test")
    await agent.run_task(task)

    event_types = [e.event_type for e in received]
    assert EventType.TASK_STATE_CHANGE in event_types
    assert EventType.AGENT_STARTED in event_types
    assert EventType.TASK_COMPLETED in event_types
    assert EventType.AGENT_STOPPED in event_types


async def test_agent_full_autonomy_no_user(bus: EventBus):
    """In full autonomy, agent doesn't use user interaction."""
    llm = MockLLM(
        [
            (
                "Need input.",
                Action(
                    action_type=ActionType.ASK_USER,
                    message="Budget?",
                ),
            ),
            ("Proceeding.", Action(action_type=ActionType.FINISH)),
        ]
    )
    tools = MockTools()
    user = MockUser(responses=["$500"])
    agent = Agent(llm, tools, bus, user=user, autonomy=AutonomyLevel.FULL)

    task = Task(description="Test")
    await agent.run_task(task)
    # In full autonomy, user.ask should NOT be called


async def test_agent_semi_autonomy_uses_user(bus: EventBus):
    """In semi autonomy, agent uses user interaction."""
    llm = MockLLM(
        [
            (
                "Need input.",
                Action(
                    action_type=ActionType.ASK_USER,
                    message="Budget?",
                ),
            ),
            ("Got it.", Action(action_type=ActionType.FINISH, message="ok")),
        ]
    )
    tools = MockTools()
    user = MockUser(responses=["$500"])
    agent = Agent(llm, tools, bus, user=user, autonomy=AutonomyLevel.SEMI)

    task = Task(description="Test", autonomy=AutonomyLevel.SEMI)
    result = await agent.run_task(task)

    assert result.status == TaskStatus.COMPLETED


async def test_agent_handles_llm_exception(bus: EventBus):
    """Agent handles exceptions from the LLM gracefully."""

    class ExplodingLLM:
        async def think(self, task, steps, tools):
            raise RuntimeError("LLM API is down")

        async def reflect(self, task, steps, obs):
            return ""

    tools = MockTools()
    agent = Agent(ExplodingLLM(), tools, bus)

    task = Task(description="Test")
    result = await agent.run_task(task)

    assert result.status == TaskStatus.FAILED
    assert "LLM API is down" in result.error


async def test_agent_stop_task(bus: EventBus):
    """Agent can stop a running task."""
    import asyncio

    llm = MockLLM(
        [
            (
                f"Step {i}",
                Action(
                    action_type=ActionType.TOOL_CALL,
                    tool_name="web_search",
                    tool_input={},
                ),
            )
            for i in range(100)
        ]
    )
    tools = MockTools()
    agent = Agent(llm, tools, bus, max_iterations=100)

    task = Task(description="Long task")

    async def stop_after_delay():
        await asyncio.sleep(0.01)
        await agent.stop_task(task.id)

    # Run both concurrently
    stop_coro = stop_after_delay()
    run_coro = agent.run_task(task)
    results = await asyncio.gather(run_coro, stop_coro)
    result_task = results[0]

    # Task should have been stopped early
    assert result_task.status == TaskStatus.FAILED


async def test_agent_active_task_ids(bus: EventBus):
    """active_task_ids reflects running tasks."""
    llm = MockLLM([("Done.", Action(action_type=ActionType.FINISH))])
    tools = MockTools()
    agent = Agent(llm, tools, bus)

    assert agent.active_task_ids == []

    task = Task(description="Test")
    await agent.run_task(task)

    # After completion, should be empty
    assert agent.active_task_ids == []
