"""Metrics collector for job queue monitoring."""

from __future__ import annotations

import threading
import time
from dataclasses import dataclass, field
from typing import Any


@dataclass
class MetricsCollector:
    """Thread-safe metrics collector for job queue monitoring.

    Collects counters, gauges, and histograms for Prometheus-compatible
    metrics exposure.

    Attributes:
        jobs_processed: Total number of jobs processed (counter)
        jobs_failed: Total number of jobs that failed (counter)
        jobs_completed: Total number of jobs completed successfully (counter)
        queue_depth: Current number of pending jobs (gauge)
        jobs_by_status: Count of jobs by status (gauge)
        duration_count: Number of duration observations
        duration_sum: Sum of all duration observations
        duration_buckets: Histogram bucket counts for durations
        worker_count: Current number of workers (gauge)
        scaling_events: Total number of scaling events (counter)
    """

    # Counters
    jobs_processed: int = 0
    jobs_failed: int = 0
    jobs_completed: int = 0
    scaling_events: int = 0

    # Gauges
    queue_depth: int = 0
    worker_count: int = 0
    jobs_by_status: dict[str, int] = field(default_factory=dict)

    # Duration histogram
    duration_count: int = 0
    duration_sum: float = 0.0
    duration_buckets: dict[float, int] = field(default_factory=dict)

    def __post_init__(self) -> None:
        """Initialize thread lock and default buckets."""
        self._lock = threading.RLock()

        # Initialize default histogram buckets (in seconds)
        # Prometheus default buckets for durations
        default_buckets = [
            0.005,
            0.01,
            0.025,
            0.05,
            0.1,
            0.25,
            0.5,
            1.0,
            2.5,
            5.0,
            10.0,
            float("inf"),
        ]
        with self._lock:
            for bucket in default_buckets:
                if bucket not in self.duration_buckets:
                    self.duration_buckets[bucket] = 0

    def increment_processed(self, count: int = 1) -> None:
        """Increment the jobs processed counter.

        Args:
            count: Amount to increment by (default 1)
        """
        with self._lock:
            self.jobs_processed += count

    def increment_failed(self, count: int = 1) -> None:
        """Increment the jobs failed counter.

        Args:
            count: Amount to increment by (default 1)
        """
        with self._lock:
            self.jobs_failed += count

    def increment_completed(self, count: int = 1) -> None:
        """Increment the jobs completed counter.

        Args:
            count: Amount to increment by (default 1)
        """
        with self._lock:
            self.jobs_completed += count

    def set_queue_depth(self, depth: int) -> None:
        """Set the queue depth gauge.

        Args:
            depth: Current number of pending jobs
        """
        with self._lock:
            self.queue_depth = depth

    def record_duration(self, duration_seconds: float) -> None:
        """Record a job processing duration observation.

        Args:
            duration_seconds: Duration in seconds
        """
        with self._lock:
            self.duration_count += 1
            self.duration_sum += duration_seconds

            # Update all buckets that this duration falls into
            for bucket in sorted(self.duration_buckets.keys()):
                if duration_seconds <= bucket:
                    self.duration_buckets[bucket] += 1

    def update_job_status_count(self, status: str, count: int) -> None:
        """Update the count for a specific job status.

        Args:
            status: Job status (pending, running, completed, failed, workers)
            count: Number of jobs/workers with this status
        """
        with self._lock:
            self.jobs_by_status[status] = count

    def set_worker_count(self, count: int) -> None:
        """Set the worker count gauge.

        Args:
            count: Current number of workers
        """
        with self._lock:
            self.worker_count = count

    def increment_scaling_events(self, count: int = 1) -> None:
        """Increment the scaling events counter.

        Args:
            count: Amount to increment by (default 1)
        """
        with self._lock:
            self.scaling_events += count

    def collect_from_queue(self, queue: Any) -> None:
        """Collect metrics from a JobQueue instance.

        Updates queue depth and status counts from the queue.

        Args:
            queue: JobQueue instance to collect metrics from
        """
        try:
            stats = queue.get_stats()
            with self._lock:
                self.queue_depth = stats.queue_depth
                self.jobs_by_status["pending"] = stats.pending
                self.jobs_by_status["running"] = stats.running
                self.jobs_by_status["completed"] = stats.completed
                self.jobs_by_status["failed"] = stats.failed
        except Exception:
            # Don't let metrics collection break the queue
            pass

    def get_metrics_snapshot(self) -> dict[str, Any]:
        """Get a snapshot of all metrics as a dictionary.

        Returns:
            Dictionary containing all current metric values
        """
        with self._lock:
            return {
                "jobs_processed": self.jobs_processed,
                "jobs_failed": self.jobs_failed,
                "jobs_completed": self.jobs_completed,
                "scaling_events": self.scaling_events,
                "queue_depth": self.queue_depth,
                "worker_count": self.worker_count,
                "jobs_by_status": dict(self.jobs_by_status),
                "duration_count": self.duration_count,
                "duration_sum": self.duration_sum,
                "duration_buckets": dict(self.duration_buckets),
                "timestamp": time.time(),
            }


class HistogramTimer:
    """Context manager for timing operations and recording to histogram.

    Example:
        >>> collector = MetricsCollector()
        >>> with HistogramTimer(collector, "job_duration"):
        ...     process_job()
    """

    def __init__(self, collector: MetricsCollector, name: str = "duration") -> None:
        """Initialize timer.

        Args:
            collector: Metrics collector to record to
            name: Timer name (for identification)
        """
        self.collector = collector
        self.name = name
        self.start_time: float | None = None
        self.duration: float | None = None

    def start(self) -> None:
        """Start the timer manually."""
        self.start_time = time.perf_counter()

    def observe(self) -> float:
        """Stop timer and record observation.

        Returns:
            Duration in seconds
        """
        if self.start_time is None:
            self.start_time = time.perf_counter()
            return 0.0

        self.duration = time.perf_counter() - self.start_time
        self.collector.record_duration(self.duration)
        return self.duration

    def __enter__(self) -> HistogramTimer:
        """Start timing on context entry."""
        self.start()
        return self

    def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
        """Record duration on context exit."""
        self.observe()


# Global collector instance for application-wide metrics
_global_collector: MetricsCollector | None = None
_global_lock = threading.Lock()


def get_collector() -> MetricsCollector:
    """Get the global metrics collector instance.

    Creates a new collector if one doesn't exist.

    Returns:
        Global MetricsCollector instance
    """
    global _global_collector
    with _global_lock:
        if _global_collector is None:
            _global_collector = MetricsCollector()
        return _global_collector


def reset_collector() -> None:
    """Reset the global collector to a new instance."""
    global _global_collector
    with _global_lock:
        _global_collector = MetricsCollector()
