"""
Tool Executor

Central dispatcher: receives ToolRequests, validates via SecurityEngine,
routes to the correct tool or HTTP executor, returns ToolResponses.
"""

import logging
import time
import importlib
from typing import Dict, Type, Optional

from .protocol import ToolRequest, ToolResponse, ToolType
from .security import SecurityEngine
from .audit import AuditLogger
from .credential_vault import CredentialVault
from .http_executor import HttpExecutor
from .tools.base import BaseTool, ToolExecutionContext, ToolResult

logger = logging.getLogger(__name__)


class ToolExecutor:
    """
    Dispatches tool requests to the appropriate handler.
    
    Flow:
    1. Receive ToolRequest
    2. SecurityEngine validates limits
    3. Route to local tool or HTTP executor
    4. Return ToolResponse
    """

    def __init__(self, workspace_path: str, vault: CredentialVault,
                 audit: AuditLogger):
        self._workspace_path = workspace_path
        self._vault = vault
        self._audit = audit
        self._security = SecurityEngine(workspace_root=workspace_path)
        self._http_executor = HttpExecutor(vault)
        self._tools: Dict[str, BaseTool] = {}
        self._load_tools()

    def _load_tools(self):
        """
        Discover and load all tool implementations.
        
        1. Auto-sync: if backend dir exists (monorepo/dev), copy any newer
           tool files into tools/builtin/ with import rewriting.
        2. Load from tools/builtin/ (synced copies, work standalone).
        3. Fallback: legacy local tools if builtin/ is empty.
        """
        self._auto_sync_backend_tools()

        tools_loaded = False

        # ── Primary: Load from tools/builtin/ (synced from backend) ──
        builtin_modules = [
            ".tools.builtin.filesystem_tools",
            ".tools.builtin.shell_tools",
            ".tools.builtin.browser_tools",
            ".tools.builtin.web_fetch_tools",
            ".tools.builtin.email_tools",
            ".tools.builtin.persistent_shell_tools",
        ]

        for module_path in builtin_modules:
            try:
                module = importlib.import_module(module_path, package="agent_runtime")
                tool_classes = module.get_all_tools()
                for cls in tool_classes:
                    tool = cls()
                    self._tools[tool.name] = tool
                logger.debug(f"Loaded {len(tool_classes)} tools from {module_path}")
            except ImportError as e:
                logger.debug(f"Builtin tool {module_path} not available: {e}")
            except Exception as e:
                logger.warning(f"Error loading builtin tool {module_path}: {e}")

        if self._tools:
            tools_loaded = True
            logger.info(f"Loaded {len(self._tools)} tools from builtin/ (synced from backend)")

        # ── Fallback: Load from legacy local tools ──
        if not tools_loaded:
            local_modules = [
                ".tools.filesystem",
                ".tools.shell",
                ".tools.browser",
                ".tools.android",
                ".tools.web_fetch",
                ".tools.email_tools",
            ]

            for module_path in local_modules:
                try:
                    module = importlib.import_module(module_path, package="agent_runtime")
                    tool_classes = module.get_all_tools()
                    for cls in tool_classes:
                        tool = cls()
                        self._tools[tool.name] = tool
                except ImportError as e:
                    logger.warning(f"Could not load {module_path}: {e}")
                except Exception as e:
                    logger.error(f"Error loading {module_path}: {e}")

        logger.info(f"Loaded {len(self._tools)} tools: {list(self._tools.keys())}")

    def _auto_sync_backend_tools(self):
        """
        Auto-sync backend tools if we're in a monorepo dev environment.
        
        Detects the backend directory as a sibling of workspace-agent/,
        compares file modification times, and copies+rewrites any files
        that are newer in the backend. No-op if backend isn't present
        (i.e. customer deployment where workspace-agent ships standalone).
        """
        import re
        from pathlib import Path

        agent_runtime_dir = Path(__file__).resolve().parent
        backend_builtin = agent_runtime_dir.parent.parent / "backend" / "agent" / "tools" / "builtin"

        if not backend_builtin.is_dir():
            return  # Not in monorepo — customer deployment, skip silently

        local_builtin = agent_runtime_dir / "tools" / "builtin"
        local_builtin.mkdir(parents=True, exist_ok=True)

        # Ensure __init__.py exists
        init_file = local_builtin / "__init__.py"
        if not init_file.exists():
            init_file.write_text(
                '"""Backend tool implementations — auto-synced from backend."""\n',
                encoding="utf-8",
            )

        tool_files = [
            "filesystem_tools.py",
            "shell_tools.py",
            "browser_tools.py",
            "web_fetch_tools.py",
            "email_tools.py",
            "persistent_shell_tools.py",
            "shell_manager.py",
        ]

        synced = []
        for filename in tool_files:
            src = backend_builtin / filename
            dst = local_builtin / filename

            if not src.exists():
                continue

            # Skip if local copy is up-to-date
            if dst.exists() and dst.stat().st_mtime >= src.stat().st_mtime:
                continue

            # Read, rewrite imports, write
            content = src.read_text(encoding="utf-8")
            content = self._rewrite_imports(content)
            dst.write_text(content, encoding="utf-8")
            synced.append(filename)

        if synced:
            logger.info(f"Auto-synced {len(synced)} backend tools: {synced}")

    @staticmethod
    def _rewrite_imports(content: str) -> str:
        """Rewrite backend imports to workspace-agent relative imports."""
        import re

        # from agent.tools.base import ... → from ..base import ...
        content = re.sub(
            r'^from agent\.tools\.base import (.+)$',
            r'from ..base import \1',
            content, flags=re.MULTILINE,
        )

        # from agent.tools.builtin.X import ... → from .X import ...
        content = re.sub(
            r'^from agent\.tools\.builtin\.(\w+) import (.+)$',
            r'from .\1 import \2',
            content, flags=re.MULTILINE,
        )

        # Comment out Django credential resolver import
        content = content.replace(
            'from agent.services.credential_resolver import CredentialResolver',
            '# Workspace agent: credentials via vault, not Django ORM\n'
            '# from agent.services.credential_resolver import CredentialResolver'
        )

        return content

    def execute(self, request: ToolRequest) -> ToolResponse:
        """
        Execute a tool request with full security, auditing, and error handling.
        """
        start_time = time.time()

        # 1. Security check
        security_error = self._security.validate_request(request)
        if security_error:
            self._audit.log_denied(
                request.id, request.agent_id, request.agent_name,
                request.tool, security_error,
            )
            return ToolResponse(
                id=request.id,
                success=False,
                error=f"🚫 Access denied: {security_error}",
            )

        # 2. Audit start
        self._audit.log_execution_start(
            request.id, request.agent_id, request.agent_name,
            request.tool, request.tool_type.value, request.arguments,
        )

        try:
            # 3. Route to handler
            if request.tool_type == ToolType.SERVICE:
                result = self._execute_service(request)
            else:
                result = self._execute_local(request)

            duration_ms = int((time.time() - start_time) * 1000)

            # 4. Audit result
            self._audit.log_execution_result(
                request.id, request.agent_id, request.agent_name,
                request.tool, request.tool_type.value,
                result.success, result.output, result.error, duration_ms,
            )

            return ToolResponse(
                id=request.id,
                success=result.success,
                output=result.output,
                error=result.error,
                metadata={**result.metadata, "duration_ms": duration_ms},
            )

        except Exception as e:
            duration_ms = int((time.time() - start_time) * 1000)
            logger.exception(f"Unhandled error executing {request.tool}: {e}")

            self._audit.log_execution_result(
                request.id, request.agent_id, request.agent_name,
                request.tool, request.tool_type.value,
                False, "", str(e), duration_ms,
            )

            return ToolResponse(
                id=request.id,
                success=False,
                error=f"Internal error: {e}",
                metadata={"duration_ms": duration_ms},
            )

    def _execute_local(self, request: ToolRequest) -> ToolResult:
        """Execute a local tool (filesystem, shell, browser, etc.)."""
        tool = self._tools.get(request.tool)
        if not tool:
            available = ", ".join(sorted(self._tools.keys()))
            return ToolResult.fail(f"Unknown tool: {request.tool}. Available: {available}")

        # Build execution context
        context = ToolExecutionContext(
            workspace_path=self._workspace_path,
            agent_id=request.agent_id,
            agent_name=request.agent_name,
            request_id=request.id,
        )

        # Track resources
        tool_lower = request.tool.lower()
        if tool_lower in ("run_command", "persistent_shell"):
            self._security.track_shell_start(request.agent_id)

        try:
            return tool.execute(request.arguments, context)
        finally:
            if tool_lower in ("run_command", "persistent_shell"):
                self._security.track_shell_end(request.agent_id)

    def _execute_service(self, request: ToolRequest) -> ToolResult:
        """Execute a service tool via HTTP with credential resolution."""
        if not request.execution_spec:
            return ToolResult.fail("Service tool call missing execution_spec")

        return self._http_executor.execute(request.execution_spec)

    def get_tool_list(self) -> list:
        """Return metadata for all loaded tools."""
        result = []
        for tool in self._tools.values():
            meta = tool.get_metadata()
            result.append({
                "name": meta.name,
                "description": meta.description,
                "category": meta.category,
                "parameters": [
                    {"name": p.name, "type": p.type, "description": p.description,
                     "required": p.required}
                    for p in meta.parameters
                ],
            })
        return result

    def self_test(self) -> bool:
        """Run a basic self-test on all loaded tools."""
        logger.info(f"Running self-test with {len(self._tools)} tools...")
        ok = True
        for name, tool in self._tools.items():
            try:
                meta = tool.get_metadata()
                assert meta.name == name
                logger.info(f"  ✅ {name}: {meta.description[:60]}")
            except Exception as e:
                logger.error(f"  ❌ {name}: {e}")
                ok = False
        return ok

    def close(self):
        """Clean up resources."""
        self._http_executor.close()
