"""Docker image activities using docker-py SDK."""

import hashlib
import logging
from dataclasses import dataclass
from pathlib import Path

import docker
from docker.errors import ImageNotFound

from wunderunner.exceptions import BuildError

logger = logging.getLogger(__name__)


@dataclass
class BuildResult:
    """Result of a Docker build operation."""

    image_id: str
    cache_hit: bool


def _get_client() -> docker.DockerClient:
    """Get Docker client from environment."""
    return docker.from_env()


def _compute_cache_tag(path: Path, dockerfile_content: str) -> str:
    """Generate stable tag from project path and Dockerfile content.

    Uses hashes of both to create a unique, reproducible tag that allows
    cache hits when the same Dockerfile is used for the same project.
    """
    path_hash = hashlib.sha256(str(path.resolve()).encode()).hexdigest()[:8]
    content_hash = hashlib.sha256(dockerfile_content.encode()).hexdigest()[:8]
    return f"wunderunner-{path_hash}-{content_hash}"


def _image_exists(client: docker.DockerClient, tag: str) -> bool:
    """Check if a Docker image with this tag exists locally."""
    try:
        client.images.get(tag)
        return True
    except ImageNotFound:
        return False


async def build(path: Path, dockerfile_content: str) -> BuildResult:
    """Build Docker image from dockerfile content, using cache if available.

    Writes the Dockerfile to the project directory and runs docker build.
    Uses content-based caching to skip rebuilds when Dockerfile is unchanged.

    Args:
        path: Path to the project directory (build context).
        dockerfile_content: The Dockerfile content to build.

    Returns:
        BuildResult with image_id and cache_hit flag.

    Raises:
        BuildError: If build fails.
    """
    dockerfile_path = path / "Dockerfile"

    # Write Dockerfile
    dockerfile_path.write_text(dockerfile_content)

    # Generate stable tag for caching
    tag = _compute_cache_tag(path, dockerfile_content)

    client = _get_client()

    # Check if image already exists (cache hit)
    if _image_exists(client, tag):
        image = client.images.get(tag)
        return BuildResult(image_id=image.id, cache_hit=True)

    # Build the image with streaming logs
    # Use low-level API to get streaming logs
    build_output = client.api.build(
        path=str(path),
        dockerfile=str(dockerfile_path.relative_to(path)),
        tag=tag,
        rm=True,
        forcerm=True,
        decode=True,
    )

    logs = []
    has_error = False
    for chunk in build_output:
        if "stream" in chunk:
            line = chunk["stream"].rstrip()
            if line:
                logger.info("  %s", line)
                logs.append(chunk["stream"])
        elif "error" in chunk:
            logger.error("  %s", chunk["error"])
            logs.append(chunk["error"])
            has_error = True
        elif "status" in chunk:
            # Pull progress
            status = chunk.get("status", "")
            progress = chunk.get("progress", "")
            if progress:
                logger.debug("  %s %s", status, progress)
            elif status:
                logger.info("  %s", status)

    # Check if build failed
    if has_error:
        output = "".join(logs)
        raise BuildError(f"Docker build failed:\n{output}")

    # Get the built image
    try:
        image = client.images.get(tag)
        return BuildResult(image_id=image.id, cache_hit=False)
    except ImageNotFound as e:
        # Build completed but image not found - include logs for debugging
        output = "".join(logs)
        raise BuildError(f"Docker build failed (image not created):\n{output}") from e
