//! High-level runtime that wires up metrics collection, command execution,
//! and the Queuerious agent into a single entry point.
//!
//! Available when either the `metrics` or `commands` feature is enabled.
//! The runtime adapts to the enabled features:
//!
//! - **`metrics` only**: Spawns an agent that collects and pushes queue metrics.
//!   `tracked_consumer()` auto-attaches counters for Tier 0 data collection.
//! - **`commands` only**: Spawns an agent that polls for and executes remote commands
//!   (pause, resume, purge, retry).
//! - **Both (`agent` feature)**: Full experience — metrics + commands.
//!
//! # Example
//!
//! ```no_run
//! use queuerious::QueuriousClient;
//! use queuerious_lapin::{ObservabilityRuntime, StaticConnectionFactory};
//! use std::sync::Arc;
//!
//! # async fn example(conn: lapin::Connection) -> Result<(), Box<dyn std::error::Error>> {
//! let client = Arc::new(
//!     QueuriousClient::builder()
//!         .api_key("qk_your_key")
//!         .build()?
//! );
//!
//! let channel_factory = Arc::new(StaticConnectionFactory::new(conn));
//! let runtime = ObservabilityRuntime::builder(client.clone(), channel_factory)
//!     .queue_names(vec!["email".into(), "payments".into()])
//!     .start()
//!     .await?;
//!
//! // Consumers get counters auto-attached (when metrics feature is enabled):
//! // let tracked = runtime.tracked_consumer(consumer, "email");
//!
//! // Graceful shutdown:
//! runtime.shutdown().await?;
//! # Ok(())
//! # }
//! ```

use std::sync::Arc;

#[cfg(feature = "metrics")]
use std::collections::HashMap;

use queuerious::{AgentConfig, QueuriousClient, QueuriousError};
use tokio::sync::oneshot;
use tokio::task::JoinHandle;

use crate::channel_factory::ChannelFactory;
use crate::config::LapinAdapterConfig;
use crate::consumer::TrackedConsumer;

#[cfg(feature = "metrics")]
use crate::metrics::{RabbitMqMetricsCollector, SlidingWindowCounters};

#[cfg(feature = "commands")]
use crate::commands::RabbitMqCommandExecutor;

#[cfg(all(feature = "metrics", feature = "management"))]
use crate::metrics::{ManagementApiCollector, ManagementApiConfig};

/// Bundles metrics collection, command execution, and the Queuerious agent
/// into a single managed runtime.
///
/// Created via [`ObservabilityRuntime::builder`].
pub struct ObservabilityRuntime {
    client: Arc<QueuriousClient>,
    #[cfg(feature = "metrics")]
    counters: Arc<HashMap<String, SlidingWindowCounters>>,
    agent_handle: Option<JoinHandle<()>>,
    agent_shutdown: Option<oneshot::Sender<()>>,
    default_adapter_config: LapinAdapterConfig,
}

impl ObservabilityRuntime {
    /// Create a builder for configuring the runtime.
    ///
    /// The `channel_factory` provides AMQP channels on demand for:
    /// - Tier 1 metrics collection (passive `queue_declare`) when `metrics` is enabled
    /// - Command execution (pause/resume/purge/retry) when `commands` is enabled
    ///
    /// Use [`StaticConnectionFactory`](crate::StaticConnectionFactory) for simple
    /// setups, or implement [`ChannelFactory`] for automatic reconnection after
    /// RabbitMQ restarts.
    pub fn builder(
        client: Arc<QueuriousClient>,
        channel_factory: Arc<dyn ChannelFactory>,
    ) -> RuntimeBuilder {
        RuntimeBuilder {
            client,
            channel_factory,
            queue_names: Vec::new(),
            agent_config: None,
            adapter_config: None,
            #[cfg(feature = "metrics")]
            window_seconds: None,
            #[cfg(feature = "commands")]
            prefetch_count: None,
            #[cfg(all(feature = "metrics", feature = "management"))]
            management_config: None,
        }
    }

    /// Create a [`TrackedConsumer`] with counters automatically attached.
    ///
    /// When the `metrics` feature is enabled, the consumer's processing
    /// metrics (throughput, success rate, latency) are automatically recorded
    /// to the shared counters and pushed to Queuerious by the agent.
    pub fn tracked_consumer(
        &self,
        consumer: lapin::Consumer,
        queue_name: impl Into<String>,
    ) -> TrackedConsumer {
        self.tracked_consumer_with_config(
            consumer,
            queue_name,
            self.default_adapter_config.clone(),
        )
    }

    /// Create a [`TrackedConsumer`] with custom configuration and counters attached.
    pub fn tracked_consumer_with_config(
        &self,
        consumer: lapin::Consumer,
        queue_name: impl Into<String>,
        config: LapinAdapterConfig,
    ) -> TrackedConsumer {
        let tracked = TrackedConsumer::with_config(
            consumer,
            queue_name,
            self.client.clone(),
            config,
        );

        #[cfg(feature = "metrics")]
        let tracked = tracked.with_counters(self.counters.clone());

        tracked
    }

    /// Access the shared counters map.
    ///
    /// Useful for manually attaching counters to consumers created outside
    /// the runtime, or for direct inspection.
    #[cfg(feature = "metrics")]
    pub fn counters(&self) -> &Arc<HashMap<String, SlidingWindowCounters>> {
        &self.counters
    }

    /// Access the underlying client.
    pub fn client(&self) -> &Arc<QueuriousClient> {
        &self.client
    }

