"""Prometheus metrics exporter for container statistics."""

from __future__ import annotations

import logging
import threading
from datetime import datetime
from typing import TYPE_CHECKING

from prometheus_client import CollectorRegistry, Gauge, generate_latest

from gaol.metrics.exceptions import MetricsCollectionError
from gaol.metrics.models import ContainerMetrics, MetricsSnapshot
from gaol.models.container import ContainerStatus
from gaol.runtimes import get_runtime
from gaol.store import get_container_store

if TYPE_CHECKING:
    from gaol.store import ContainerStore

logger = logging.getLogger(__name__)


class PrometheusExporter:
    """Collects container metrics and exposes them in Prometheus format.

    This class manages Prometheus Gauge metrics for container resources
    and provides methods to collect and update metrics from running containers.

    Example:
        >>> exporter = PrometheusExporter()
        >>> exporter.collect()  # Update all metrics
        >>> metrics_output = exporter.generate_metrics()  # Get Prometheus format
    """

    def __init__(
        self,
        container_store: ContainerStore | None = None,
        registry: CollectorRegistry | None = None,
    ) -> None:
        """Initialize the exporter.

        Args:
            container_store: Container store (defaults to global instance)
            registry: Prometheus registry (defaults to a new isolated registry)
        """
        self._container_store = container_store or get_container_store()
        self._registry = registry or CollectorRegistry()
        self._lock = threading.Lock()
        self._last_snapshot: MetricsSnapshot | None = None
        self._known_containers: set[tuple[str, str]] = set()  # (name, runtime)

        # Create Prometheus Gauge metrics
        self._cpu_gauge = Gauge(
            "gaol_container_cpu_percent",
            "CPU usage percentage",
            ["container_name", "runtime"],
            registry=self._registry,
        )

        self._memory_gauge = Gauge(
            "gaol_container_memory_bytes",
            "Memory usage in bytes",
            ["container_name", "runtime"],
            registry=self._registry,
        )

        self._memory_limit_gauge = Gauge(
            "gaol_container_memory_limit_bytes",
            "Memory limit in bytes",
            ["container_name", "runtime"],
            registry=self._registry,
        )

        self._memory_percent_gauge = Gauge(
            "gaol_container_memory_percent",
            "Memory usage percentage",
            ["container_name", "runtime"],
            registry=self._registry,
        )

        self._network_rx_gauge = Gauge(
            "gaol_container_network_rx_bytes",
            "Network bytes received",
            ["container_name", "runtime"],
            registry=self._registry,
        )

        self._network_tx_gauge = Gauge(
            "gaol_container_network_tx_bytes",
            "Network bytes transmitted",
            ["container_name", "runtime"],
            registry=self._registry,
        )

        self._block_read_gauge = Gauge(
            "gaol_container_block_read_bytes",
            "Block device bytes read",
            ["container_name", "runtime"],
            registry=self._registry,
        )

        self._block_write_gauge = Gauge(
            "gaol_container_block_write_bytes",
            "Block device bytes written",
            ["container_name", "runtime"],
            registry=self._registry,
        )

        self._pids_gauge = Gauge(
            "gaol_container_pids",
            "Number of processes",
            ["container_name", "runtime"],
            registry=self._registry,
        )

        self._info_gauge = Gauge(
            "gaol_container_info",
            "Container metadata (always 1 if running)",
            ["container_name", "runtime", "status", "distro", "profile"],
            registry=self._registry,
        )

        self._up_gauge = Gauge(
            "gaol_up",
            "Whether gaol metrics collection is working (1 = up)",
            registry=self._registry,
        )

    @property
    def registry(self) -> CollectorRegistry:
        """Get the Prometheus collector registry."""
        return self._registry

    @property
    def last_snapshot(self) -> MetricsSnapshot | None:
        """Get the last collected metrics snapshot."""
        return self._last_snapshot

    def collect(
        self, containers: list[str] | None = None
    ) -> MetricsSnapshot:
        """Collect metrics from containers and update Prometheus gauges.

        Args:
            containers: Specific container names to collect (None = all)

        Returns:
            MetricsSnapshot with collected metrics
        """
        with self._lock:
            snapshot = MetricsSnapshot(collected_at=datetime.now())
            current_containers: set[tuple[str, str]] = set()

            # Get all containers from store
            all_containers = self._container_store.list()

            for stored in all_containers:
                # Filter by name if specified
                if containers and stored.config.name not in containers:
                    continue

                container_name = stored.config.name
                runtime_name = stored.config.runtime

                # Collect metrics for this container
                try:
                    metrics = self._collect_container_metrics(stored)
                    if metrics:
                        snapshot.containers.append(metrics)
                        current_containers.add((container_name, runtime_name))
                        self._update_gauges(metrics, stored)
                except MetricsCollectionError as e:
                    logger.warning(f"Failed to collect metrics for {container_name}: {e}")
                except Exception as e:
                    logger.error(f"Unexpected error collecting metrics for {container_name}: {e}")

            # Clear stale metrics for containers that no longer exist
            stale_containers = self._known_containers - current_containers
            for name, runtime in stale_containers:
                self._clear_container_metrics(name, runtime)

            self._known_containers = current_containers
            self._last_snapshot = snapshot
            self._up_gauge.set(1)

            return snapshot

    def _collect_container_metrics(self, stored) -> ContainerMetrics | None:
        """Collect metrics for a single container.

        Args:
            stored: StoredContainer from the container store

        Returns:
            ContainerMetrics or None if container is not running
        """
        container_name = stored.config.name
        runtime_name = stored.config.runtime

        # Get container status
        status = ContainerStatus.UNKNOWN
        if stored.runtime_id:
            try:
                runtime = get_runtime(runtime_name)
                status_info = runtime.status(stored.runtime_id)
                status = status_info.status
            except Exception:
                status = ContainerStatus.UNKNOWN

        # Build basic metrics with container info
        metrics = ContainerMetrics(
            container_name=container_name,
            runtime=runtime_name,
            status=status.value,
            collected_at=datetime.now(),
        )

        # Only collect resource stats if container is running
        if status == ContainerStatus.RUNNING and stored.runtime_id:
            try:
                runtime = get_runtime(runtime_name)
                stats = runtime.stats(stored.runtime_id)
                if stats:
                    metrics.cpu_percent = stats.cpu_percent
                    metrics.memory_used_bytes = stats.memory_used_bytes
                    metrics.memory_limit_bytes = stats.memory_limit_bytes
                    metrics.network_rx_bytes = stats.network_rx_bytes
                    metrics.network_tx_bytes = stats.network_tx_bytes
                    metrics.block_read_bytes = stats.block_read_bytes
                    metrics.block_write_bytes = stats.block_write_bytes
                    metrics.pids = stats.pids

                    # Calculate memory percentage
                    if stats.memory_limit_bytes > 0:
                        metrics.memory_percent = (
                            stats.memory_used_bytes / stats.memory_limit_bytes * 100
                        )
            except Exception as e:
                raise MetricsCollectionError(
                    container_name, f"Failed to get stats: {e}", e
                )

        return metrics

    def _update_gauges(self, metrics: ContainerMetrics, stored) -> None:
        """Update Prometheus gauges with container metrics.

        Args:
            metrics: Collected container metrics
            stored: StoredContainer with config info
        """
        labels = {"container_name": metrics.container_name, "runtime": metrics.runtime}

        self._cpu_gauge.labels(**labels).set(metrics.cpu_percent)
        self._memory_gauge.labels(**labels).set(metrics.memory_used_bytes)
        self._memory_limit_gauge.labels(**labels).set(metrics.memory_limit_bytes)
        self._memory_percent_gauge.labels(**labels).set(metrics.memory_percent)
        self._network_rx_gauge.labels(**labels).set(metrics.network_rx_bytes)
        self._network_tx_gauge.labels(**labels).set(metrics.network_tx_bytes)
        self._block_read_gauge.labels(**labels).set(metrics.block_read_bytes)
        self._block_write_gauge.labels(**labels).set(metrics.block_write_bytes)
        self._pids_gauge.labels(**labels).set(metrics.pids)

        # Info gauge with additional labels
        info_labels = {
            "container_name": metrics.container_name,
            "runtime": metrics.runtime,
            "status": metrics.status,
            "distro": stored.config.distro or "unknown",
            "profile": stored.config.profile or "default",
        }
        self._info_gauge.labels(**info_labels).set(1)

    def _clear_container_metrics(self, container_name: str, runtime: str) -> None:
        """Clear metrics for a container that no longer exists.

        Args:
            container_name: Name of the container
            runtime: Runtime type
        """
        labels = {"container_name": container_name, "runtime": runtime}

        try:
            self._cpu_gauge.remove(*labels.values())
            self._memory_gauge.remove(*labels.values())
            self._memory_limit_gauge.remove(*labels.values())
            self._memory_percent_gauge.remove(*labels.values())
            self._network_rx_gauge.remove(*labels.values())
            self._network_tx_gauge.remove(*labels.values())
            self._block_read_gauge.remove(*labels.values())
            self._block_write_gauge.remove(*labels.values())
            self._pids_gauge.remove(*labels.values())
        except KeyError:
            # Metrics may not exist, that's fine
            pass

    def generate_metrics(self) -> bytes:
        """Generate Prometheus exposition format output.

        Returns:
            Metrics in Prometheus text format as bytes
        """
        return generate_latest(self._registry)

    def generate_metrics_text(self) -> str:
        """Generate Prometheus exposition format output as string.

        Returns:
            Metrics in Prometheus text format
        """
        return self.generate_metrics().decode("utf-8")


# Global exporter instance
_exporter: PrometheusExporter | None = None


def get_prometheus_exporter() -> PrometheusExporter:
    """Get the global Prometheus exporter instance."""
    global _exporter
    if _exporter is None:
        _exporter = PrometheusExporter()
    return _exporter
