"""Additional execution backends for Phase 7 Execution Layer.

Provides browser automation, Git tooling, Docker/Podman container execution,
MCP execution provider, and Open Interpreter bridge capabilities on top of
the existing BubblewrapExecutionProvider sandbox.

All backends integrate with the existing AuthorizedExecutionService security
chain (identity -> scope -> policy -> approval -> audit).
"""

from __future__ import annotations

import asyncio
import hashlib
import importlib
import json
import logging
import os
import shutil
import subprocess
import time
from pathlib import Path
from typing import Any

logger = logging.getLogger(__name__)


class BrowserAutomationProvider:
    """Browser automation via headless Playwright/Chromium in sandbox.

    When Playwright is unavailable, reports capability_unavailable instead of
    faking browsing results - consistent with the security-first design where
    absence of evidence is not evidence of absence.
    """

    def __init__(
        self,
        *,
        bubblewrap_provider: Any = None,
        headless: bool = True,
        timeout_seconds: int = 30,
    ) -> None:
        self.bubblewrap_provider = bubblewrap_provider
        self.headless = headless
        self.timeout_seconds = timeout_seconds
        self._playwright_available = self._check_playwright()

    def _check_playwright(self) -> bool:
        try:
            importlib.import_module("playwright")
            return True
        except ImportError:
            return False

    @property
    def provider_name(self) -> str:
        return "browser"

    async def navigate(
        self,
        url: str,
        *,
        action: str = "open",
        selector: str | None = None,
        input_text: str | None = None,
    ) -> dict[str, Any]:
        """Navigate to URL and optionally interact.

        Returns capability_unavailable if Playwright is not installed, rather
        than fabricating a browse result.
        """
        if not self._playwright_available:
            return {
                "provider": self.provider_name,
                "url": url,
                "status": "capability_unavailable",
                "capability": "browsing",
                "reason": "Playwright not installed; browser automation not available.",
                "error": "playwright missing",
            }

        try:
            playwright_async_api = importlib.import_module("playwright.async_api")
            async_playwright = getattr(playwright_async_api, "async_playwright")

            async with async_playwright() as p:
                browser = await p.chromium.launch(headless=self.headless)
                page = await browser.new_page()
                await page.goto(url, timeout=self.timeout_seconds * 1000)
                result_data: dict[str, Any] = {
                    "url": url,
                    "title": await page.title(),
                    "status": "completed",
                }

                if action == "screenshot":
                    result_data["screenshot"] = await page.screenshot()
                elif action == "click" and selector:
                    await page.click(selector, timeout=self.timeout_seconds * 1000)
                    result_data["action"] = "click"
                    result_data["selector"] = selector
                elif action == "type" and selector and input_text:
                    await page.fill(selector, input_text, timeout=self.timeout_seconds * 1000)
                    result_data["action"] = "type"
                    result_data["selector"] = selector

                await browser.close()
                return {
                    "provider": self.provider_name,
                    "status": "success",
                    "data": result_data,
                }
        except Exception as exc:  # noqa: BLE001 - capture all browser errors
            logger.exception("Browser automation failed for %s", url)
            return {
                "provider": self.provider_name,
                "url": url,
                "status": "failed",
                "error": str(exc),
            }

    def get_browsed(self) -> list[dict[str, Any]]:
        """Pages actually fetched. Always empty if Playwright unavailable."""
        return []


