"""OCLAWMA Skill: Docker

Official Docker skill for OCLAWMA providing comprehensive container
management capabilities including image operations, container lifecycle,
network and volume management, and Docker Compose support.
"""

from __future__ import annotations

import subprocess
from typing import Any

from oclawma.skills import LazySkill, SkillMetadata

__all__ = ["DockerSkill"]


class DockerSkill(LazySkill):
    """Docker container management skill.

    This skill provides tools for managing Docker containers, images,
    networks, and volumes. It wraps the docker CLI into simple, safe
    tool calls with proper error handling and output formatting.

    Features:
    - Container lifecycle management (run, start, stop, rm)
    - Image operations (pull, build, rmi, images)
    - Container inspection and logs
    - Network management
    - Volume management
    - Docker Compose support
    - Resource statistics

    Example:
        >>> from oclawma.skills import SkillRegistry
        >>> registry = SkillRegistry()
        >>>
        >>> # List containers
        >>> result = await registry.execute_tool("docker", "ps", all=True)
        >>>
        >>> # Run a container
        >>> result = await registry.execute_tool(
        ...     "docker", "run",
        ...     image="nginx:latest",
        ...     name="web",
        ...     ports=["8080:80"]
        ... )
        >>>
        >>> # Get container logs
        >>> result = await registry.execute_tool(
        ...     "docker", "logs",
        ...     container="web", tail=50
        ... )
    """

    def __init__(self, metadata: SkillMetadata) -> None:
        """Initialize the Docker skill.

        Args:
            metadata: Skill metadata from the entry point.
        """
        super().__init__(metadata)
        self._docker_path: str | None = None
        self._compose_path: str | None = None

    def _find_binary(self, name: str) -> str | None:
        """Find the path to a binary.

        Args:
            name: Binary name to find.

        Returns:
            Full path to binary or None if not found.
        """
        try:
            result = subprocess.run(
                ["which", name],
                capture_output=True,
                text=True,
                check=False
            )
            if result.returncode == 0:
                return result.stdout.strip()
        except Exception:
            pass
        return None

    def _get_docker(self) -> str:
        """Get docker binary path, caching the result.

        Returns:
            Path to docker binary.

        Raises:
            SkillLoadError: If docker is not found.
        """
        if self._docker_path is None:
            self._docker_path = self._find_binary("docker")
            if self._docker_path is None:
                from oclawma.skills import SkillLoadError
                raise SkillLoadError(
                    "Docker not found. Please install Docker and ensure it's in PATH."
                )
        return self._docker_path

    def _get_compose(self) -> str | None:
        """Get docker compose binary path, caching the result.

        Returns:
            Path to docker compose binary or None if not found.
        """
        if self._compose_path is None:
            # Try docker compose plugin first
            compose_plugin = self._find_binary("docker-compose")
            if compose_plugin:
                self._compose_path = compose_plugin
            else:
                # Check if 'docker compose' works
                try:
                    result = subprocess.run(
                        [self._get_docker(), "compose", "version"],
                        capture_output=True,
                        text=True,
                        check=False
                    )
                    if result.returncode == 0:
                        self._compose_path = f"{self._get_docker()} compose"
                except Exception:
                    pass
        return self._compose_path

    def _run_docker(
        self,
        args: list[str],
        timeout: int = 60,
        capture_output: bool = True
    ) -> dict[str, Any]:
        """Execute a docker command.

        Args:
            args: Command arguments.
            timeout: Command timeout in seconds.
            capture_output: Whether to capture output.

        Returns:
            Standardized result dictionary.
        """
        try:
            cmd = [self._get_docker()] + args

            result = subprocess.run(
                cmd,
                capture_output=capture_output,
                text=True,
                check=False,
                timeout=timeout
            )

            output = result.stdout if result.returncode == 0 else result.stderr

            # Truncate very long outputs
            truncated = False
            if len(output) > 50000:
                output = output[:50000] + "\n... [output truncated]"
                truncated = True

            return {
                "success": result.returncode == 0,
                "output": output,
                "exit_code": result.returncode,
                "truncated": truncated
            }

        except subprocess.TimeoutExpired:
            return {
                "success": False,
                "output": "",
                "error": f"Command timed out after {timeout} seconds"
            }
        except Exception as e:
            return {
                "success": False,
                "output": "",
                "error": f"Failed to execute docker: {str(e)}"
            }

    def _run_compose(
        self,
        args: list[str],
        timeout: int = 120
    ) -> dict[str, Any]:
        """Execute a docker compose command.

        Args:
            args: Command arguments.
            timeout: Command timeout in seconds.

        Returns:
            Standardized result dictionary.
        """
        compose_path = self._get_compose()
        if not compose_path:
            return {
                "success": False,
                "output": "",
                "error": "Docker Compose not found. Please install Docker Compose."
            }

        try:
            if compose_path.endswith("compose"):
                # Using 'docker compose' subcommand
                cmd = [self._get_docker(), "compose"] + args
            else:
                # Using docker-compose binary
                cmd = [compose_path] + args

            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                check=False,
                timeout=timeout
            )

            output = result.stdout if result.returncode == 0 else result.stderr

            truncated = False
            if len(output) > 50000:
                output = output[:50000] + "\n... [output truncated]"
                truncated = True

            return {
                "success": result.returncode == 0,
                "output": output,
                "exit_code": result.returncode,
                "truncated": truncated
            }

        except subprocess.TimeoutExpired:
            return {
                "success": False,
                "output": "",
                "error": f"Command timed out after {timeout} seconds"
            }
        except Exception as e:
            return {
                "success": False,
                "output": "",
                "error": f"Failed to execute docker compose: {str(e)}"
            }

    def _load(self) -> None:
        """Load the skill's tools."""
        self._tools = {
            # Container lifecycle
            "run": self._run,
            "ps": self._ps,
            "stop": self._stop,
            "start": self._start,
            "restart": self._restart,
            "rm": self._rm,

            # Container operations
            "logs": self._logs,
            "exec": self._exec,
            "inspect": self._inspect,
            "stats": self._stats,

            # Image operations
            "images": self._images,
            "pull": self._pull,
            "build": self._build,
            "rmi": self._rmi,

            # Network operations
            "network_ls": self._network_ls,
            "network_create": self._network_create,
            "network_rm": self._network_rm,

            # Volume operations
            "volume_ls": self._volume_ls,
            "volume_create": self._volume_create,
            "volume_rm": self._volume_rm,

            # System operations
            "system_prune": self._system_prune,

            # Docker Compose
            "compose_up": self._compose_up,
            "compose_down": self._compose_down,
        }
        self._loaded = True

    # -------------------------------------------------------------------------
    # Container Lifecycle
    # -------------------------------------------------------------------------

    async def _run(
        self,
        image: str,
        command: str | None = None,
        name: str | None = None,
        ports: list[str] | None = None,
        volumes: list[str] | None = None,
        env: dict[str, str] | None = None,
        detach: bool = True,
        network: str | None = None,
        restart: str | None = None
    ) -> dict[str, Any]:
        """Run a new container."""
        args = ["run"]

        if detach:
            args.append("--detach")

        if name:
            args.extend(["--name", name])

        if ports:
            for port in ports:
                args.extend(["-p", port])

        if volumes:
            for volume in volumes:
                args.extend(["-v", volume])

        if env:
            for key, value in env.items():
                args.extend(["-e", f"{key}={value}"])

        if network:
            args.extend(["--network", network])

        if restart:
            args.extend(["--restart", restart])

        args.append(image)

        if command:
            args.extend(command.split())

        return self._run_docker(args, timeout=120)

    async def _ps(
        self,
        all: bool = False,
        filter: str | None = None,
        format: str = "table"
    ) -> dict[str, Any]:
        """List containers."""
        args = ["ps"]

        if all:
            args.append("--all")

        if filter:
            args.extend(["--filter", filter])

        if format:
            args.extend(["--format", format])

        return self._run_docker(args)

    async def _stop(
        self,
        container: str,
        timeout: int = 10
    ) -> dict[str, Any]:
        """Stop a container."""
        args = ["stop", "-t", str(timeout), container]
        return self._run_docker(args, timeout=timeout + 10)

    async def _start(self, container: str) -> dict[str, Any]:
        """Start a container."""
        args = ["start", container]
        return self._run_docker(args)

    async def _restart(
        self,
        container: str,
        timeout: int = 10
    ) -> dict[str, Any]:
        """Restart a container."""
        args = ["restart", "-t", str(timeout), container]
        return self._run_docker(args, timeout=timeout + 30)

    async def _rm(
        self,
        container: str,
        force: bool = False,
        volumes: bool = False
    ) -> dict[str, Any]:
        """Remove a container."""
        args = ["rm"]

        if force:
            args.append("--force")

        if volumes:
            args.append("--volumes")

        args.append(container)
        return self._run_docker(args)

    # -------------------------------------------------------------------------
    # Container Operations
    # -------------------------------------------------------------------------

    async def _logs(
        self,
        container: str,
        follow: bool = False,
        tail: int = 100,
        timestamps: bool = True,
        since: str | None = None
    ) -> dict[str, Any]:
        """Fetch container logs."""
        args = ["logs"]

        if follow:
            args.append("--follow")

        if tail >= 0 and not follow:
            args.extend(["--tail", str(tail)])

        if timestamps:
            args.append("--timestamps")

        if since:
            args.extend(["--since", since])

        args.append(container)

        timeout = 300 if follow else 30
        result = self._run_docker(args, timeout=timeout)

        # Truncate long logs for non-follow mode
        if not follow and result["success"] and len(result["output"]) > 10000:
            lines = result["output"].split("\n")
            result["output"] = "\n".join(lines[-tail:])
            result["truncated"] = True

        return result

    async def _exec(
        self,
        container: str,
        command: str,
        interactive: bool = False,
        tty: bool = False
    ) -> dict[str, Any]:
        """Execute command in running container."""
        args = ["exec"]

        if interactive:
            args.append("--interactive")

        if tty:
            args.append("--tty")

        args.append(container)
        args.extend(command.split())

        return self._run_docker(args, timeout=60)

    async def _inspect(
        self,
        target: str,
        type: str = "container",
        format: str | None = None
    ) -> dict[str, Any]:
        """Inspect container or image."""
        args = ["inspect"]

        if format:
            args.extend(["--format", format])

        args.append(target)
        return self._run_docker(args)

    async def _stats(
        self,
        container: str | None = None,
        stream: bool = False,
        no_stream: bool = True
    ) -> dict[str, Any]:
        """Display container resource usage."""
        args = ["stats"]

        if no_stream:
            args.append("--no-stream")

        if container:
            args.append(container)

        return self._run_docker(args, timeout=30)

    # -------------------------------------------------------------------------
    # Image Operations
    # -------------------------------------------------------------------------

    async def _images(
        self,
        all: bool = False,
        filter: str | None = None,
        format: str = "table"
    ) -> dict[str, Any]:
        """List images."""
        args = ["images"]

        if all:
            args.append("--all")

        if filter:
            args.extend(["--filter", filter])

        if format:
            args.extend(["--format", format])

        return self._run_docker(args)

    async def _pull(
        self,
        image: str,
        platform: str | None = None
    ) -> dict[str, Any]:
        """Pull an image."""
        args = ["pull"]

        if platform:
            args.extend(["--platform", platform])

        args.append(image)
        return self._run_docker(args, timeout=300)

    async def _build(
        self,
        path: str,
        tag: str | None = None,
        dockerfile: str | None = None,
        build_args: dict[str, str] | None = None,
        no_cache: bool = False
    ) -> dict[str, Any]:
        """Build an image."""
        args = ["build"]

        if tag:
            args.extend(["-t", tag])

        if dockerfile:
            args.extend(["-f", dockerfile])

        if no_cache:
            args.append("--no-cache")

        if build_args:
            for key, value in build_args.items():
                args.extend(["--build-arg", f"{key}={value}"])

        args.append(path)
        return self._run_docker(args, timeout=600)

    async def _rmi(
        self,
        image: str,
        force: bool = False
    ) -> dict[str, Any]:
        """Remove an image."""
        args = ["rmi"]

        if force:
            args.append("--force")

        args.append(image)
        return self._run_docker(args)

    # -------------------------------------------------------------------------
    # Network Operations
    # -------------------------------------------------------------------------

    async def _network_ls(
        self,
        filter: str | None = None,
        format: str = "table"
    ) -> dict[str, Any]:
        """List networks."""
        args = ["network", "ls"]

        if filter:
            args.extend(["--filter", filter])

        if format:
            args.extend(["--format", format])

        return self._run_docker(args)

    async def _network_create(
        self,
        name: str,
        driver: str = "bridge",
        subnet: str | None = None
    ) -> dict[str, Any]:
        """Create a network."""
        args = ["network", "create", "--driver", driver]

        if subnet:
            args.extend(["--subnet", subnet])

        args.append(name)
        return self._run_docker(args)

    async def _network_rm(self, name: str) -> dict[str, Any]:
        """Remove a network."""
        args = ["network", "rm", name]
        return self._run_docker(args)

    # -------------------------------------------------------------------------
    # Volume Operations
    # -------------------------------------------------------------------------

    async def _volume_ls(self, filter: str | None = None) -> dict[str, Any]:
        """List volumes."""
        args = ["volume", "ls"]

        if filter:
            args.extend(["--filter", filter])

        return self._run_docker(args)

    async def _volume_create(
        self,
        name: str,
        driver: str = "local"
    ) -> dict[str, Any]:
        """Create a volume."""
        args = ["volume", "create", "--driver", driver, name]
        return self._run_docker(args)

    async def _volume_rm(
        self,
        name: str,
        force: bool = False
    ) -> dict[str, Any]:
        """Remove a volume."""
        args = ["volume", "rm"]

        if force:
            args.append("--force")

        args.append(name)
        return self._run_docker(args)

    # -------------------------------------------------------------------------
    # System Operations
    # -------------------------------------------------------------------------

    async def _system_prune(
        self,
        all: bool = False,
        volumes: bool = False,
        force: bool = False
    ) -> dict[str, Any]:
        """Remove unused data."""
        args = ["system", "prune"]

        if all:
            args.append("--all")

        if volumes:
            args.append("--volumes")

        if force:
            args.append("--force")

        return self._run_docker(args, timeout=120)

    # -------------------------------------------------------------------------
    # Docker Compose
    # -------------------------------------------------------------------------

    async def _compose_up(
        self,
        file: str = "docker-compose.yml",
        services: list[str] | None = None,
        detach: bool = True,
        build: bool = False
    ) -> dict[str, Any]:
        """Start services with Docker Compose."""
        args = ["-f", file, "up"]

        if detach:
            args.append("--detach")

        if build:
            args.append("--build")

        if services:
            args.extend(services)

        return self._run_compose(args, timeout=300)

    async def _compose_down(
        self,
        file: str = "docker-compose.yml",
        volumes: bool = False,
        remove_orphans: bool = False
    ) -> dict[str, Any]:
        """Stop and remove containers with Docker Compose."""
        args = ["-f", file, "down"]

        if volumes:
            args.append("--volumes")

        if remove_orphans:
            args.append("--remove-orphans")

        return self._run_compose(args, timeout=120)
