use std::collections::HashMap;

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MessageLevel {
    Info,
    Success,
    Warning,
    Error,
}

impl MessageLevel {
    /// Slack attachment hex color string.
    pub fn slack_color(self) -> &'static str {
        match self {
            Self::Info => "#0099ff",
            Self::Success => "#36a64f",
            Self::Warning => "#ffcc00",
            Self::Error => "#cc0000",
        }
    }

    /// Discord embed color as 0xRRGGBB integer.
    pub fn discord_color(self) -> u32 {
        match self {
            Self::Info => 0x0099ff,
            Self::Success => 0x36a64f,
            Self::Warning => 0xffcc00,
            Self::Error => 0xcc0000,
        }
    }

    /// Unicode emoji prefix for plain-text formats.
    pub fn emoji(self) -> &'static str {
        match self {
            Self::Info => "ℹ️",
            Self::Success => "✅",
            Self::Warning => "⚠️",
            Self::Error => "🔴",
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
    pub title: String,
    pub body: String,
    pub level: MessageLevel,
    #[serde(default)]
    pub metadata: HashMap<String, String>,
}

impl Message {
    pub fn new(title: impl Into<String>, body: impl Into<String>, level: MessageLevel) -> Self {
        Self {
            title: title.into(),
            body: body.into(),
            level,
            metadata: HashMap::new(),
        }
    }

    pub fn info(title: impl Into<String>, body: impl Into<String>) -> Self {
        Self::new(title, body, MessageLevel::Info)
    }

    pub fn success(title: impl Into<String>, body: impl Into<String>) -> Self {
        Self::new(title, body, MessageLevel::Success)
    }

    pub fn warning(title: impl Into<String>, body: impl Into<String>) -> Self {
        Self::new(title, body, MessageLevel::Warning)
    }

    pub fn error(title: impl Into<String>, body: impl Into<String>) -> Self {
        Self::new(title, body, MessageLevel::Error)
    }

    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.metadata.insert(key.into(), value.into());
        self
    }
}

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

    #[test]
    fn level_colors_are_distinct() {
        let levels = [
            MessageLevel::Info,
            MessageLevel::Success,
            MessageLevel::Warning,
            MessageLevel::Error,
        ];
        let slack_colors: Vec<_> = levels.iter().map(|l| l.slack_color()).collect();
        let discord_colors: Vec<_> = levels.iter().map(|l| l.discord_color()).collect();
        // All four Slack colors are unique.
        let mut deduped = slack_colors.clone();
        deduped.dedup();
        assert_eq!(deduped.len(), 4);
        // All four Discord colors are unique.
        let mut deduped = discord_colors.clone();
        deduped.dedup();
        assert_eq!(deduped.len(), 4);
    }

    #[test]
    fn constructors_set_correct_level() {
        assert_eq!(Message::info("t", "b").level, MessageLevel::Info);
        assert_eq!(Message::success("t", "b").level, MessageLevel::Success);
        assert_eq!(Message::warning("t", "b").level, MessageLevel::Warning);
        assert_eq!(Message::error("t", "b").level, MessageLevel::Error);
    }

    #[test]
    fn with_metadata_is_additive() {
        let msg = Message::info("t", "b")
            .with_metadata("env", "prod")
            .with_metadata("region", "eu-west-1");
        assert_eq!(msg.metadata.len(), 2);
        assert_eq!(msg.metadata["env"], "prod");
    }
}
