"""Registry operations for pushing images."""

from __future__ import annotations

import logging
from datetime import datetime
from typing import Iterator

import docker
from docker.errors import APIError, ImageNotFound

from gaol.build.exceptions import (
    ImageNotFoundError,
    RegistryAuthenticationError,
    RegistryPushError,
)
from gaol.build.models import PushProgress, PushResult

logger = logging.getLogger(__name__)


class RegistryClient:
    """Client for container registry operations.

    Handles authentication and push operations to Docker registries.
    """

    def __init__(self) -> None:
        """Initialize registry client."""
        self._client: docker.DockerClient | None = None

    @property
    def client(self) -> docker.DockerClient:
        """Get or create Docker client."""
        if self._client is None:
            self._client = docker.from_env()
        return self._client

    def login(
        self,
        registry: str,
        username: str,
        password: str,
    ) -> bool:
        """Login to a registry.

        Args:
            registry: Registry URL
            username: Registry username
            password: Registry password

        Returns:
            True if login successful
        """
        try:
            result = self.client.login(
                username=username,
                password=password,
                registry=registry,
            )
            return result.get("Status") == "Login Succeeded"
        except APIError as e:
            raise RegistryAuthenticationError(registry, str(e)) from e

    def push(
        self,
        image_ref: str,
        registry: str | None = None,
    ) -> PushResult:
        """Push image to registry.

        Args:
            image_ref: Image reference (e.g., "myimage:latest")
            registry: Target registry URL (if different from image ref)

        Returns:
            PushResult with outcome
        """
        result = PushResult(
            success=False,
            image_tag=image_ref,
            registry=registry or self._extract_registry(image_ref),
            started_at=datetime.now(),
        )

        try:
            # Get image
            try:
                image = self.client.images.get(image_ref)
            except ImageNotFound:
                raise ImageNotFoundError(image_ref)

            # Tag for registry if needed
            push_tag = image_ref
            if registry:
                # Prepend registry to image name
                image_name = image_ref.split("/")[-1]  # Get just name:tag part
                push_tag = f"{registry}/{image_name}"

                # Tag the image
                image.tag(push_tag)

            # Push
            push_output = self.client.images.push(
                push_tag,
                stream=True,
                decode=True,
            )

            # Process push output
            digest = None
            total_size = 0

            for line in push_output:
                if "error" in line:
                    raise RegistryPushError(
                        result.registry,
                        push_tag,
                        line.get("error", "Unknown error"),
                    )

                if "aux" in line and "Digest" in line.get("aux", {}):
                    digest = line["aux"]["Digest"]

                if "size" in line:
                    total_size += line.get("size", 0)

            result.success = True
            result.digest = digest
            result.size_bytes = total_size

        except ImageNotFoundError:
            result.error = f"Image not found: {image_ref}"
        except RegistryPushError as e:
            result.error = str(e)
        except APIError as e:
            result.error = f"Push failed: {e}"
        except Exception as e:
            result.error = f"Unexpected error: {e}"
            logger.exception("Push failed")

        result.finished_at = datetime.now()
        return result

    def push_with_progress(
        self,
        image_ref: str,
        registry: str | None = None,
    ) -> Iterator[PushProgress]:
        """Push image with progress updates.

        Args:
            image_ref: Image reference
            registry: Target registry URL

        Yields:
            PushProgress updates
        """
        try:
            image = self.client.images.get(image_ref)
        except ImageNotFound:
            raise ImageNotFoundError(image_ref)

        # Tag for registry if needed
        push_tag = image_ref
        if registry:
            image_name = image_ref.split("/")[-1]
            push_tag = f"{registry}/{image_name}"
            image.tag(push_tag)

        # Push with streaming
        for line in self.client.images.push(push_tag, stream=True, decode=True):
            if "error" in line:
                raise RegistryPushError(
                    registry or self._extract_registry(image_ref),
                    push_tag,
                    line.get("error", "Unknown error"),
                )

            yield PushProgress(
                status=line.get("status", ""),
                progress=line.get("progress"),
                layer_id=line.get("id"),
                current=line.get("progressDetail", {}).get("current", 0),
                total=line.get("progressDetail", {}).get("total", 0),
            )

    def _extract_registry(self, image_ref: str) -> str:
        """Extract registry from image reference."""
        parts = image_ref.split("/")
        if len(parts) > 1 and ("." in parts[0] or ":" in parts[0]):
            return parts[0]
        return "docker.io"


def push_image(
    image_ref: str,
    registry: str | None = None,
) -> PushResult:
    """Push image to registry.

    High-level function for pushing images.

    Args:
        image_ref: Image reference
        registry: Target registry

    Returns:
        PushResult with outcome
    """
    client = RegistryClient()
    return client.push(image_ref, registry)


def push_image_with_progress(
    image_ref: str,
    registry: str | None = None,
) -> Iterator[PushProgress]:
    """Push image with progress updates.

    Args:
        image_ref: Image reference
        registry: Target registry

    Yields:
        PushProgress updates
    """
    client = RegistryClient()
    yield from client.push_with_progress(image_ref, registry)
