//! Metrics collection for RabbitMQ queues.
//!
//! Provides a three-tier metrics system:
//!
//! | Tier | Source | Data | Always Available? |
//! |------|--------|------|-------------------|
//! | 0 | Event counters from `TrackedDelivery` | throughput, success_rate, avg_processing_ms, error_rate | Yes |
//! | 1 | AMQP `queue_declare(passive=true)` | message_count, consumer_count | Yes (needs Channel) |
//! | 2 | RabbitMQ Management HTTP API | publish_rate, deliver_rate, ack_rate, memory_bytes, unacked_count | Optional (`management` feature) |
//!
//! # Example
//!
//! ```no_run
//! use queuerious::agent::MetricsCollector;
//! use queuerious_lapin::StaticConnectionFactory;
//! use queuerious_lapin::metrics::{RabbitMqMetricsCollector, SlidingWindowCounters};
//! use std::collections::HashMap;
//! use std::sync::Arc;
//!
//! # async fn example(conn: lapin::Connection) -> Result<(), Box<dyn std::error::Error>> {
//! // 1. Create shared counters (populated by TrackedDelivery)
//! let mut counters = HashMap::new();
//! counters.insert("my-queue".to_string(), SlidingWindowCounters::new());
//! let counters = Arc::new(counters);
//!
//! // 2. Build the collector (channel factory enables auto-reconnect)
//! let channel_factory = Arc::new(StaticConnectionFactory::new(conn));
//! let collector = RabbitMqMetricsCollector::builder(counters, channel_factory)
//!     .window_seconds(60.0)
//!     .build();
//!
//! // 3. Collect metrics (queue names are derived from the counters map)
//! let snapshots = collector.collect().await;
//! # Ok(())
//! # }
//! ```

pub mod sliding_window;
pub mod tier0;
pub mod tier1;

#[cfg(feature = "management")]
pub mod tier2;

use std::collections::HashMap;
use std::sync::Arc;

use queuerious::agent::QueueMetricSnapshot;

use crate::channel_factory::ChannelFactory;

pub use sliding_window::SlidingWindowCounters;
pub use tier0::Tier0Snapshot;
pub use tier1::{Tier1Collector, Tier1Metrics};

#[cfg(feature = "management")]
pub use tier2::{ManagementApiCollector, ManagementApiConfig, Tier2Metrics};

/// Default collection window in seconds (1 minute).
const DEFAULT_WINDOW_SECONDS: f64 = 60.0;

/// Collects RabbitMQ queue metrics from all three tiers.
///
/// Implements the [`MetricsCollector`](queuerious::agent::MetricsCollector) trait
/// when the `metrics` feature is enabled.
pub struct RabbitMqMetricsCollector {
    /// Shared atomic counters, keyed by queue name.
    /// Written by `TrackedDelivery`, read by this collector.
    counters: Arc<HashMap<String, SlidingWindowCounters>>,

    /// Tier 1 collector using AMQP passive queue_declare.
    tier1: Tier1Collector,

    /// Tier 2 collector using the Management HTTP API.
    #[cfg(feature = "management")]
    tier2: Option<ManagementApiCollector>,

    /// Duration of the sliding window in seconds, used for rate calculations.
    window_seconds: f64,
}

impl RabbitMqMetricsCollector {
    /// Create a builder for configuring the metrics collector.
    ///
    /// The `channel_factory` provides AMQP channels on demand for Tier 1
    /// metrics collection (passive `queue_declare`). Use
    /// [`StaticConnectionFactory`](crate::StaticConnectionFactory) for simple
    /// setups, or implement [`ChannelFactory`] for automatic reconnection.
    pub fn builder(
        counters: Arc<HashMap<String, SlidingWindowCounters>>,
        channel_factory: Arc<dyn ChannelFactory>,
    ) -> RabbitMqMetricsCollectorBuilder {
        RabbitMqMetricsCollectorBuilder {
            counters,
            channel_factory,
            #[cfg(feature = "management")]
            management: None,
            window_seconds: DEFAULT_WINDOW_SECONDS,
        }
    }

