"""
Agent Core

The main Agent class that implements the agentic execution loop:
1. Receive user request
2. Plan and decide on tool calls
3. Execute tools with visual feedback
4. Process results and continue or complete
"""

import json
import re
from dataclasses import dataclass, field
from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING
from enum import Enum

from .tools.base import Tool, ToolResult, ToolRegistry, ToolPermission, get_default_registry
from .tools.file_tools import register_file_tools
from .tools.search_tools import register_search_tools
from .tools.bash_tool import register_bash_tools
from .tools.task_tool import register_task_tools, TaskTool
from .tools.web_tools import register_web_tools
from .tools.notebook_tools import register_notebook_tools

# Import visual feedback components
try:
    from ..cli_ui import (
        ActionSpinner, Color, Icons,
        status, thinking, success, error, warning, info,
        log_action
    )
    HAS_CLI_UI = True
except ImportError:
    HAS_CLI_UI = False


class AgentState(Enum):
    """States of the agent execution"""
    IDLE = "idle"
    THINKING = "thinking"
    EXECUTING_TOOL = "executing_tool"
    WAITING_APPROVAL = "waiting_approval"
    COMPLETED = "completed"
    ERROR = "error"


@dataclass
class AgentConfig:
    """Configuration for an Agent"""
    max_iterations: int = 50
    max_tool_retries: int = 3
    tool_permissions: Dict[str, ToolPermission] = field(default_factory=dict)
    auto_approve_tools: List[str] = field(default_factory=lambda: [
        "Read", "Glob", "Grep", "TodoWrite"
    ])
    require_approval_tools: List[str] = field(default_factory=lambda: [
        "Write", "Edit", "Bash", "Task", "WebFetch"
    ])
    show_tool_output: bool = True
    verbose: bool = False


@dataclass
class ToolCall:
    """Represents a tool call parsed from LLM output"""
    name: str
    parameters: Dict[str, Any]
    raw_text: str = ""


