"""
koci Docker Runtime Implementation

Docker container runtime using the docker-py SDK.
"""

import io
import os
import tarfile
from typing import Optional, Iterator

try:
    import docker
    from docker.errors import DockerException, NotFound, APIError, ImageNotFound
    DOCKER_AVAILABLE = True
except ImportError:
    DOCKER_AVAILABLE = False
    docker = None
    DockerException = Exception
    NotFound = Exception
    APIError = Exception
    ImageNotFound = Exception

from .container import (
    ContainerConfig,
    ContainerInfo,
    ExecResult,
    RuntimeType,
)


class DockerRuntime:
    """
    Docker container runtime implementation.

    Uses the docker-py SDK to interact with Docker.
    """

    def __init__(self):
        """Initialize Docker client."""
        if not DOCKER_AVAILABLE:
            raise RuntimeError(
                "docker-py is not installed. Run: pip install docker"
            )

        try:
            self._client = docker.from_env()
            # Test connection
            self._client.ping()
        except DockerException as e:
            raise RuntimeError(f"Failed to connect to Docker: {e}")

    @property
    def runtime_type(self) -> RuntimeType:
        return RuntimeType.DOCKER

    @property
    def client(self):
        """Get the underlying Docker client."""
        return self._client

    def is_available(self) -> bool:
        """Check if Docker is available."""
        try:
            self._client.ping()
            return True
        except Exception:
            return False

    def pull_image(self, image: str, quiet: bool = False) -> bool:
        """Pull a Docker image."""
        try:
            if not quiet:
                # Stream pull progress
                for line in self._client.api.pull(image, stream=True, decode=True):
                    if 'status' in line and not quiet:
                        status = line.get('status', '')
                        progress = line.get('progress', '')
                        if progress:
                            print(f"\r{status}: {progress}", end='', flush=True)
                if not quiet:
                    print()  # Newline after progress
            else:
                self._client.images.pull(image)
            return True
        except (APIError, ImageNotFound) as e:
            if not quiet:
                print(f"Failed to pull image {image}: {e}")
            return False

    def image_exists(self, image: str) -> bool:
        """Check if an image exists locally."""
        try:
            self._client.images.get(image)
            return True
        except ImageNotFound:
            return False

    def create_container(self, config: ContainerConfig) -> str:
        """Create a new container."""
        # Build volumes configuration
        volumes = {}
        for host_path, container_config in config.volumes.items():
            bind_path = container_config.get('bind', host_path)
            mode = container_config.get('mode', 'rw')
            volumes[host_path] = {'bind': bind_path, 'mode': mode}

        # Build port bindings
        ports = {}
        for container_port, host_port in config.ports.items():
            ports[container_port] = host_port

        try:
            container = self._client.containers.create(
                image=config.image,
                name=config.name,
                command=config.command,
                entrypoint=config.entrypoint,
                environment=config.env,
                volumes=volumes if volumes else None,
                ports=ports if ports else None,
                working_dir=config.working_dir,
                network=config.network,
                labels=config.labels,
                user=config.user,
                privileged=config.privileged,
                auto_remove=config.auto_remove,
                detach=config.detach,
                stdin_open=True,
                tty=False,
            )
            return container.id
        except (APIError, ImageNotFound) as e:
            raise RuntimeError(f"Failed to create container: {e}")

    def start_container(self, container_id: str) -> bool:
        """Start a container."""
        try:
            container = self._client.containers.get(container_id)
            container.start()
            return True
        except (NotFound, APIError) as e:
            print(f"Failed to start container {container_id}: {e}")
            return False

    def stop_container(self, container_id: str, timeout: int = 10) -> bool:
        """Stop a container."""
        try:
            container = self._client.containers.get(container_id)
            container.stop(timeout=timeout)
            return True
        except NotFound:
            return True  # Already gone
        except APIError as e:
            print(f"Failed to stop container {container_id}: {e}")
            return False

    def remove_container(self, container_id: str, force: bool = False) -> bool:
        """Remove a container."""
        try:
            container = self._client.containers.get(container_id)
            container.remove(force=force)
            return True
        except NotFound:
            return True  # Already gone
        except APIError as e:
            print(f"Failed to remove container {container_id}: {e}")
            return False

    def exec_command(
        self,
        container_id: str,
        command: list[str],
        working_dir: Optional[str] = None,
        env: Optional[dict[str, str]] = None,
        user: Optional[str] = None,
    ) -> ExecResult:
        """Execute a command in a container."""
        try:
            container = self._client.containers.get(container_id)

            # Build environment list
            environment = None
            if env:
                environment = [f"{k}={v}" for k, v in env.items()]

            result = container.exec_run(
                cmd=command,
                workdir=working_dir,
                environment=environment,
                user=user,
                demux=True,  # Separate stdout/stderr
            )

            stdout = result.output[0].decode('utf-8') if result.output[0] else ''
            stderr = result.output[1].decode('utf-8') if result.output[1] else ''

            return ExecResult(
                exit_code=result.exit_code,
                stdout=stdout,
                stderr=stderr,
            )
        except (NotFound, APIError) as e:
            return ExecResult(
                exit_code=1,
                stdout='',
                stderr=str(e),
            )

    def exec_stream(
        self,
        container_id: str,
        command: list[str],
        working_dir: Optional[str] = None,
        env: Optional[dict[str, str]] = None,
    ) -> Iterator[str]:
        """Execute a command and stream output."""
        try:
            container = self._client.containers.get(container_id)

            environment = None
            if env:
                environment = [f"{k}={v}" for k, v in env.items()]

            # Create exec instance
            exec_id = self._client.api.exec_create(
                container.id,
                cmd=command,
                workdir=working_dir,
                environment=environment,
            )

            # Start and stream
            output = self._client.api.exec_start(exec_id, stream=True)

            buffer = ''
            for chunk in output:
                text = chunk.decode('utf-8')
                buffer += text

                # Yield complete lines
                while '\n' in buffer:
                    line, buffer = buffer.split('\n', 1)
                    yield line

            # Yield any remaining content
            if buffer:
                yield buffer

        except (NotFound, APIError) as e:
            yield f"Error: {e}"

    def copy_to_container(
        self,
        container_id: str,
        src_path: str,
        dest_path: str,
    ) -> bool:
        """Copy files from host to container."""
        try:
            container = self._client.containers.get(container_id)

            # Create a tar archive of the source
            stream = io.BytesIO()
            with tarfile.open(fileobj=stream, mode='w') as tar:
                arcname = os.path.basename(src_path)
                tar.add(src_path, arcname=arcname)

            stream.seek(0)
            container.put_archive(dest_path, stream)
            return True

        except (NotFound, APIError) as e:
            print(f"Failed to copy to container: {e}")
            return False

    def copy_from_container(
        self,
        container_id: str,
        src_path: str,
        dest_path: str,
    ) -> bool:
        """Copy files from container to host."""
        try:
            container = self._client.containers.get(container_id)

            # Get archive from container
            bits, stat = container.get_archive(src_path)

            # Extract to destination
            stream = io.BytesIO()
            for chunk in bits:
                stream.write(chunk)
            stream.seek(0)

            with tarfile.open(fileobj=stream, mode='r') as tar:
                tar.extractall(path=dest_path)

            return True

        except (NotFound, APIError) as e:
            print(f"Failed to copy from container: {e}")
            return False

    def get_container_info(self, container_id: str) -> Optional[ContainerInfo]:
        """Get information about a container."""
        try:
            container = self._client.containers.get(container_id)

            # Extract port mappings
            ports = {}
            if container.ports:
                for container_port, bindings in container.ports.items():
                    if bindings:
                        ports[container_port] = bindings[0].get('HostPort', '')

            return ContainerInfo(
                id=container.id,
                name=container.name,
                image=container.image.tags[0] if container.image.tags else container.image.id,
                status=container.status,
                runtime=RuntimeType.DOCKER,
                ports=ports,
                labels=container.labels or {},
            )

        except NotFound:
            return None

    def container_logs(
        self,
        container_id: str,
        follow: bool = False,
        tail: Optional[int] = None,
    ) -> Iterator[str]:
        """Get container logs."""
        try:
            container = self._client.containers.get(container_id)

            logs = container.logs(
                stream=follow,
                follow=follow,
                tail=tail if tail else 'all',
            )

            if follow:
                for line in logs:
                    yield line.decode('utf-8').rstrip('\n')
            else:
                yield logs.decode('utf-8')

        except (NotFound, APIError) as e:
            yield f"Error: {e}"

    def create_network(self, name: str) -> str:
        """Create a Docker network."""
        try:
            network = self._client.networks.create(
                name=name,
                driver='bridge',
                labels={'koci': 'true'},
            )
            return network.id
        except APIError as e:
            # Network might already exist
            try:
                network = self._client.networks.get(name)
                return network.id
            except NotFound:
                raise RuntimeError(f"Failed to create network: {e}")

    def remove_network(self, name: str) -> bool:
        """Remove a Docker network."""
        try:
            network = self._client.networks.get(name)
            network.remove()
            return True
        except NotFound:
            return True  # Already gone
        except APIError as e:
            print(f"Failed to remove network {name}: {e}")
            return False

    def wait_for_container(
        self,
        container_id: str,
        timeout: Optional[int] = None,
    ) -> int:
        """Wait for a container to finish."""
        try:
            container = self._client.containers.get(container_id)
            result = container.wait(timeout=timeout)
            return result.get('StatusCode', 1)
        except (NotFound, APIError) as e:
            print(f"Error waiting for container: {e}")
            return 1

    def cleanup_koci_resources(self):
        """Remove all koci-created containers, networks, and volumes (keeps images)."""
        # Remove containers with koci label
        for container in self._client.containers.list(
            all=True,
            filters={'label': 'koci=true'}
        ):
            try:
                container.remove(force=True)
            except Exception:
                pass

        # Remove networks with koci label
        for network in self._client.networks.list(
            filters={'label': 'koci=true'}
        ):
            try:
                network.remove()
            except Exception:
                pass

        # Remove dangling volumes created by koci containers
        try:
            self._client.volumes.prune(filters={'label': 'koci=true'})
        except Exception:
            pass

        # Also prune any anonymous volumes left behind
        try:
            self._client.volumes.prune(filters={'dangling': 'true'})
        except Exception:
            pass