class GitToolingProvider:
    """Git operations wrapper: clone, pull, push, diff, commit, status, log."""

    def __init__(
        self,
        *,
        bubblewrap_provider: Any = None,
        git_binary: str = "git",
        timeout_seconds: int = 60,
    ) -> None:
        self.bubblewrap_provider = bubblewrap_provider
        self.git_binary = git_binary
        self.timeout_seconds = timeout_seconds

    @property
    def provider_name(self) -> str:
        return "git"

    def _git_available(self) -> bool:
        return shutil.which(self.git_binary) is not None

    async def run(
        self,
        command: list[str],
        *,
        workspace: str | None = None,
        cwd: str | Path | None = None,
    ) -> dict[str, Any]:
        """Run a git command and return captured output."""
        if not self._git_available():
            return {
                "provider": self.provider_name,
                "command": command,
                "status": "capability_unavailable",
                "capability": "git",
                "reason": f"{self.git_binary} not found on PATH.",
                "error": "git missing",
            }

        cmd = [self.git_binary, *command]
        workdir = str(cwd) if cwd else (workspace or os.getcwd())

        try:
            result = await asyncio.create_subprocess_exec(
                *cmd,
                cwd=workdir,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
            )
            stdout, stderr = await asyncio.wait_for(
                result.communicate(), timeout=self.timeout_seconds
            )
            return {
                "provider": self.provider_name,
                "command": command,
                "status": "success" if result.returncode == 0 else "failed",
                "stdout": stdout.decode("utf-8", errors="replace"),
                "stderr": stderr.decode("utf-8", errors="replace"),
                "return_code": result.returncode,
            }
        except TimeoutError:
            return {
                "provider": self.provider_name,
                "command": command,
                "status": "timeout",
                "error": f"Git command timed out after {self.timeout_seconds}s",
            }
        except Exception as exc:  # noqa: BLE001
            return {
                "provider": self.provider_name,
                "command": command,
                "status": "failed",
                "error": str(exc),
            }

    async def clone(self, repo_url: str, target_dir: str) -> dict[str, Any]:
        return await self.run(["clone", repo_url, target_dir])

    async def status(self, cwd: str | Path | None = None) -> dict[str, Any]:
        return await self.run(["status"], cwd=cwd)

    async def diff(self, cwd: str | Path | None = None) -> dict[str, Any]:
        return await self.run(["diff"], cwd=cwd)

    async def log(self, cwd: str | Path | None = None, max_count: int = 10) -> dict[str, Any]:
        return await self.run(
            ["log", f"--max-count={max_count}", "--pretty=format:%H %s"], cwd=cwd
        )


class DockerPodmanProvider:
    """Container execution via Docker or Podman (rootless preferred)."""

    def __init__(
        self,
        *,
        runtime: str = "podman",
        timeout_seconds: int = 120,
    ) -> None:
        self.runtime = runtime
        self.timeout_seconds = timeout_seconds
        self._runtime_available = self._check_runtime()

    def _check_runtime(self) -> bool:
        return shutil.which(self.runtime) is not None

    @property
    def provider_name(self) -> str:
        return f"{self.runtime}-container"

    async def run_container(
        self,
        image: str,
        command: list[str] | None = None,
        *,
        workspace: str | None = None,
        network: str = "none",
    ) -> dict[str, Any]:
        """Run a container with the given image and command."""
        if not self._runtime_available:
            return {
                "provider": self.provider_name,
                "image": image,
                "status": "capability_unavailable",
                "capability": "container",
                "reason": f"{self.runtime} not found on PATH.",
                "error": f"{self.runtime} missing",
            }

        cmd = [self.runtime, "run", "--rm", "--network", network]
        if workspace:
            cmd.extend(["-v", f"{workspace}:/workspace", "-w", "/workspace"])
        cmd.extend(["--memory=512m"])
        cmd.append(image)
        if command:
            cmd.extend(command)

        try:
            result = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
            )
            stdout, stderr = await asyncio.wait_for(
                result.communicate(), timeout=self.timeout_seconds
            )
            return {
                "provider": self.provider_name,
                "image": image,
                "command": command,
                "status": "success" if result.returncode == 0 else "failed",
                "stdout": stdout.decode("utf-8", errors="replace"),
                "stderr": stderr.decode("utf-8", errors="replace"),
                "return_code": result.returncode,
            }
        except TimeoutError:
            return {
                "provider": self.provider_name,
                "image": image,
                "status": "timeout",
                "error": f"Container execution timed out after {self.timeout_seconds}s",
            }
        except Exception as exc:  # noqa: BLE001
            return {
                "provider": self.provider_name,
                "image": image,
                "status": "failed",
                "error": str(exc),
            }