    /// Gracefully shut down the agent.
    pub async fn shutdown(mut self) -> Result<(), QueuriousError> {
        if let Some(tx) = self.agent_shutdown.take() {
            let _ = tx.send(());
        }
        if let Some(handle) = self.agent_handle.take() {
            handle.await.map_err(|e| {
                QueuriousError::Config(format!("Agent task panicked: {}", e))
            })?;
        }
        Ok(())
    }
}

impl Drop for ObservabilityRuntime {
    fn drop(&mut self) {
        // Best-effort shutdown signal (non-blocking).
        if let Some(tx) = self.agent_shutdown.take() {
            let _ = tx.send(());
        }
    }
}

/// Builder for [`ObservabilityRuntime`].
pub struct RuntimeBuilder {
    client: Arc<QueuriousClient>,
    channel_factory: Arc<dyn ChannelFactory>,
    queue_names: Vec<String>,
    agent_config: Option<AgentConfig>,
    adapter_config: Option<LapinAdapterConfig>,
    #[cfg(feature = "metrics")]
    window_seconds: Option<f64>,
    #[cfg(feature = "commands")]
    prefetch_count: Option<u16>,
    #[cfg(all(feature = "metrics", feature = "management"))]
    management_config: Option<ManagementApiConfig>,
}

impl RuntimeBuilder {
    /// Set the queue names to monitor.
    ///
    /// When `metrics` is enabled, a [`SlidingWindowCounters`] entry is created
    /// for each queue name, and the metrics collector reports on all of them.
    pub fn queue_names(mut self, names: Vec<String>) -> Self {
        self.queue_names = names;
        self
    }

    /// Set the agent configuration (heartbeat interval, metrics interval, etc.).
    pub fn agent_config(mut self, config: AgentConfig) -> Self {
        self.agent_config = Some(config);
        self
    }

    /// Set the default [`LapinAdapterConfig`] for consumers created via
    /// [`ObservabilityRuntime::tracked_consumer`].
    pub fn adapter_config(mut self, config: LapinAdapterConfig) -> Self {
        self.adapter_config = Some(config);
        self
    }

    /// Set the sliding window duration in seconds for rate calculations (default: 60).
    #[cfg(feature = "metrics")]
    pub fn window_seconds(mut self, seconds: f64) -> Self {
        self.window_seconds = Some(seconds);
        self
    }

    /// Set the prefetch count for the command executor's pause/resume cycle.
    ///
    /// When a `pause` command is received, QoS is set to 0. On `resume`,
    /// QoS is restored to this value. Default: 10.
    #[cfg(feature = "commands")]
    pub fn prefetch_count(mut self, count: u16) -> Self {
        self.prefetch_count = Some(count);
        self
    }

    /// Enable Tier 2 metrics via the RabbitMQ Management HTTP API.
    ///
    /// Adds publish_rate, deliver_rate, ack_rate, memory_bytes, and unacked_count
    /// to each queue's metric snapshot.
    #[cfg(all(feature = "metrics", feature = "management"))]
    pub fn management_api(mut self, config: ManagementApiConfig) -> Self {
        self.management_config = Some(config);
        self
    }

    /// Build and start the runtime.
    ///
    /// This:
    /// 1. Creates shared counters for each queue name (when `metrics` is enabled)
    /// 2. Builds a metrics collector and/or command executor based on enabled features
    /// 3. Spawns the Queuerious agent as a background task
    pub async fn start(self) -> Result<ObservabilityRuntime, QueuriousError> {
        let channel_factory = self.channel_factory;

        // --- Metrics setup (only when metrics feature is enabled) ---
        #[cfg(feature = "metrics")]
        let counters = {
            let mut map = HashMap::new();
            for name in &self.queue_names {
                map.insert(name.clone(), SlidingWindowCounters::new());
            }
            Arc::new(map)
        };

        #[cfg(feature = "metrics")]
        let metrics_collector: Arc<dyn queuerious::MetricsCollector> = {
            let mut builder = RabbitMqMetricsCollector::builder(
                counters.clone(),
                channel_factory.clone(),
            );

            if let Some(window) = self.window_seconds {
                builder = builder.window_seconds(window);
            }

            #[cfg(all(feature = "metrics", feature = "management"))]
            if let Some(mgmt_config) = self.management_config {
                let mgmt_collector = ManagementApiCollector::new(mgmt_config);
                builder = builder.management_api(mgmt_collector);
            }

            Arc::new(builder.build())
        };

        // --- Commands setup (only when commands feature is enabled) ---
        #[cfg(feature = "commands")]
        let command_executor: Arc<dyn queuerious::CommandExecutor> = {
            let prefetch = self.prefetch_count.unwrap_or(10);
            Arc::new(RabbitMqCommandExecutor::new(channel_factory.clone(), prefetch))
        };

        // --- Agent setup ---
        let mut agent_builder = self.client.agent_builder();

        if let Some(config) = self.agent_config {
            agent_builder = agent_builder.config(config);
        }

        #[cfg(feature = "metrics")]
        {
            agent_builder = agent_builder.metrics_collector(metrics_collector);
        }

        #[cfg(feature = "commands")]
        {
            agent_builder = agent_builder.command_executor(command_executor);
        }

        let agent = agent_builder.build()?;
        let (handle, shutdown_tx) = agent.spawn();

        Ok(ObservabilityRuntime {
            client: self.client,
            #[cfg(feature = "metrics")]
            counters,
            agent_handle: Some(handle),
            agent_shutdown: Some(shutdown_tx),
            default_adapter_config: self.adapter_config.unwrap_or_default(),
        })
    }
}
