//! Tier 2 metrics: RabbitMQ Management HTTP API.
//!
//! Fetches detailed queue statistics (publish/deliver/ack rates, memory usage,
//! unacknowledged message count) from the RabbitMQ Management plugin.
//!
//! Requires the `management` feature flag.

use serde::Deserialize;

/// Metrics obtained from the RabbitMQ Management HTTP API.
#[derive(Debug, Clone, Copy, Default)]
pub struct Tier2Metrics {
    /// Messages published per second.
    pub publish_rate: f64,
    /// Messages delivered per second.
    pub deliver_rate: f64,
    /// Messages acknowledged per second.
    pub ack_rate: f64,
    /// Queue memory usage in bytes.
    pub memory_bytes: u64,
    /// Number of unacknowledged messages.
    pub unacked_count: u32,
}

/// Configuration for connecting to the RabbitMQ Management API.
#[derive(Debug, Clone)]
pub struct ManagementApiConfig {
    /// Base URL of the management API (e.g., `http://localhost:15672`).
    pub base_url: String,
    /// HTTP basic auth username (default: `guest`).
    pub username: String,
    /// HTTP basic auth password (default: `guest`).
    pub password: String,
    /// RabbitMQ virtual host (default: `/`, URL-encoded as `%2F`).
    pub vhost: String,
}

impl Default for ManagementApiConfig {
    fn default() -> Self {
        Self {
            base_url: "http://localhost:15672".to_string(),
            username: "guest".to_string(),
            password: "guest".to_string(),
            vhost: "%2F".to_string(),
        }
    }
}

/// Collects Tier 2 metrics from the RabbitMQ Management HTTP API.
pub struct ManagementApiCollector {
    client: reqwest::Client,
    config: ManagementApiConfig,
}

impl ManagementApiCollector {
    /// Create a new Management API collector.
    pub fn new(config: ManagementApiConfig) -> Self {
        Self {
            client: reqwest::Client::new(),
            config,
        }
    }

    /// Create a collector with a pre-configured HTTP client.
    pub fn with_client(client: reqwest::Client, config: ManagementApiConfig) -> Self {
        Self { client, config }
    }

    /// Fetch queue metrics from the Management API.
    pub async fn collect(&self, queue_name: &str) -> Result<Tier2Metrics, ManagementApiError> {
        let url = format!(
            "{}/api/queues/{}/{}",
            self.config.base_url, self.config.vhost, queue_name
        );

        let response = self
            .client
            .get(&url)
            .basic_auth(&self.config.username, Some(&self.config.password))
            .send()
            .await
            .map_err(|e| ManagementApiError::Request(e.to_string()))?;

        if !response.status().is_success() {
            return Err(ManagementApiError::HttpStatus {
                status: response.status().as_u16(),
                queue_name: queue_name.to_string(),
            });
        }

        let body: ManagementQueueResponse = response
            .json()
            .await
            .map_err(|e| ManagementApiError::Deserialization(e.to_string()))?;

        Ok(Tier2Metrics {
            publish_rate: body
                .message_stats
                .as_ref()
                .and_then(|s| s.publish_details.as_ref())
                .map(|d| d.rate)
                .unwrap_or(0.0),
            deliver_rate: body
                .message_stats
                .as_ref()
                .and_then(|s| s.deliver_get_details.as_ref())
                .map(|d| d.rate)
                .unwrap_or(0.0),
            ack_rate: body
                .message_stats
                .as_ref()
                .and_then(|s| s.ack_details.as_ref())
                .map(|d| d.rate)
                .unwrap_or(0.0),
            memory_bytes: body.memory.unwrap_or(0),
            unacked_count: body.messages_unacknowledged.unwrap_or(0),
        })
    }
}

/// Errors from the Management API collector.
#[derive(Debug, thiserror::Error)]
pub enum ManagementApiError {
    #[error("HTTP request failed: {0}")]
    Request(String),
    #[error("HTTP {status} for queue '{queue_name}'")]
    HttpStatus { status: u16, queue_name: String },
    #[error("Failed to parse response: {0}")]
    Deserialization(String),
}

// ---- Management API response types ----

#[derive(Debug, Deserialize)]
struct ManagementQueueResponse {
    #[serde(default)]
    message_stats: Option<MessageStats>,
    #[serde(default)]
    memory: Option<u64>,
    #[serde(default)]
    messages_unacknowledged: Option<u32>,
}

#[derive(Debug, Deserialize)]
struct MessageStats {
    #[serde(default)]
    publish_details: Option<RateDetails>,
    #[serde(default)]
    deliver_get_details: Option<RateDetails>,
    #[serde(default)]
    ack_details: Option<RateDetails>,
}

#[derive(Debug, Deserialize)]
struct RateDetails {
    rate: f64,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn config_default_values() {
        let config = ManagementApiConfig::default();
        assert_eq!(config.base_url, "http://localhost:15672");
        assert_eq!(config.username, "guest");
        assert_eq!(config.password, "guest");
        assert_eq!(config.vhost, "%2F");
    }

