"""
Prometheus metrics for monitoring atomingest pipelines.

This module provides metrics collection for:
- Ingestion throughput (records processed/failed)
- Ingestion duration
- Batch statistics
- Error rates

Metrics are exposed in Prometheus format and can be scraped by a Prometheus server.
"""

import logging
import time
from contextlib import contextmanager

try:
    from prometheus_client import (
        Counter,
        Gauge,
        Histogram,
        Summary,
        Info,
        CollectorRegistry,
        generate_latest,
        REGISTRY,
    )

    PROMETHEUS_AVAILABLE = True
except ImportError:
    PROMETHEUS_AVAILABLE = False

    # Provide no-op implementations if prometheus_client is not installed
    class _NoOpMetric:
        def __init__(self, *args, **kwargs):
            """No-op metric that accepts and ignores all arguments."""
            pass

        def inc(self, *args, **kwargs):
            pass

        def dec(self, *args, **kwargs):
            pass

        def set(self, *args, **kwargs):
            pass

        def observe(self, *args, **kwargs):
            pass

        def info(self, *args, **kwargs):
            pass

        def labels(self, *args, **kwargs):
            return self

    Counter = _NoOpMetric
    Gauge = _NoOpMetric
    Histogram = _NoOpMetric
    Summary = _NoOpMetric
    Info = _NoOpMetric
    CollectorRegistry = object
    REGISTRY = None

    def generate_latest(registry=None):
        return b""


logger = logging.getLogger(__name__)


# Ingestion metrics
ingestion_records_total = Counter(
    "atomingest_records_total",
    "Total number of records processed",
    ["table", "status"],  # status: success, failed
    registry=REGISTRY if PROMETHEUS_AVAILABLE else None,
)

ingestion_duration_seconds = Histogram(
    "atomingest_duration_seconds",
    "Time spent ingesting data",
    ["table"],
    buckets=(0.1, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0, 60.0, 120.0, 300.0),
    registry=REGISTRY if PROMETHEUS_AVAILABLE else None,
)

ingestion_batch_size = Histogram(
    "atomingest_batch_size",
    "Size of ingestion batches",
    ["table"],
    buckets=(10, 50, 100, 250, 500, 1000, 2500, 5000),
    registry=REGISTRY if PROMETHEUS_AVAILABLE else None,
)

ingestion_active = Gauge(
    "atomingest_active_ingestions",
    "Number of currently active ingestions",
    ["table"],
    registry=REGISTRY if PROMETHEUS_AVAILABLE else None,
)

normalization_duration_seconds = Histogram(
    "atomingest_normalization_duration_seconds",
    "Time spent normalizing data",
    ["table"],
    buckets=(0.01, 0.05, 0.1, 0.5, 1.0, 5.0, 10.0),
    registry=REGISTRY if PROMETHEUS_AVAILABLE else None,
)

hook_execution_duration_seconds = Histogram(
    "atomingest_hook_execution_duration_seconds",
    "Time spent executing hooks",
    ["hook_type", "hook_name"],
    buckets=(0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0),
    registry=REGISTRY if PROMETHEUS_AVAILABLE else None,
)

# Dead letter queue metrics
dlq_records_total = Counter(
    "atomingest_dlq_records_total",
    "Total number of records sent to dead letter queue",
    ["table", "error_type"],
    registry=REGISTRY if PROMETHEUS_AVAILABLE else None,
)

# Pipeline info
pipeline_info = Info(
    "atomingest_pipeline",
    "Information about the atomingest pipeline",
    registry=REGISTRY if PROMETHEUS_AVAILABLE else None,
)


