//! Webhook Notification Module
//!
//! Sends notifications via webhooks when API key events occur

use anyhow::{Context, Result};
use chrono::Utc;
use reqwest::Client;
use serde::{Deserialize, Serialize};

/// Webhook event types
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WebhookEvent {
    KeyCreated,
    KeyRevoked,
    KeyExpired,
    KeyRotated,
    AnomalyDetected,
    RateLimited,
    IpBlocked,
}

impl std::fmt::Display for WebhookEvent {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            WebhookEvent::KeyCreated => write!(f, "key_created"),
            WebhookEvent::KeyRevoked => write!(f, "key_revoked"),
            WebhookEvent::KeyExpired => write!(f, "key_expired"),
            WebhookEvent::KeyRotated => write!(f, "key_rotated"),
            WebhookEvent::AnomalyDetected => write!(f, "anomaly_detected"),
            WebhookEvent::RateLimited => write!(f, "rate_limited"),
            WebhookEvent::IpBlocked => write!(f, "ip_blocked"),
        }
    }
}

/// Webhook payload
#[derive(Debug, Clone, Serialize)]
pub struct WebhookPayload {
    pub event: String,
    pub timestamp: String,
    pub data: serde_json::Value,
}

/// Webhook configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebhookConfig {
    pub url: String,
    pub secret: String,
    pub events: Vec<WebhookEvent>,
    pub enabled: bool,
    pub retry_count: u32,
    pub timeout_secs: u64,
}

impl Default for WebhookConfig {
    fn default() -> Self {
        Self {
            url: String::new(),
            secret: String::new(),
            events: vec![
                WebhookEvent::KeyCreated,
                WebhookEvent::KeyRevoked,
                WebhookEvent::AnomalyDetected,
            ],
            enabled: false,
            retry_count: 3,
            timeout_secs: 30,
        }
    }
}

/// Webhook notifier
pub struct WebhookNotifier {
    client: Client,
    config: WebhookConfig,
}

impl WebhookNotifier {
    pub fn new(config: WebhookConfig) -> Result<Self> {
        let client = Client::builder()
            .timeout(std::time::Duration::from_secs(config.timeout_secs))
            .build()
            .context("Failed to create HTTP client")?;

        Ok(Self { client, config })
    }

    pub fn from_env() -> Result<Option<Self>> {
        let url = match std::env::var("WEBHOOK_URL") {
            Ok(url) if !url.is_empty() => url,
            _ => return Ok(None),
        };

        let secret = std::env::var("WEBHOOK_SECRET").unwrap_or_default();

        let events = std::env::var("WEBHOOK_EVENTS")
            .unwrap_or_default()
            .split(',')
            .filter_map(|s| match s.trim() {
                "key_created" => Some(WebhookEvent::KeyCreated),
                "key_revoked" => Some(WebhookEvent::KeyRevoked),
                "key_expired" => Some(WebhookEvent::KeyExpired),
                "key_rotated" => Some(WebhookEvent::KeyRotated),
                "anomaly_detected" => Some(WebhookEvent::AnomalyDetected),
                "rate_limited" => Some(WebhookEvent::RateLimited),
                "ip_blocked" => Some(WebhookEvent::IpBlocked),
                _ => None,
            })
            .collect::<Vec<_>>();

        let events = if events.is_empty() {
            vec![
                WebhookEvent::KeyCreated,
                WebhookEvent::KeyRevoked,
                WebhookEvent::AnomalyDetected,
            ]
        } else {
            events
        };

        Ok(Some(Self::new(WebhookConfig {
            url,
            secret,
            events,
            enabled: true,
            retry_count: 3,
            timeout_secs: 30,
        })?))
    }

    /// Check if an event should be sent
    pub fn should_send(&self, event: &WebhookEvent) -> bool {
        self.config.enabled && self.config.events.contains(event)
    }

    /// Generate HMAC signature for payload
    fn sign(&self, payload: &str) -> String {
        use sha2::{Digest, Sha256};
        let mut hasher = Sha256::new();
        hasher.update(self.config.secret.as_bytes());
        hasher.update(payload.as_bytes());
        format!("{:x}", hasher.finalize())
    }