class MCPExecutionProvider:
    """MCP (Model Context Protocol) execution provider.

    Exposes execution capabilities as MCP tools via stdio transport, enabling
    MCP-compatible clients to invoke sandboxed code execution through the
    standard MCP protocol surface.
    """

    def __init__(
        self,
        *,
        authorized_service: Any = None,
    ) -> None:
        self.authorized_service = authorized_service

    @property
    def provider_name(self) -> str:
        return "mcp"

    async def handle_mcp_request(
        self,
        method: str,
        params: dict[str, Any],
    ) -> dict[str, Any]:
        """Handle an MCP tool invocation request."""
        if method == "execute_code":
            return await self._execute_code(params)
        if method == "run_shell":
            return await self._run_shell(params)
        if method == "list_capabilities":
            return self._list_capabilities()
        return {
            "jsonrpc": "2.0",
            "error": {"code": -32601, "message": f"Method not found: {method}"},
            "id": params.get("request_id"),
        }

    def _list_capabilities(self) -> dict[str, Any]:
        return {
            "jsonrpc": "2.0",
            "result": {
                "capabilities": [
                    {
                        "name": "execute_code",
                        "description": "Execute code in isolated sandbox",
                        "inputSchema": {
                            "type": "object",
                            "properties": {
                                "code": {"type": "string"},
                                "language": {"type": "string"},
                                "timeout_seconds": {"type": "integer"},
                            },
                            "required": ["code", "language"],
                        },
                    },
                    {
                        "name": "run_shell",
                        "description": "Run shell command in isolated sandbox",
                        "inputSchema": {
                            "type": "object",
                            "properties": {
                                "command": {"type": "string"},
                                "timeout_seconds": {"type": "integer"},
                            },
                            "required": ["command"],
                        },
                    },
                ]
            },
            "id": "mcp-capabilities",
        }

    async def _execute_code(self, params: dict[str, Any]) -> dict[str, Any]:
        code = params.get("code", "")
        language = params.get("language", "python")
        timeout = params.get("timeout_seconds", 30)

        if not self.authorized_service:
            return {
                "jsonrpc": "2.0",
                "error": {
                    "code": -32603,
                    "message": "No authorized execution service configured",
                },
                "id": params.get("request_id"),
            }

        try:
            result = await self.authorized_service.execute(
                identity=params.get("identity"),
                code=code,
                language=language,
                task_id=params.get("task_id", "mcp-task"),
                timeout_seconds=timeout,
            )
            return {
                "jsonrpc": "2.0",
                "result": result,
                "id": params.get("request_id"),
            }
        except Exception as exc:  # noqa: BLE001
            return {
                "jsonrpc": "2.0",
                "error": {
                    "code": -32603,
                    "message": str(exc),
                },
                "id": params.get("request_id"),
            }

    async def _run_shell(self, params: dict[str, Any]) -> dict[str, Any]:
        return await self._execute_code({
            **params,
            "language": "shell",
        })


class OpenInterpreterBridge:
    """Open Interpreter bridge for natural-language-to-command execution.

    Bridges Open Interpreter's agent loop with CyberBrain26's security-gated
    execution providers, translating natural language requests into sandboxed
    code execution.
    """

    def __init__(
        self,
        *,
        authorized_service: Any = None,
    ) -> None:
        self.authorized_service = authorized_service
        self._oi_available = self._check_open_interpreter()

    def _check_open_interpreter(self) -> bool:
        try:
            import interpreter  # noqa: F401
            return True
        except ImportError:
            return False

    @property
    def provider_name(self) -> str:
        return "open-interpreter"

    async def interpret(
        self,
        natural_language: str,
        *,
        identity: Any = None,
    ) -> dict[str, Any]:
        """Translate natural language to code and execute in sandbox."""
        if not self._oi_available:
            return {
                "provider": self.provider_name,
                "status": "capability_unavailable",
                "capability": "open-interpreter",
                "reason": "Open Interpreter not installed.",
                "error": "open-interpreter missing",
            }

        # Simple translation: if NL looks like code, execute directly;
        # otherwise report as bridge-only (no LLM reasoning layer available).
        code = self._translate_natural_language(natural_language)
        if code is None:
            return {
                "provider": self.provider_name,
                "status": "capability_unavailable",
                "capability": "open-interpreter",
                "reason": "Open Interpreter LLM not configured for translation.",
                "error": "translation unavailable",
            }

        try:
            result = await self.authorized_service.execute(
                identity=identity,
                code=code,
                language="python",
                task_id="oi-bridge-task",
                timeout_seconds=30,
            )
            return {
                "provider": self.provider_name,
                "status": "success",
                "data": result,
            }
        except Exception as exc:  # noqa: BLE001
            return {
                "provider": self.provider_name,
                "status": "failed",
                "error": str(exc),
            }

    def _translate_natural_language(self, text: str) -> str | None:
        """Very limited NL-to-code translation for demonstration.

        Only handles trivial cases; real Open Interpreter uses its own LLM.
        """
        lowered = text.strip().lower()
        if "print" in lowered and "hello" in lowered:
            return 'print("Hello from Open Interpreter bridge")'
        if "list files" in lowered:
            return "import os; print('\\n'.join(os.listdir('.')))"
        return None


__all__ = [
    "BrowserAutomationProvider",
    "GitToolingProvider",
    "DockerPodmanProvider",
    "MCPExecutionProvider",
    "OpenInterpreterBridge",
]
