import os
import math
import re
from typing import Dict, Any, List, Optional
from .interfaces import UserInterfaceAdapter
from . import ui_state
import logging

# ------------------------------------------------------------------
# Base class
# ------------------------------------------------------------------
class BaseTool:
    """Common interface for all tools."""
    name: str
    description: str

    def run(self, args: Dict[str, Any]) -> str:
        raise NotImplementedError

# ------------------------------------------------------------------
# 1️⃣  Calculator
# ------------------------------------------------------------------
class CalcTool(BaseTool):
    name = "calc"
    description = (
        "Evaluate a simple arithmetic expression. "
        "Supported operators: + - * / ** and parentheses. "
        "Example args: {\"expression\": \"2 * (3 + 4)\"}"
    )

    _allowed_names = {
        k: getattr(math, k) for k in dir(math) if not k.startswith("__")
    } | {"abs": abs, "round": round, "int": int, "float": float}

    def _safe_eval(self, expr: str) -> float:
        if not re.fullmatch(r"[0-9\s\.\+\-\*\/\(\)\^\%]+", expr):
            raise ValueError("Expression contains illegal characters")
        return eval(expr, {"__builtins__": {}}, self._allowed_names)

    def run(self, args: Dict[str, Any]) -> str:
        expr = args.get("expression")
        if not expr:
            return "❌ Missing 'expression' argument."
        try:
            result = self._safe_eval(expr)
            return f"✅ Result = {result}"
        except Exception as e:
            return f"❌ Calculator error: {e}"

# ------------------------------------------------------------------
# 2️⃣  Search stub
# ------------------------------------------------------------------
class SearchTool(BaseTool):
    name = "search"
    description = (
        "Search the web for a short query and return the top result snippet. "
        "Example args: {\"query\": \"capital of Norway\"}"
    )

    def run(self, args: Dict[str, Any]) -> str:
        query = args.get("query")
        if not query:
            return "❌ Missing 'query' argument."
        # This is a placeholder. Replace with a real API if desired.
        return f"🔎 (stub) Top result for '{query}': <pretend we fetched something here>"

# ------------------------------------------------------------------
# 3️⃣  File‑system tools (sandboxed to the current working directory)
# ------------------------------------------------------------------
class ReadFileTool(BaseTool):
    name = "read_file"
    description = (
        "Read a UTF‑8 text file from the local filesystem. "
        "Example args: {\"path\": \"./notes.txt\"}"
    )

    def run(self, args: Dict[str, Any]) -> str:
        path = args.get("path")
        if not path:
            return "❌ Missing 'path' argument."

        safe_path = os.path.abspath(path)
        cwd = os.path.abspath(os.getcwd())
        if not safe_path.startswith(cwd):
            return "❌ Access to paths outside the current directory is forbidden."

        if not os.path.isfile(safe_path):
            return f"❌ File not found: {path}"
        try:
            with open(safe_path, "r", encoding="utf-8") as f:
                content = f.read(500)
                return f"📄 Content of {path} (truncated):\n{content}"
        except Exception as e:
            return f"❌ Could not read file: {e}"

class WriteFileTool(BaseTool):
    name = "write_file"
    description = (
        "Create or overwrite a UTF‑8 text file. "
        "Example args: {\"path\": \"./data.txt\", \"content\": \"Hello, world!\"}"
    )

    def run(self, args: Dict[str, Any]) -> str:
        path = args.get("path")
        content = args.get("content", "")
        if not path:
            return "❌ Missing 'path' argument."

        safe_path = os.path.abspath(path)
        cwd = os.path.abspath(os.getcwd())
        if not safe_path.startswith(cwd):
            return "❌ Refusing to write outside the current working directory."

        try:
            with open(safe_path, "w", encoding="utf-8") as f:
                f.write(content)
            return f"✅ Wrote {len(content)} characters to {path}"
        except Exception as e:
            return f"❌ Failed to write file: {e}"