    /// Send a webhook notification
    pub async fn notify(&self, event: WebhookEvent, data: serde_json::Value) -> Result<bool> {
        if !self.should_send(&event) {
            return Ok(false);
        }

        let payload = WebhookPayload {
            event: event.to_string(),
            timestamp: Utc::now().to_rfc3339(),
            data,
        };

        let json = serde_json::to_string(&payload)?;
        let signature = self.sign(&json);

        let mut attempts = 0;
        let max_attempts = self.config.retry_count;

        while attempts < max_attempts {
            attempts += 1;

            let result = self
                .client
                .post(&self.config.url)
                .header("Content-Type", "application/json")
                .header("X-Webhook-Signature", &signature)
                .header("X-Webhook-Event", event.to_string())
                .body(json.clone())
                .send()
                .await;

            match result {
                Ok(response) if response.status().is_success() => {
                    tracing::info!("Webhook sent successfully: {:?}", event);
                    return Ok(true);
                }
                Ok(response) => {
                    tracing::warn!(
                        "Webhook failed with status {}: {:?}",
                        response.status(),
                        event
                    );
                }
                Err(e) => {
                    tracing::warn!("Webhook error (attempt {}): {}", attempts, e);
                }
            }

            if attempts < max_attempts {
                tokio::time::sleep(std::time::Duration::from_secs(1)).await;
            }
        }

        tracing::error!(
            "Webhook failed after {} attempts: {:?}",
            max_attempts,
            event
        );
        Ok(false)
    }

    /// Notify key created
    pub async fn notify_key_created(&self, key_id: &str, name: &str) -> Result<bool> {
        self.notify(
            WebhookEvent::KeyCreated,
            serde_json::json!({
                "key_id": key_id,
                "name": name,
            }),
        )
        .await
    }

    /// Notify key revoked
    pub async fn notify_key_revoked(&self, key_id: &str, reason: &str) -> Result<bool> {
        self.notify(
            WebhookEvent::KeyRevoked,
            serde_json::json!({
                "key_id": key_id,
                "reason": reason,
            }),
        )
        .await
    }

    /// Notify anomaly detected
    pub async fn notify_anomaly(
        &self,
        key_id: &str,
        anomaly_type: &str,
        severity: &str,
    ) -> Result<bool> {
        self.notify(
            WebhookEvent::AnomalyDetected,
            serde_json::json!({
                "key_id": key_id,
                "anomaly_type": anomaly_type,
                "severity": severity,
            }),
        )
        .await
    }

    /// Notify IP blocked
    pub async fn notify_ip_blocked(&self, ip: &str, reason: &str) -> Result<bool> {
        self.notify(
            WebhookEvent::IpBlocked,
            serde_json::json!({
                "ip": ip,
                "reason": reason,
            }),
        )
        .await
    }
}

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

    #[test]
    fn test_webhook_config_default() {
        let config = WebhookConfig::default();
        assert!(!config.enabled);
        assert_eq!(config.retry_count, 3);
        assert_eq!(config.timeout_secs, 30);
    }

    #[test]
    fn test_should_send() {
        let config = WebhookConfig {
            url: "https://example.com/webhook".to_string(),
            secret: "secret".to_string(),
            events: vec![WebhookEvent::KeyCreated, WebhookEvent::AnomalyDetected],
            enabled: true,
            retry_count: 3,
            timeout_secs: 30,
        };

        let notifier = WebhookNotifier::new(config).unwrap();

        assert!(notifier.should_send(&WebhookEvent::KeyCreated));
        assert!(notifier.should_send(&WebhookEvent::AnomalyDetected));
        assert!(!notifier.should_send(&WebhookEvent::KeyRevoked));
    }

    #[test]
    fn test_sign() {
        let config = WebhookConfig {
            url: "https://example.com/webhook".to_string(),
            secret: "mysecret".to_string(),
            events: vec![],
            enabled: true,
            retry_count: 3,
            timeout_secs: 30,
        };

        let notifier = WebhookNotifier::new(config).unwrap();
        let sig1 = notifier.sign("test payload");
        let sig2 = notifier.sign("test payload");
        let sig3 = notifier.sign("different payload");

        assert_eq!(sig1, sig2);
        assert_ne!(sig1, sig3);
    }
}
