"""Container execution for paude."""

from __future__ import annotations

import json
import subprocess
import time
from pathlib import Path
from typing import Any


class ProxyStartError(Exception):
    """Error starting the proxy container."""

    pass


class ContainerNotFoundError(Exception):
    """Container not found."""

    pass


class ContainerRunner:
    """Runs paude containers."""

    _proxy_counter = 0

    def create_secret(self, name: str, source_file: Path) -> None:
        """(Re)Create a Podman secret from a file.

        Args:
            name: Secret name.
            source_file: Path to the source file.

        Raises:
            subprocess.CalledProcessError: If secret creation fails.
        """
        subprocess.run(
            ["podman", "secret", "create", "--replace=true", name, str(source_file)],
            capture_output=True,
            check=True,
        )

    def remove_secret(self, name: str) -> None:
        """Remove a Podman secret, ignoring errors.

        Args:
            name: Secret name.
        """
        subprocess.run(
            ["podman", "secret", "rm", name],
            capture_output=True,
        )

    def create_container(
        self,
        name: str,
        image: str,
        mounts: list[str],
        env: dict[str, str],
        workdir: str,
        network: str | None = None,
        labels: dict[str, str] | None = None,
        entrypoint: str | None = None,
        command: list[str] | None = None,
        secrets: list[str] | None = None,
    ) -> str:
        """Create a container without starting it.

        Args:
            name: Container name.
            image: Container image to run.
            mounts: Volume mount arguments.
            env: Environment variables.
            workdir: Working directory inside the container.
            network: Optional network to attach to.
            labels: Labels to attach to the container.
            entrypoint: Optional entrypoint override.
            command: Optional command to run (after image in podman create).
            secrets: Optional list of secret specs (e.g.,
                ["name,target=/path"]).

        Returns:
            Container ID.

        Raises:
            subprocess.CalledProcessError: If container creation fails.
        """
        cmd = [
            "podman",
            "create",
            "--name",
            name,
            "--hostname",
            "paude",
            "-w",
            workdir,
            "-it",
        ]

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

        if secrets:
            for secret in secrets:
                cmd.extend(["--secret", secret])

        cmd.extend(mounts)

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

        if labels:
            for key, value in labels.items():
                cmd.extend(["--label", f"{key}={value}"])

        if entrypoint:
            cmd.extend(["--entrypoint", entrypoint])

        cmd.append(image)

        if command:
            cmd.extend(command)

        result = subprocess.run(cmd, capture_output=True, text=True)
        if result.returncode != 0:
            raise subprocess.CalledProcessError(
                result.returncode, cmd, result.stdout, result.stderr
            )

        return result.stdout.strip()

    def start_container(self, name: str) -> None:
        """Start an existing container.

        Args:
            name: Container name.

        Raises:
            ContainerNotFoundError: If container doesn't exist.
        """
        result = subprocess.run(
            ["podman", "start", name],
            capture_output=True,
            text=True,
        )
        if result.returncode != 0:
            if "no such container" in result.stderr.lower():
                raise ContainerNotFoundError(f"Container not found: {name}")
            raise subprocess.CalledProcessError(
                result.returncode,
                ["podman", "start", name],
                result.stdout,
                result.stderr,
            )

    def stop_container(self, name: str) -> None:
        """Stop a container gracefully with SIGTERM.

        Uses 'podman stop' with a 1-second timeout. The proxy container
        is configured with shutdown_lifetime=0 so squid exits immediately
        on SIGTERM. The timeout is a fallback in case of issues.

        Args:
            name: Container name.
        """
        subprocess.run(
            ["podman", "stop", "-t", "1", name],
            capture_output=True,
        )

    def stop_container_graceful(self, name: str, timeout: int = 10) -> None:
        """Stop a container gracefully with timeout.

        Args:
            name: Container name.
            timeout: Seconds to wait before SIGKILL.
        """
        subprocess.run(
            ["podman", "stop", "-t", str(timeout), name],
            capture_output=True,
        )

    def remove_container(self, name: str, force: bool = False) -> None:
        """Remove a container.

        Args:
            name: Container name.
            force: Force removal even if running.
        """
        cmd = ["podman", "rm"]
        if force:
            cmd.append("-f")
        cmd.append(name)

        subprocess.run(cmd, capture_output=True)

    def attach_container(
        self,
        name: str,
        entrypoint: str | None = None,
        extra_env: dict[str, str] | None = None,
    ) -> int:
        """Attach to a running container.

        Args:
            name: Container name.
            entrypoint: Optional command to exec into.
            extra_env: Optional extra environment variables to pass to exec.
                Only applies when entrypoint is provided (podman exec mode).
                These env vars are NOT stored in the container definition.

        Returns:
            Exit code from the attached session.
        """
        if entrypoint:
            cmd = ["podman", "exec", "-it"]
            if extra_env:
                for key, value in extra_env.items():
                    cmd.extend(["-e", f"{key}={value}"])
            cmd.extend([name, entrypoint])
        else:
            cmd = ["podman", "attach", name]

        result = subprocess.run(cmd)
        return result.returncode

    def exec_container(
        self,
        name: str,
        command: list[str],
        interactive: bool = True,
        tty: bool = True,
    ) -> int:
        """Execute a command in a running container.

        Args:
            name: Container name.
            command: Command to execute.
            interactive: Enable interactive mode.
            tty: Allocate a TTY.

        Returns:
            Exit code from the command.
        """
        cmd = ["podman", "exec"]
        if interactive:
            cmd.append("-i")
        if tty:
            cmd.append("-t")
        cmd.append(name)
        cmd.extend(command)

        result = subprocess.run(cmd)
        return result.returncode

    def exec_in_container(
        self,
        name: str,
        command: list[str],
        check: bool = True,
    ) -> subprocess.CompletedProcess[str]:
        """Execute a command in a running container and capture output.

        Args:
            name: Container name.
            command: Command to execute.
            check: Raise exception on non-zero exit code.

        Returns:
            CompletedProcess with stdout, stderr, and returncode.
        """
        cmd = ["podman", "exec", name, *command]
        return subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            check=check,
        )

    def container_exists(self, name: str) -> bool:
        """Check if a container exists.

        Args:
            name: Container name.

        Returns:
            True if container exists.
        """
        result = subprocess.run(
            ["podman", "container", "exists", name],
            capture_output=True,
        )
        return result.returncode == 0

    def container_running(self, name: str) -> bool:
        """Check if a container is running.

        Args:
            name: Container name.

        Returns:
            True if container is running.
        """
        result = subprocess.run(
            ["podman", "inspect", "-f", "{{.State.Running}}", name],
            capture_output=True,
            text=True,
        )
        return result.returncode == 0 and result.stdout.strip() == "true"

    def get_container_state(self, name: str) -> str | None:
        """Get the state of a container.

        Args:
            name: Container name.

        Returns:
            Container state string or None if not found.
        """
        result = subprocess.run(
            ["podman", "inspect", "-f", "{{.State.Status}}", name],
            capture_output=True,
            text=True,
        )
        if result.returncode != 0:
            return None
        return result.stdout.strip()

    def list_containers(
        self,
        label_filter: str | None = None,
        all_containers: bool = True,
    ) -> list[dict[str, Any]]:
        """List containers with optional label filter.

        Args:
            label_filter: Label filter (e.g., "app=paude").
            all_containers: Include stopped containers.

        Returns:
            List of container info dictionaries.
        """
        cmd = ["podman", "ps", "--format", "json"]
        if all_containers:
            cmd.append("-a")
        if label_filter:
            cmd.extend(["--filter", f"label={label_filter}"])

        result = subprocess.run(cmd, capture_output=True, text=True)
        if result.returncode != 0:
            return []

        try:
            return json.loads(result.stdout) if result.stdout.strip() else []
        except json.JSONDecodeError:
            return []

    def run_proxy(
        self,
        image: str,
        network: str,
        dns: str | None = None,
        allowed_domains: list[str] | None = None,
    ) -> str:
        """Start the proxy container.

        Args:
            image: Proxy image to run.
            network: Network to attach to.
            dns: Optional DNS IP for squid to use (passed as SQUID_DNS env var).
            allowed_domains: List of domains to allow. If provided, passed as
                ALLOWED_DOMAINS env var (comma-separated).

        Returns:
            Container name.

        Raises:
            ProxyStartError: If the proxy container fails to start.
        """
        # Generate unique container name using timestamp and counter
        ContainerRunner._proxy_counter += 1
        session_id = f"{int(time.time())}-{ContainerRunner._proxy_counter}"
        container_name = f"paude-proxy-{session_id}"

        # Connect to both internal network and podman network for external access
        cmd = [
            "podman",
            "run",
            "-d",
            "--rm",
            "--name",
            container_name,
            "--network",
            f"{network},podman",
        ]

        # Pass DNS IP as environment variable for squid to use
        if dns:
            cmd.extend(["-e", f"SQUID_DNS={dns}"])

        # Pass allowed domains as comma-separated list
        if allowed_domains:
            domains_str = ",".join(allowed_domains)
            cmd.extend(["-e", f"ALLOWED_DOMAINS={domains_str}"])

        cmd.append(image)

        result = subprocess.run(cmd, capture_output=True)
        if result.returncode != 0:
            stderr = result.stderr.decode("utf-8", errors="replace")
            raise ProxyStartError(f"Failed to start proxy: {stderr}")

        # Give proxy time to initialize (matches bash sleep 1)
        time.sleep(1)

        return container_name

    def create_session_proxy(
        self,
        name: str,
        image: str,
        network: str,
        dns: str | None = None,
        allowed_domains: list[str] | None = None,
    ) -> str:
        """Create a proxy container for a session (does not start it).

        Unlike run_proxy() which uses --rm, this creates a persistent proxy
        container that can be started/stopped with the session.

        Args:
            name: Deterministic container name (e.g., "paude-proxy-my-session").
            image: Proxy image to run.
            network: Internal network name. Proxy joins both this and "podman".
            dns: Optional DNS IP for squid (passed as SQUID_DNS env var).
            allowed_domains: Domains to allow (passed as ALLOWED_DOMAINS env var).

        Returns:
            Container name.
        """
        cmd = [
            "podman",
            "create",
            "--pull=never",
            "--name",
            name,
            "--network",
            f"{network},podman",
        ]

        if dns:
            cmd.extend(["-e", f"SQUID_DNS={dns}"])

        if allowed_domains:
            domains_str = ",".join(allowed_domains)
            cmd.extend(["-e", f"ALLOWED_DOMAINS={domains_str}"])

        cmd.append(image)

        result = subprocess.run(cmd, capture_output=True, text=True)
        if result.returncode != 0:
            raise ProxyStartError(f"Failed to create proxy: {result.stderr}")

        return name

    def start_session_proxy(self, name: str) -> None:
        """Start a session proxy container and wait for it to initialize.

        Args:
            name: Proxy container name.

        Raises:
            ProxyStartError: If the proxy fails to start.
        """
        result = subprocess.run(
            ["podman", "start", name],
            capture_output=True,
            text=True,
        )
        if result.returncode != 0:
            raise ProxyStartError(f"Failed to start proxy: {result.stderr}")

        # Give proxy time to initialize
        time.sleep(1)

    def recreate_session_proxy(
        self,
        name: str,
        image: str,
        network: str,
        dns: str | None = None,
        allowed_domains: list[str] | None = None,
    ) -> str:
        """Recreate a session proxy with new configuration.

        Stops and removes the old proxy, then creates and starts a new one.

        Args:
            name: Proxy container name.
            image: Proxy image.
            network: Internal network name.
            dns: Optional DNS IP.
            allowed_domains: New list of allowed domains.

        Returns:
            Container name.
        """
        # Stop and remove old proxy
        self.stop_container(name)
        self.remove_container(name, force=True)

        # Create and start new proxy
        self.create_session_proxy(
            name=name,
            image=image,
            network=network,
            dns=dns,
            allowed_domains=allowed_domains,
        )
        self.start_session_proxy(name)

        return name

    def get_container_image(self, name: str) -> str | None:
        """Get the image name of a container.

        Args:
            name: Container name.

        Returns:
            Image name string, or None if not found.
        """
        result = subprocess.run(
            ["podman", "inspect", "-f", "{{.ImageName}}", name],
            capture_output=True,
            text=True,
        )
        if result.returncode != 0:
            return None
        return result.stdout.strip() or None

    def get_container_env(self, name: str, var_name: str) -> str | None:
        """Get an environment variable from a container's config.

        Args:
            name: Container name.
            var_name: Environment variable name.

        Returns:
            Value of the variable, or None if not found.
        """
        result = subprocess.run(
            ["podman", "inspect", "-f", "{{json .Config.Env}}", name],
            capture_output=True,
            text=True,
        )
        if result.returncode != 0:
            return None

        try:
            env_list = json.loads(result.stdout.strip())
            prefix = f"{var_name}="
            for entry in env_list:
                if entry.startswith(prefix):
                    return str(entry[len(prefix) :])
        except (json.JSONDecodeError, TypeError):
            pass

        return None

    def run_post_create(
        self,
        image: str,
        mounts: list[str],
        env: dict[str, str],
        command: str,
        workdir: str,
        network: str | None = None,
    ) -> bool:
        """Run the postCreateCommand.

        Args:
            image: Container image to use.
            mounts: Volume mount arguments.
            env: Environment variables.
            command: Command to run.
            workdir: Working directory for the command.
            network: Optional network.

        Returns:
            True if successful.
        """
        cmd = [
            "podman",
            "run",
            "--rm",
            "-w",
            workdir,
        ]

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

        cmd.extend(mounts)

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

        # Use /bin/bash to match bash implementation
        cmd.extend([image, "/bin/bash", "-c", command])

        result = subprocess.run(cmd)
        return result.returncode == 0