    #[test]
    fn parse_full_response() {
        let json = r#"{
            "message_stats": {
                "publish_details": {"rate": 10.5},
                "deliver_get_details": {"rate": 8.2},
                "ack_details": {"rate": 7.9}
            },
            "memory": 1048576,
            "messages_unacknowledged": 42
        }"#;

        let resp: ManagementQueueResponse = serde_json::from_str(json).unwrap();
        assert_eq!(resp.memory, Some(1048576));
        assert_eq!(resp.messages_unacknowledged, Some(42));

        let stats = resp.message_stats.unwrap();
        let publish = stats.publish_details.unwrap();
        assert!((publish.rate - 10.5).abs() < f64::EPSILON);
        let deliver = stats.deliver_get_details.unwrap();
        assert!((deliver.rate - 8.2).abs() < f64::EPSILON);
        let ack = stats.ack_details.unwrap();
        assert!((ack.rate - 7.9).abs() < f64::EPSILON);
    }

    #[test]
    fn parse_empty_response() {
        let json = r#"{}"#;
        let resp: ManagementQueueResponse = serde_json::from_str(json).unwrap();
        assert!(resp.message_stats.is_none());
        assert!(resp.memory.is_none());
        assert!(resp.messages_unacknowledged.is_none());
    }

    #[test]
    fn parse_partial_stats() {
        let json = r#"{
            "message_stats": {
                "publish_details": {"rate": 5.0}
            },
            "memory": 512
        }"#;

        let resp: ManagementQueueResponse = serde_json::from_str(json).unwrap();
        let stats = resp.message_stats.unwrap();
        assert!((stats.publish_details.unwrap().rate - 5.0).abs() < f64::EPSILON);
        assert!(stats.deliver_get_details.is_none());
        assert!(stats.ack_details.is_none());
        assert_eq!(resp.memory, Some(512));
        assert!(resp.messages_unacknowledged.is_none());
    }

    #[test]
    fn parse_zero_rates() {
        let json = r#"{
            "message_stats": {
                "publish_details": {"rate": 0.0},
                "deliver_get_details": {"rate": 0.0},
                "ack_details": {"rate": 0.0}
            },
            "memory": 0,
            "messages_unacknowledged": 0
        }"#;

        let resp: ManagementQueueResponse = serde_json::from_str(json).unwrap();
        let stats = resp.message_stats.unwrap();
        assert_eq!(stats.publish_details.unwrap().rate, 0.0);
        assert_eq!(stats.deliver_get_details.unwrap().rate, 0.0);
        assert_eq!(stats.ack_details.unwrap().rate, 0.0);
        assert_eq!(resp.memory, Some(0));
        assert_eq!(resp.messages_unacknowledged, Some(0));
    }

    #[test]
    fn parse_extra_fields_ignored() {
        let json = r#"{
            "name": "my-queue",
            "vhost": "/",
            "durable": true,
            "auto_delete": false,
            "message_stats": {
                "publish": 1000,
                "publish_details": {"rate": 3.5},
                "deliver_get": 800,
                "deliver_get_details": {"rate": 2.1}
            },
            "memory": 2048,
            "messages_unacknowledged": 5,
            "messages": 100,
            "consumers": 3
        }"#;

        let resp: ManagementQueueResponse = serde_json::from_str(json).unwrap();
        assert_eq!(resp.memory, Some(2048));
        assert_eq!(resp.messages_unacknowledged, Some(5));
        let stats = resp.message_stats.unwrap();
        assert!((stats.publish_details.unwrap().rate - 3.5).abs() < f64::EPSILON);
        assert!((stats.deliver_get_details.unwrap().rate - 2.1).abs() < f64::EPSILON);
        assert!(stats.ack_details.is_none());
    }

    #[test]
    fn tier2_metrics_default() {
        let m = Tier2Metrics::default();
        assert_eq!(m.publish_rate, 0.0);
        assert_eq!(m.deliver_rate, 0.0);
        assert_eq!(m.ack_rate, 0.0);
        assert_eq!(m.memory_bytes, 0);
        assert_eq!(m.unacked_count, 0);
    }

    #[test]
    fn url_construction() {
        let config = ManagementApiConfig {
            base_url: "http://rabbitmq:15672".to_string(),
            username: "admin".to_string(),
            password: "secret".to_string(),
            vhost: "%2F".to_string(),
        };
        let collector = ManagementApiCollector::new(config);
        // Verify config is stored correctly
        assert_eq!(collector.config.base_url, "http://rabbitmq:15672");
        assert_eq!(collector.config.vhost, "%2F");
    }

    #[test]
    fn error_display() {
        let err = ManagementApiError::Request("connection refused".to_string());
        assert_eq!(err.to_string(), "HTTP request failed: connection refused");

        let err = ManagementApiError::HttpStatus {
            status: 404,
            queue_name: "my-queue".to_string(),
        };
        assert_eq!(err.to_string(), "HTTP 404 for queue 'my-queue'");

        let err = ManagementApiError::Deserialization("invalid json".to_string());
        assert_eq!(err.to_string(), "Failed to parse response: invalid json");
    }
}
