"""ReAct (Reason + Act) reasoning loop implementation.

The core reasoning engine that drives the agent through iterative cycles of:
  THINK → ACT → OBSERVE → REFLECT → repeat until FINISH or max iterations.
"""

from __future__ import annotations

import time
from dataclasses import dataclass, field
from enum import StrEnum
from typing import Any, Protocol

from agent_scout.events import Event, EventBus, EventType


class ActionType(StrEnum):
    TOOL_CALL = "tool_call"
    ASK_USER = "ask_user"
    FINISH = "finish"


@dataclass
class Action:
    """An action the agent wants to take."""

    action_type: ActionType
    tool_name: str = ""
    tool_input: dict[str, Any] = field(default_factory=dict)
    message: str = ""  # For ask_user or finish
    result: dict[str, Any] = field(default_factory=dict)  # For finish


@dataclass
class Step:
    """A single step in the ReAct loop."""

    step_number: int
    thought: str
    action: Action
    observation: str = ""
    reflection: str = ""
    duration_ms: float = 0.0


class LLMProvider(Protocol):
    """Protocol for LLM providers used by the ReAct loop."""

    async def think(
        self,
        task: str,
        steps: list[Step],
        available_tools: list[dict[str, Any]],
    ) -> tuple[str, Action]:
        """Given the task and history, produce a thought and next action."""
        ...

    async def reflect(
        self,
        task: str,
        steps: list[Step],
        latest_observation: str,
    ) -> str:
        """Reflect on the latest observation and decide if we're on track."""
        ...


class ToolExecutor(Protocol):
    """Protocol for executing tool calls."""

    async def execute(self, tool_name: str, tool_input: dict[str, Any]) -> str:
        """Execute a tool and return the result as a string."""
        ...

    def get_available_tools(self) -> list[dict[str, Any]]:
        """Return metadata about available tools for the LLM."""
        ...


class UserInteraction(Protocol):
    """Protocol for asking the user questions."""

    async def ask(self, message: str) -> str:
        """Ask the user a question and return their response."""
        ...


@dataclass
class ReActConfig:
    """Configuration for the ReAct loop."""

    max_iterations: int = 25
    enable_reflection: bool = True


class ReActLoop:
    """The core reasoning engine.

    Drives iterative Thought→Action→Observation cycles until the agent
    finishes the task, exceeds max iterations, or encounters an error.
    """

    def __init__(
        self,
        llm: LLMProvider,
        tools: ToolExecutor,
        event_bus: EventBus,
        user: UserInteraction | None = None,
        config: ReActConfig | None = None,
    ) -> None:
        self._llm = llm
        self._tools = tools
        self._bus = event_bus
        self._user = user
        self._config = config or ReActConfig()
        self._steps: list[Step] = []
        self._running = False

    @property
    def steps(self) -> list[Step]:
        return list(self._steps)

    @property
    def is_running(self) -> bool:
        return self._running

    async def run(self, task: str, task_id: str | None = None) -> list[Step]:
        """Execute the full ReAct loop for a given task.

        Returns the list of steps taken.
        """
        self._steps = []
        self._running = True

        try:
            for i in range(1, self._config.max_iterations + 1):
                if not self._running:
                    break

                step = await self._execute_step(i, task, task_id)
                self._steps.append(step)

                if step.action.action_type == ActionType.FINISH:
                    break

            return self._steps
        finally:
            self._running = False

    async def _execute_step(self, step_number: int, task: str, task_id: str | None) -> Step:
        """Execute a single Think→Act→Observe→Reflect cycle."""
        start = time.time()

        # 1. THINK — LLM reasons about what to do next
        available_tools = self._tools.get_available_tools()
        thought, action = await self._llm.think(task, self._steps, available_tools)

        await self._bus.emit(
            Event(
                event_type=EventType.THOUGHT,
                data={"step": step_number, "thought": thought},
                task_id=task_id,
            )
        )

        await self._bus.emit(
            Event(
                event_type=EventType.ACTION,
                data={
                    "step": step_number,
                    "action_type": action.action_type.value,
                    "tool_name": action.tool_name,
                    "message": action.message,
                },
                task_id=task_id,
            )
        )

        # 2. ACT — Execute the chosen action
        observation = ""

        if action.action_type == ActionType.TOOL_CALL:
            observation = await self._execute_tool(action, task_id)

        elif action.action_type == ActionType.ASK_USER:
            if self._user:
                observation = await self._user.ask(action.message)
                await self._bus.emit(
                    Event(
                        event_type=EventType.USER_APPROVAL_RECEIVED,
                        data={"response": observation},
                        task_id=task_id,
                    )
                )
            else:
                observation = "No user interaction available."

        elif action.action_type == ActionType.FINISH:
            observation = "Task completed."

        # 3. OBSERVE — Record what happened
        await self._bus.emit(
            Event(
                event_type=EventType.OBSERVATION,
                data={"step": step_number, "observation": observation[:500]},
                task_id=task_id,
            )
        )

        # 4. REFLECT — Optionally reflect on progress
        reflection = ""
        if self._config.enable_reflection and action.action_type != ActionType.FINISH:
            reflection = await self._llm.reflect(task, self._steps, observation)
            await self._bus.emit(
                Event(
                    event_type=EventType.REFLECTION,
                    data={"step": step_number, "reflection": reflection},
                    task_id=task_id,
                )
            )

        duration = (time.time() - start) * 1000
        return Step(
            step_number=step_number,
            thought=thought,
            action=action,
            observation=observation,
            reflection=reflection,
            duration_ms=duration,
        )

    async def _execute_tool(self, action: Action, task_id: str | None) -> str:
        """Execute a tool call with event emission."""
        await self._bus.emit(
            Event(
                event_type=EventType.TOOL_CALL_START,
                data={"tool": action.tool_name, "input": action.tool_input},
                task_id=task_id,
            )
        )

        try:
            result = await self._tools.execute(action.tool_name, action.tool_input)
            await self._bus.emit(
                Event(
                    event_type=EventType.TOOL_CALL_END,
                    data={
                        "tool": action.tool_name,
                        "success": True,
                        "output_preview": result[:200],
                    },
                    task_id=task_id,
                )
            )
            return result
        except Exception as e:
            error_msg = f"Tool error: {e}"
            await self._bus.emit(
                Event(
                    event_type=EventType.TOOL_ERROR,
                    data={"tool": action.tool_name, "error": str(e)},
                    task_id=task_id,
                )
            )
            return error_msg

    def stop(self) -> None:
        """Stop the loop after the current step completes."""
        self._running = False