class MetricsCollector:
    """
    Context manager for collecting metrics during ingestion.

    Automatically tracks:
    - Active ingestions (gauge)
    - Ingestion duration (histogram)
    - Records processed/failed (counter)
    """

    def __init__(self, table_name: str):
        """
        Initialize metrics collector for a table.

        Args:
            table_name: The name of the table being ingested.
        """
        self.table_name = table_name
        self.start_time = None
        self.records_processed = 0
        self.records_failed = 0

    def __enter__(self):
        """Start metrics collection."""
        if PROMETHEUS_AVAILABLE:
            ingestion_active.labels(table=self.table_name).inc()
        self.start_time = time.time()
        logger.info(f"Started metrics collection for table '{self.table_name}'")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Stop metrics collection and record final metrics."""
        duration = time.time() - self.start_time

        if PROMETHEUS_AVAILABLE:
            # Record duration
            ingestion_duration_seconds.labels(table=self.table_name).observe(duration)

            # Record final counts
            if self.records_processed > 0:
                ingestion_records_total.labels(
                    table=self.table_name, status="success"
                ).inc(self.records_processed)

            if self.records_failed > 0:
                ingestion_records_total.labels(
                    table=self.table_name, status="failed"
                ).inc(self.records_failed)

            # Decrement active gauge
            ingestion_active.labels(table=self.table_name).dec()

        logger.info(
            f"Completed metrics collection for table '{self.table_name}': "
            f"duration={duration:.2f}s, processed={self.records_processed}, "
            f"failed={self.records_failed}"
        )

    def record_batch(self, batch_size: int):
        """
        Record a batch being processed.

        Args:
            batch_size: Number of records in the batch.
        """
        if PROMETHEUS_AVAILABLE:
            ingestion_batch_size.labels(table=self.table_name).observe(batch_size)

    def record_success(self, count: int = 1):
        """
        Record successful record(s) processing.

        Args:
            count: Number of records successfully processed.
        """
        self.records_processed += count

    def record_failure(self, count: int = 1):
        """
        Record failed record(s) processing.

        Args:
            count: Number of records that failed.
        """
        self.records_failed += count


@contextmanager
def track_normalization(table_name: str):
    """
    Context manager for tracking normalization duration.

    Usage:
        with track_normalization("users"):
            # normalization code
            pass

    Args:
        table_name: The name of the table being normalized.
    """
    start_time = time.time()
    try:
        yield
    finally:
        duration = time.time() - start_time
        if PROMETHEUS_AVAILABLE:
            normalization_duration_seconds.labels(table=table_name).observe(duration)


@contextmanager
def track_hook_execution(hook_type: str, hook_name: str):
    """
    Context manager for tracking hook execution duration.

    Usage:
        with track_hook_execution("pre_save", "MyHook"):
            # hook code
            pass

    Args:
        hook_type: The type of hook (pre_save, post_save, etc.).
        hook_name: The name of the hook.
    """
    start_time = time.time()
    try:
        yield
    finally:
        duration = time.time() - start_time
        if PROMETHEUS_AVAILABLE:
            hook_execution_duration_seconds.labels(
                hook_type=hook_type, hook_name=hook_name
            ).observe(duration)


def record_dlq_entry(table_name: str, error_type: str):
    """
    Record a record being sent to the dead letter queue.

    Args:
        table_name: The name of the table.
        error_type: The type of error (e.g., validation_error, database_error).
    """
    if PROMETHEUS_AVAILABLE:
        dlq_records_total.labels(table=table_name, error_type=error_type).inc()


def set_pipeline_info(version: str, environment: str = "production"):
    """
    Set pipeline information.

    Args:
        version: The version of atomingest.
        environment: The environment (production, staging, development).
    """
    if PROMETHEUS_AVAILABLE:
        pipeline_info.info(
            {
                "version": version,
                "environment": environment,
            }
        )


def get_metrics() -> bytes:
    """
    Get current metrics in Prometheus format.

    Returns:
        Metrics as bytes in Prometheus exposition format.
    """
    if PROMETHEUS_AVAILABLE:
        return generate_latest(REGISTRY)  # type: ignore[no-any-return]
    return b"# Prometheus client not installed\n"


def is_prometheus_available() -> bool:
    """
    Check if Prometheus client library is available.

    Returns:
        True if prometheus_client is installed, False otherwise.
    """
    return PROMETHEUS_AVAILABLE
