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

/// Discord webhook embed payload.
#[derive(Debug, Clone, Serialize)]
pub struct DiscordEmbed {
    pub title: String,
    pub description: String,
    pub color: u32,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub fields: Vec<DiscordEmbedField>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub timestamp: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub footer: Option<DiscordEmbedFooter>,
}

/// Field within a Discord embed.
#[derive(Debug, Clone, Serialize)]
pub struct DiscordEmbedField {
    pub name: String,
    pub value: String,
    pub inline: bool,
}

/// Footer for a Discord embed.
#[derive(Debug, Clone, Serialize)]
pub struct DiscordEmbedFooter {
    pub text: String,
}

/// Discord webhook request body.
#[derive(Debug, Serialize)]
struct WebhookPayload {
    #[serde(skip_serializing_if = "Option::is_none")]
    username: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    avatar_url: Option<String>,
    embeds: Vec<DiscordEmbed>,
}

// Embed colors
const COLOR_GREEN: u32 = 0x22c55e;
const COLOR_ORANGE: u32 = 0xf59e0b;
const COLOR_RED: u32 = 0xef4444;
const COLOR_YELLOW: u32 = 0xeab308;

/// Discord webhook notification service.
///
/// Sends rich embed messages to Discord channels via webhook URLs.
/// Used for wipe announcements, completion notifications, failure
/// alerts, and crash recovery notifications.
pub struct DiscordNotifier {
    http: Client,
    webhook_url: String,
    server_name: String,
}

impl DiscordNotifier {
    pub fn new(webhook_url: String, server_name: String) -> Self {
        Self {
            http: Client::new(),
            webhook_url,
            server_name,
        }
    }

    /// Send a generic notification with a custom embed.
    pub async fn send_notification(&self, embed: DiscordEmbed) -> Result<()> {
        let payload = WebhookPayload {
            username: Some(format!("Corrosion — {}", self.server_name)),
            avatar_url: None,
            embeds: vec![embed],
        };

        let response = self
            .http
            .post(&self.webhook_url)
            .json(&payload)
            .send()
            .await
            .context("Failed to send Discord webhook")?;

        // Handle rate limiting
        if response.status().as_u16() == 429 {
            if let Some(retry_after) = response
                .headers()
                .get("Retry-After")
                .and_then(|v| v.to_str().ok())
                .and_then(|v| v.parse::<f64>().ok())
            {
                let wait = std::time::Duration::from_secs_f64(retry_after);
                tracing::warn!("Discord rate limited, retrying after {:.1}s", retry_after);
                tokio::time::sleep(wait).await;

                // Single retry
                self.http
                    .post(&self.webhook_url)
                    .json(&payload)
                    .send()
                    .await
                    .context("Failed to send Discord webhook on retry")?;
            }
        } else if !response.status().is_success() {
            let status = response.status();
            let body = response.text().await.unwrap_or_default();
            tracing::error!("Discord webhook failed: {} — {}", status, body);
            anyhow::bail!("Discord webhook returned {status}");
        }

        Ok(())
    }

    /// Send a wipe-starting announcement.
    pub async fn send_wipe_start(&self, wipe_type: &str, eta_minutes: u32) -> Result<()> {
        let embed = DiscordEmbed {
            title: format!("🔄 {} Wipe Starting", capitalize(wipe_type)),
            description: format!(
                "**{}** is beginning a {} wipe. Estimated completion in ~{} minutes.",
                self.server_name, wipe_type, eta_minutes
            ),
            color: COLOR_ORANGE,
            fields: vec![
                DiscordEmbedField {
                    name: "Wipe Type".to_string(),
                    value: capitalize(wipe_type),
                    inline: true,
                },
                DiscordEmbedField {
                    name: "ETA".to_string(),
                    value: format!("~{} min", eta_minutes),
                    inline: true,
                },
            ],
            timestamp: Some(chrono::Utc::now().to_rfc3339()),
            footer: Some(DiscordEmbedFooter {
                text: "Corrosion Server Management".to_string(),
            }),
        };
        self.send_notification(embed).await
    }

