"""
The Core Agent — Local Forge's multi-step reasoning engine.

This is the brain of Local Forge. It takes a user request, breaks it
into steps, uses tools to gather information and take actions, and
synthesizes a final response. Everything runs locally.
"""

import json
import re
from typing import Optional

from forge_agent.config import ForgeConfig
from forge_agent.llm import LLMBackend
from forge_agent.memory import ConversationMemory
from forge_agent.tools import ToolRegistry


SYSTEM_PROMPT = """\
You are Local Forge, a privacy-first AI agent running entirely on the \
user's local machine. You have access to tools for file operations, \
shell commands, and code analysis.

## Behavior
- You are helpful, direct, and concise.
- You think step-by-step for complex tasks.
- You use tools when needed — don't guess when you can look.
- You respect the user's workspace and never modify files without being asked.
- You explain what you're doing before taking actions.

## Tool Use
When you need to use a tool, respond with a JSON block like this:
```tool
{{"tool": "tool_name", "arguments": {{"arg1": "value1"}}}}
```

Available tools:
{tools}

## Important Rules
1. NEVER make network requests or call external APIs.
2. NEVER access files outside the workspace directory.
3. Always explain your reasoning before and after using tools.
4. If a task is unclear, ask for clarification.
5. For multi-step tasks, outline your plan first.
"""


class ForgeAgent:
    """
    The Local Forge Core Agent.

    Orchestrates multi-step reasoning with tool use:
    1. Receives user input
    2. Plans an approach
    3. Executes tools as needed
    4. Synthesizes a response
    """

    def __init__(self, config: ForgeConfig, llm: LLMBackend):
        self.config = config
        self.llm = llm
        self.tools = ToolRegistry(workspace=config.workspace)

        # Build system prompt with tool descriptions
        tool_docs = self._format_tool_docs()
        system = SYSTEM_PROMPT.format(tools=tool_docs)

        self.memory = ConversationMemory(
            system_prompt=system,
            max_turns=50,
        )

    def run(self, user_input: str, verbose: bool = False) -> str:
        """
        Run the agent on a user request.

        This is the main loop:
        1. Add user message to memory
        2. Generate LLM response
        3. If response contains tool calls, execute them
        4. Feed results back to LLM
        5. Repeat until final answer or max steps
        """
        self.memory.add_user_message(user_input)

        for step in range(self.config.max_steps):
            # Get LLM response
            messages = self.memory.get_messages()
            response = self.llm.generate(
                messages=messages,
                tools=self.tools.get_tool_schemas(),
            )

            if verbose:
                print(f"\n  [Step {step + 1}] LLM response:")
                print(f"  {response[:200]}{'...' if len(response) > 200 else ''}")

            # Check for tool calls (structured format from Ollama)
            if response.startswith("TOOL_CALLS:"):
                tool_calls = json.loads(response[len("TOOL_CALLS:"):])
                self.memory.add_assistant_message(
                    f"I'll use the following tools: {json.dumps(tool_calls)}"
                )

                for call in tool_calls:
                    result = self._execute_tool(call, verbose)
                    self.memory.add_tool_result(call["tool"], result)
                continue

            # Check for inline tool calls in ```tool blocks
            tool_match = re.search(
                r"```tool\s*\n({.*?})\s*\n```", response, re.DOTALL
            )

            if tool_match:
                # Extract text before tool call
                pre_text = response[: tool_match.start()].strip()
                if pre_text:
                    self.memory.add_assistant_message(pre_text)
                    if verbose:
                        print(f"  → {pre_text[:100]}")

                # Parse and execute tool call
                try:
                    call = json.loads(tool_match.group(1))
                    result = self._execute_tool(call, verbose)
                    self.memory.add_tool_result(call["tool"], result)
                except json.JSONDecodeError:
                    self.memory.add_assistant_message(response)
                    return response
                continue

            # No tool calls — this is the final response
            self.memory.add_assistant_message(response)
            return response

        return (
            "I've reached the maximum number of reasoning steps. "
            "Here's what I have so far — please refine your request "
            "if you need more."
        )

    def _execute_tool(self, call: dict, verbose: bool = False) -> str:
        """Execute a single tool call and return the result."""
        tool_name = call.get("tool", call.get("name", "unknown"))
        arguments = call.get("arguments", call.get("args", {}))

        if verbose:
            print(f"  🔧 Executing: {tool_name}({arguments})")

        result = self.tools.execute(tool_name, arguments)

        if verbose:
            preview = result[:150] + ("..." if len(result) > 150 else "")
            print(f"  → Result: {preview}")

        return result

    def list_tools(self) -> list[dict]:
        """List all available tools."""
        return self.tools.list_tools()

    def clear_memory(self):
        """Clear conversation history."""
        self.memory.clear()

    def status(self) -> dict:
        """Return agent status information."""
        return {
            "backend": self.config.backend,
            "model": self.llm.name(),
            "max_steps": self.config.max_steps,
            "memory_turns": self.memory.turn_count,
            "workspace": self.config.workspace,
            "tools_count": len(self.tools.list_tools()),
        }

    def _format_tool_docs(self) -> str:
        """Format tool documentation for the system prompt."""
        tools = self.tools.list_tools()
        lines = []
        for t in tools:
            params = ", ".join(
                f"{k}: {v.get('type', 'any')}" for k, v in t["parameters"].items()
            )
            lines.append(f"- **{t['name']}**({params}): {t['description']}")
        return "\n".join(lines)