class Agent:
    """
    Main Agent class implementing the agentic execution loop.

    The agent:
    1. Takes a user request
    2. Uses an LLM to decide what tools to call
    3. Executes tools with permission checks
    4. Provides visual feedback
    5. Continues until task is complete
    """

    SYSTEM_PROMPT = """You are NC1709, an AI assistant with access to tools for completing tasks.

You have access to these tools:
{tools_description}

## How to use tools

To use a tool, include a tool call in your response using this exact format:
```tool
{{"tool": "ToolName", "parameters": {{"param1": "value1", "param2": "value2"}}}}
```

## Guidelines

1. **Read before writing**: Always read files before modifying them
2. **Be precise**: Use exact file paths and specific parameters
3. **Explain your actions**: Briefly explain what you're doing and why
4. **Handle errors**: If a tool fails, try to understand why and fix it
5. **Complete the task**: Keep working until the task is fully complete
6. **Ask if unclear**: Use AskUser if you need clarification

## Current context

Working directory: {cwd}
"""

    def __init__(
        self,
        llm=None,
        config: AgentConfig = None,
        registry: ToolRegistry = None,
        parent_agent: "Agent" = None,
    ):
        """Initialize the agent

        Args:
            llm: LLM adapter for generating responses
            config: Agent configuration
            registry: Tool registry (creates default if None)
            parent_agent: Parent agent if this is a sub-agent
        """
        self.llm = llm
        self.config = config or AgentConfig()
        self.registry = registry or self._create_default_registry()
        self.parent_agent = parent_agent

        self.state = AgentState.IDLE
        self.iteration_count = 0
        self.conversation_history: List[Dict[str, str]] = []
        self.tool_results: List[ToolResult] = []

        # Visual feedback
        self._spinner: Optional[ActionSpinner] = None

        # Apply permission settings
        self._apply_permission_config()

    def _create_default_registry(self) -> ToolRegistry:
        """Create and populate default tool registry"""
        registry = ToolRegistry()

        # Register all built-in tools
        register_file_tools(registry)
        register_search_tools(registry)
        register_bash_tools(registry)
        task_tool = register_task_tools(registry, parent_agent=self)
        register_web_tools(registry)
        register_notebook_tools(registry)

        return registry

    def _apply_permission_config(self) -> None:
        """Apply permission configuration to registry"""
        # Set auto-approve tools
        for tool_name in self.config.auto_approve_tools:
            self.registry.set_permission(tool_name, ToolPermission.AUTO)

        # Set require-approval tools
        for tool_name in self.config.require_approval_tools:
            self.registry.set_permission(tool_name, ToolPermission.ASK)

        # Apply custom overrides
        for tool_name, permission in self.config.tool_permissions.items():
            self.registry.set_permission(tool_name, permission)

    def run(self, user_request: str) -> str:
        """
        Run the agent on a user request.

        Args:
            user_request: The user's request or task

        Returns:
            Final response or result
        """
        self.state = AgentState.THINKING
        self.iteration_count = 0
        self.conversation_history = []
        self.tool_results = []

        # Build system prompt with tools
        import os
        system_prompt = self.SYSTEM_PROMPT.format(
            tools_description=self.registry.get_tools_prompt(),
            cwd=os.getcwd(),
        )

        # Add user request
        self.conversation_history.append({
            "role": "system",
            "content": system_prompt,
        })
        self.conversation_history.append({
            "role": "user",
            "content": user_request,
        })

        # Main execution loop
        while self.iteration_count < self.config.max_iterations:
            self.iteration_count += 1

            if HAS_CLI_UI:
                thinking(f"Iteration {self.iteration_count}...")

            try:
                # Get LLM response
                response = self._get_llm_response()

                # Parse for tool calls
                tool_calls = self._parse_tool_calls(response)

                if not tool_calls:
                    # No tool calls - agent is done or providing final response
                    self.state = AgentState.COMPLETED
                    return self._clean_response(response)

                # Execute tool calls
                all_results = []
                for tool_call in tool_calls:
                    result = self._execute_tool_call(tool_call)
                    all_results.append(result)
                    self.tool_results.append(result)

                # Add results to conversation
                results_text = self._format_tool_results(all_results)
                self.conversation_history.append({
                    "role": "assistant",
                    "content": response,
                })
                self.conversation_history.append({
                    "role": "user",
                    "content": f"Tool results:\n{results_text}\n\nContinue with the task.",
                })

            except Exception as e:
                self.state = AgentState.ERROR
                if HAS_CLI_UI:
                    error(f"Agent error: {e}")
                return f"Error during execution: {e}"

        # Max iterations reached
        self.state = AgentState.COMPLETED
        if HAS_CLI_UI:
            warning(f"Reached maximum iterations ({self.config.max_iterations})")
        return "Task incomplete - reached maximum iterations."

    def _get_llm_response(self) -> str:
        """Get response from LLM"""
        if self.llm is None:
            raise ValueError("No LLM configured for agent")

        # Build messages for LLM
        messages = self.conversation_history.copy()

        # Get completion
        response = self.llm.chat(messages)
        return response

    def _parse_tool_calls(self, response: str) -> List[ToolCall]:
        """Parse tool calls from LLM response"""
        tool_calls = []

        # Pattern 1: ```tool ... ``` blocks
        pattern = r"```tool\s*\n?(.*?)\n?```"
        matches = re.findall(pattern, response, re.DOTALL)

        for match in matches:
            try:
                data = json.loads(match.strip())
                if "tool" in data:
                    tool_calls.append(ToolCall(
                        name=data["tool"],
                        parameters=data.get("parameters", {}),
                        raw_text=match,
                    ))
            except json.JSONDecodeError:
                continue

        # Pattern 2: JSON objects with "tool" key
        json_pattern = r'\{[^{}]*"tool"\s*:\s*"[^"]+"\s*[^{}]*\}'
        json_matches = re.findall(json_pattern, response)

        for match in json_matches:
            if match not in [tc.raw_text for tc in tool_calls]:
                try:
                    data = json.loads(match)
                    if "tool" in data:
                        tool_calls.append(ToolCall(
                            name=data["tool"],
                            parameters=data.get("parameters", {}),
                            raw_text=match,
                        ))
                except json.JSONDecodeError:
                    continue

        return tool_calls

    def _execute_tool_call(self, tool_call: ToolCall) -> ToolResult:
        """Execute a single tool call"""
        tool = self.registry.get(tool_call.name)

        if not tool:
            return ToolResult(
                success=False,
                output="",
                error=f"Unknown tool: {tool_call.name}",
                tool_name=tool_call.name,
                target=str(tool_call.parameters)[:30],
            )

        # Check permission
        if self.registry.needs_approval(tool_call.name):
            self.state = AgentState.WAITING_APPROVAL
            approved = self._request_approval(tool_call)
            if not approved:
                return ToolResult(
                    success=False,
                    output="",
                    error="Tool execution denied by user",
                    tool_name=tool_call.name,
                    target=tool._get_target(**tool_call.parameters),
                )

        # Execute tool
        self.state = AgentState.EXECUTING_TOOL

        if HAS_CLI_UI:
            target = tool._get_target(**tool_call.parameters)
            log_action(tool_call.name, target, "running")

        result = tool.run(**tool_call.parameters)

        if HAS_CLI_UI:
            state = "success" if result.success else "error"
            if self.config.verbose or not result.success:
                log_action(tool_call.name, result.target, state)
                if self.config.show_tool_output and result.output:
                    # Show truncated output
                    output = result.output[:500]
                    if len(result.output) > 500:
                        output += "..."
                    print(f"    {Color.DIM}{output}{Color.RESET}")

        return result

    def _request_approval(self, tool_call: ToolCall) -> bool:
        """Request user approval for a tool call"""
        tool = self.registry.get(tool_call.name)
        target = tool._get_target(**tool_call.parameters) if tool else ""

        print(f"\n{Color.YELLOW}⚠ Tool requires approval:{Color.RESET}")
        print(f"  {Color.BOLD}{tool_call.name}{Color.RESET}({Color.CYAN}{target}{Color.RESET})")

        if tool_call.parameters:
            print(f"  Parameters: {json.dumps(tool_call.parameters, indent=2)[:200]}")

        response = input(f"\n{Color.BOLD}Allow?{Color.RESET} [y/N/always]: ").strip().lower()

        if response == "always":
            self.registry.approve_for_session(tool_call.name)
            return True
        elif response in ["y", "yes"]:
            return True
        else:
            return False

    def _format_tool_results(self, results: List[ToolResult]) -> str:
        """Format tool results for conversation"""
        parts = []
        for result in results:
            if result.success:
                parts.append(f"✓ {result.tool_name}({result.target}):\n{result.output}")
            else:
                parts.append(f"✗ {result.tool_name}({result.target}) failed: {result.error}")
        return "\n\n".join(parts)

    def _clean_response(self, response: str) -> str:
        """Clean tool call markers from final response"""
        # Remove tool blocks
        response = re.sub(r"```tool\s*\n?.*?\n?```", "", response, flags=re.DOTALL)
        # Remove JSON tool calls
        response = re.sub(r'\{[^{}]*"tool"\s*:\s*"[^"]+"\s*[^{}]*\}', "", response)
        return response.strip()

    def get_tool_history(self) -> List[Dict[str, Any]]:
        """Get history of tool calls and results"""
        return [
            {
                "tool": r.tool_name,
                "target": r.target,
                "success": r.success,
                "duration_ms": r.duration_ms,
            }
            for r in self.tool_results
        ]


def create_agent(llm=None, **config_kwargs) -> Agent:
    """Create an agent with default configuration

    Args:
        llm: LLM adapter
        **config_kwargs: Configuration overrides

    Returns:
        Configured Agent instance
    """
    config = AgentConfig(**config_kwargs)
    return Agent(llm=llm, config=config)