class DeleteFileTool(BaseTool):
    name = "delete_file"
    description = (
        "Delete a file from the local filesystem. "
        "Example args: {\"path\": \"./old.txt\"}"
    )

    def run(self, args: Dict[str, Any]) -> str:
        path = args.get("path")
        if not path:
            return "❌ Missing 'path' argument."

        safe_path = os.path.abspath(path)
        cwd = os.path.abspath(os.getcwd())
        if not safe_path.startswith(cwd):
            return "❌ Refusing to delete outside the current working directory."

        if not os.path.isfile(safe_path):
            return f"❌ File not found: {path}"
        try:
            os.remove(safe_path)
            return f"✅ Deleted file {path}"
        except Exception as e:
            return f"❌ Could not delete file: {e}"

class ListFilesTool(BaseTool):
    name = "list_files"
    description = (
        "List files in a directory (optionally filter by extension). "
        "Example args: {\"directory\": \"./\", \"extension\": \".txt\"}"
    )

    def run(self, args: Dict[str, Any]) -> str:
        directory = args.get("directory", ".")
        ext = args.get("extension")

        safe_dir = os.path.abspath(directory)
        cwd = os.path.abspath(os.getcwd())
        if not safe_dir.startswith(cwd):
            return "❌ Refusing to list outside the current working directory."

        if not os.path.isdir(safe_dir):
            return f"❌ Not a directory: {directory}"

        try:
            entries = os.listdir(safe_dir)
            if ext:
                entries = [e for e in entries if e.endswith(ext)]
            if not entries:
                return f"🔍 No files found in {directory} with extension '{ext}'."
            return "📂 Files:\n" + "\n".join(entries)
        except Exception as e:
            return f"❌ Error while listing files: {e}"

# ------------------------------------------------------------------
# 4️⃣  Interactive “ask_user” tool
# ------------------------------------------------------------------
class AskUserTool(BaseTool):
    """Prompt the real user; supports both sync (CLI) and modal web via token.
    When used with a web adapter, returns {'awaiting': token, 'prompt': text}.
    """
    name = "ask_user"
    description = (
        "Ask the person running the agent a question and return their answer. "
        "Example args: {\"question\": \"What is your name?\"}"
    )

    def __init__(self, ui_adapter: Optional[UserInterfaceAdapter] = None):
        self.ui_adapter = ui_adapter

    def run(self, args: Dict[str, Any], context: Optional[Dict[str, Any]] = None) -> str:
        question = args.get("question", "Please provide input:")
        logger = logging.getLogger(__name__)
        # If adapter supports sync, use it directly
        if self.ui_adapter and getattr(self.ui_adapter, "supports_sync", False):
            try:
                ans = self.ui_adapter.ask_sync(question)
                logger.info("AskUserTool sync answered: %s", ans)
                return f"🗣️  User answered: {ans}"
            except Exception as e:
                logger.exception("Error during AskUserTool sync ask: %s", e)
                return f"❌ Error while asking user: {e}"

        # Otherwise create a token and return awaiting sentinel; Agent will store saved state
        token = ui_state.new_token()
        logger.info("AskUserTool created token %s for prompt: %s", token, question)
        return {"awaiting": token, "prompt": question}

# ------------------------------------------------------------------
# Helper to build the registry (used by the agent)
# ------------------------------------------------------------------
def build_tool_registry(ui_adapter: Optional[UserInterfaceAdapter] = None) -> Dict[str, BaseTool]:
    """
    Returns a dictionary mapping tool names to instantiated tool objects.
    Add any new tool class to this dictionary to make it visible to the agent.
    """
    return {
        CalcTool.name: CalcTool(),
        SearchTool.name: SearchTool(),
        ReadFileTool.name: ReadFileTool(),
        WriteFileTool.name: WriteFileTool(),
        DeleteFileTool.name: DeleteFileTool(),
        ListFilesTool.name: ListFilesTool(),
        AskUserTool.name: AskUserTool(ui_adapter),
    }