    /// Collect metrics for a single queue.
    async fn collect_queue(&self, queue_name: &str) -> QueueMetricSnapshot {
        // Tier 0: Snapshot atomic counters (always available if tracked).
        let tier0 = self
            .counters
            .get(queue_name)
            .map(|c| c.snapshot_and_reset());

        // Tier 1: AMQP passive queue_declare.
        let tier1 = match self.tier1.collect(queue_name).await {
            Ok(m) => Some(m),
            Err(e) => {
                tracing::debug!(
                    queue = queue_name,
                    error = %e,
                    "Tier 1 metrics collection failed"
                );
                None
            }
        };

        // Tier 2: Management HTTP API (if configured).
        #[cfg(feature = "management")]
        let tier2 = match &self.tier2 {
            Some(collector) => match collector.collect(queue_name).await {
                Ok(m) => Some(m),
                Err(e) => {
                    tracing::debug!(
                        queue = queue_name,
                        error = %e,
                        "Tier 2 metrics collection failed"
                    );
                    None
                }
            },
            None => None,
        };

        // Build Tier 0 metadata (throughput, success_rate, etc.) into the
        // `metadata` field since QueueMetricSnapshot doesn't have dedicated
        // fields for these computed metrics.
        let metadata = tier0.map(|snap| {
            let mut meta = serde_json::Map::new();
            meta.insert(
                "throughput".to_string(),
                serde_json::json!(snap.throughput(self.window_seconds)),
            );
            meta.insert(
                "success_rate".to_string(),
                serde_json::json!(snap.success_rate()),
            );
            meta.insert(
                "avg_processing_ms".to_string(),
                serde_json::json!(snap.avg_processing_ms()),
            );
            meta.insert(
                "error_rate".to_string(),
                serde_json::json!(snap.error_rate()),
            );
            meta.insert(
                "window_seconds".to_string(),
                serde_json::json!(self.window_seconds),
            );

            // Include Tier 2 extras that don't map to top-level fields.
            #[cfg(feature = "management")]
            if let Some(ref t2) = tier2 {
                meta.insert(
                    "deliver_rate".to_string(),
                    serde_json::json!(t2.deliver_rate),
                );
                meta.insert("ack_rate".to_string(), serde_json::json!(t2.ack_rate));
                meta.insert(
                    "unacked_count".to_string(),
                    serde_json::json!(t2.unacked_count),
                );
            }

            serde_json::Value::Object(meta)
        });

        // Assemble the unified metrics snapshot.
        QueueMetricSnapshot {
            queue_name: queue_name.to_string(),

            // Tier 1 (required fields — default to 0 if unavailable).
            message_count: tier1.map(|m| m.message_count as u64).unwrap_or(0),
            consumer_count: tier1.map(|m| m.consumer_count).unwrap_or(0),

            // Tier 2 (optional fields).
            #[cfg(feature = "management")]
            publish_rate: tier2.map(|m| m.publish_rate),
            #[cfg(not(feature = "management"))]
            publish_rate: None,

            #[cfg(feature = "management")]
            consume_rate: tier2.map(|m| m.deliver_rate),
            #[cfg(not(feature = "management"))]
            consume_rate: None,

            #[cfg(feature = "management")]
            memory_bytes: tier2.map(|m| m.memory_bytes),
            #[cfg(not(feature = "management"))]
            memory_bytes: None,

            // Tier 0 computed metrics packed into metadata.
            metadata,
        }
    }
}

#[async_trait::async_trait]
impl queuerious::agent::MetricsCollector for RabbitMqMetricsCollector {
    async fn collect(&self) -> Vec<QueueMetricSnapshot> {
        let queue_names: Vec<String> = self.counters.keys().cloned().collect();
        let mut results = Vec::with_capacity(queue_names.len());

        for name in &queue_names {
            results.push(self.collect_queue(name).await);
        }

        results
    }
}

/// Builder for [`RabbitMqMetricsCollector`].
pub struct RabbitMqMetricsCollectorBuilder {
    counters: Arc<HashMap<String, SlidingWindowCounters>>,
    channel_factory: Arc<dyn ChannelFactory>,
    #[cfg(feature = "management")]
    management: Option<ManagementApiCollector>,
    window_seconds: f64,
}

impl RabbitMqMetricsCollectorBuilder {
    /// Set the sliding window duration in seconds (default: 60).
    pub fn window_seconds(mut self, seconds: f64) -> Self {
        self.window_seconds = seconds;
        self
    }

    /// Enable Tier 2 metrics via the RabbitMQ Management API.
    #[cfg(feature = "management")]
    pub fn management_api(mut self, collector: ManagementApiCollector) -> Self {
        self.management = Some(collector);
        self
    }

    /// Build the metrics collector.
    pub fn build(self) -> RabbitMqMetricsCollector {
        RabbitMqMetricsCollector {
            counters: self.counters,
            tier1: Tier1Collector::new(self.channel_factory),
            #[cfg(feature = "management")]
            tier2: self.management,
            window_seconds: self.window_seconds,
        }
    }
}