    /// Send a wipe-completed announcement.
    pub async fn send_wipe_complete(
        &self,
        wipe_type: &str,
        duration_seconds: u64,
        new_map: Option<&str>,
        new_seed: Option<i32>,
    ) -> Result<()> {
        let duration_str = if duration_seconds >= 60 {
            format!("{}m {}s", duration_seconds / 60, duration_seconds % 60)
        } else {
            format!("{}s", duration_seconds)
        };

        let mut fields = vec![
            DiscordEmbedField {
                name: "Wipe Type".to_string(),
                value: capitalize(wipe_type),
                inline: true,
            },
            DiscordEmbedField {
                name: "Duration".to_string(),
                value: duration_str,
                inline: true,
            },
        ];

        if let Some(map) = new_map {
            fields.push(DiscordEmbedField {
                name: "Map".to_string(),
                value: map.to_string(),
                inline: true,
            });
        }

        if let Some(seed) = new_seed {
            fields.push(DiscordEmbedField {
                name: "Seed".to_string(),
                value: seed.to_string(),
                inline: true,
            });
        }

        let embed = DiscordEmbed {
            title: format!("✅ {} Wipe Complete", capitalize(wipe_type)),
            description: format!(
                "**{}** has been wiped and is back online! Join now.",
                self.server_name
            ),
            color: COLOR_GREEN,
            fields,
            timestamp: Some(chrono::Utc::now().to_rfc3339()),
            footer: Some(DiscordEmbedFooter {
                text: "Corrosion Server Management".to_string(),
            }),
        };
        self.send_notification(embed).await
    }

    /// Send a wipe-failed alert.
    pub async fn send_wipe_failed(
        &self,
        wipe_type: &str,
        error: &str,
        rolled_back: bool,
    ) -> Result<()> {
        let rollback_status = if rolled_back {
            "✅ Backup restored — server running on pre-wipe state"
        } else {
            "⚠️ No rollback performed — manual intervention may be needed"
        };

        let embed = DiscordEmbed {
            title: format!("❌ {} Wipe Failed", capitalize(wipe_type)),
            description: format!(
                "**{}** wipe encountered an error and could not complete.",
                self.server_name
            ),
            color: COLOR_RED,
            fields: vec![
                DiscordEmbedField {
                    name: "Error".to_string(),
                    value: truncate(error, 1024),
                    inline: false,
                },
                DiscordEmbedField {
                    name: "Rollback".to_string(),
                    value: rollback_status.to_string(),
                    inline: false,
                },
            ],
            timestamp: Some(chrono::Utc::now().to_rfc3339()),
            footer: Some(DiscordEmbedFooter {
                text: "Corrosion Server Management".to_string(),
            }),
        };
        self.send_notification(embed).await
    }

    /// Send a crash detection/recovery alert.
    pub async fn send_crash_alert(
        &self,
        crash_count: u32,
        auto_recovered: bool,
    ) -> Result<()> {
        let (title, color, desc) = if auto_recovered {
            (
                "⚠️ Server Crash — Auto-Recovered".to_string(),
                COLOR_YELLOW,
                format!(
                    "**{}** crashed and was automatically restarted (attempt {}).",
                    self.server_name, crash_count
                ),
            )
        } else {
            (
                "🔴 Server Crash — Manual Intervention Required".to_string(),
                COLOR_RED,
                format!(
                    "**{}** crashed {} times and auto-recovery has been exhausted. Manual intervention required.",
                    self.server_name, crash_count
                ),
            )
        };

        let embed = DiscordEmbed {
            title,
            description: desc,
            color,
            fields: vec![DiscordEmbedField {
                name: "Crash Count".to_string(),
                value: crash_count.to_string(),
                inline: true,
            }],
            timestamp: Some(chrono::Utc::now().to_rfc3339()),
            footer: Some(DiscordEmbedFooter {
                text: "Corrosion Server Management".to_string(),
            }),
        };
        self.send_notification(embed).await
    }

    /// Send a store purchase notification.
    pub async fn send_store_purchase(
        &self,
        buyer_name: &str,
        item_name: &str,
        amount: f64,
        currency: &str,
    ) -> Result<()> {
        let embed = DiscordEmbed {
            title: "🛒 Store Purchase".to_string(),
            description: format!(
                "**{}** purchased **{}** on **{}**",
                buyer_name, item_name, self.server_name
            ),
            color: COLOR_GREEN,
            fields: vec![DiscordEmbedField {
                name: "Amount".to_string(),
                value: format!("{:.2} {}", amount, currency),
                inline: true,
            }],
            timestamp: Some(chrono::Utc::now().to_rfc3339()),
            footer: Some(DiscordEmbedFooter {
                text: "Corrosion Server Management".to_string(),
            }),
        };
        self.send_notification(embed).await
    }
}

fn capitalize(s: &str) -> String {
    let mut c = s.chars();
    match c.next() {
        None => String::new(),
        Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
    }
}

fn truncate(s: &str, max_len: usize) -> String {
    if s.len() <= max_len {
        s.to_string()
    } else {
        format!("{}...", &s[..max_len - 3])
    }
}
