use async_trait::async_trait;
use serde::Serialize;
use serde_yaml::{Mapping, Value};

use crate::{
    interpret::{InterpretError, Outcome},
    modules::monitoring::kube_prometheus::{
        prometheus::{Prometheus, PrometheusReceiver},
        types::{AlertChannelConfig, AlertManagerChannelConfig},
    },
    topology::{Url, oberservability::monitoring::AlertReceiver},
};

#[derive(Debug, Clone, Serialize)]
pub struct DiscordWebhook {
    pub name: String,
    pub url: Url,
}

#[async_trait]
impl AlertReceiver<Prometheus> for DiscordWebhook {
    async fn install(&self, sender: &Prometheus) -> Result<Outcome, InterpretError> {
        sender.install_receiver(self).await
    }
    fn clone_box(&self) -> Box<dyn AlertReceiver<Prometheus>> {
        Box::new(self.clone())
    }
}

#[async_trait]
impl PrometheusReceiver for DiscordWebhook {
    fn name(&self) -> String {
        self.name.clone()
    }
    async fn configure_receiver(&self) -> AlertManagerChannelConfig {
        self.get_config().await
    }
}

#[async_trait]
impl AlertChannelConfig for DiscordWebhook {
    async fn get_config(&self) -> AlertManagerChannelConfig {
        let channel_global_config = None;
        let channel_receiver = self.alert_channel_receiver().await;
        let channel_route = self.alert_channel_route().await;

        AlertManagerChannelConfig {
            channel_global_config,
            channel_receiver,
            channel_route,
        }
    }
}

impl DiscordWebhook {
    async fn alert_channel_route(&self) -> serde_yaml::Value {
        let mut route = Mapping::new();
        route.insert(
            Value::String("receiver".to_string()),
            Value::String(self.name.clone()),
        );
        route.insert(
            Value::String("matchers".to_string()),
            Value::Sequence(vec![Value::String("alertname!=Watchdog".to_string())]),
        );
        route.insert(Value::String("continue".to_string()), Value::Bool(true));
        Value::Mapping(route)
    }

    async fn alert_channel_receiver(&self) -> serde_yaml::Value {
        let mut receiver = Mapping::new();
        receiver.insert(
            Value::String("name".to_string()),
            Value::String(self.name.clone()),
        );

        let mut discord_config = Mapping::new();
        discord_config.insert(
            Value::String("webhook_url".to_string()),
            Value::String(self.url.to_string()),
        );

        receiver.insert(
            Value::String("discord_configs".to_string()),
            Value::Sequence(vec![Value::Mapping(discord_config)]),
        );

        Value::Mapping(receiver)
    }
}

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

    #[tokio::test]
    async fn discord_serialize_should_match() {
        let discord_receiver = DiscordWebhook {
            name: "test-discord".to_string(),
            url: Url::Url(url::Url::parse("https://discord.i.dont.exist.com").unwrap()),
        };

        let discord_receiver_receiver =
            serde_yaml::to_string(&discord_receiver.alert_channel_receiver().await).unwrap();
        println!("receiver \n{:#}", discord_receiver_receiver);
        let discord_receiver_receiver_yaml = r#"name: test-discord
discord_configs:
- webhook_url: https://discord.i.dont.exist.com/
"#
        .to_string();

        let discord_receiver_route =
            serde_yaml::to_string(&discord_receiver.alert_channel_route().await).unwrap();
        println!("route \n{:#}", discord_receiver_route);
        let discord_receiver_route_yaml = r#"receiver: test-discord
matchers:
- alertname!=Watchdog
continue: true
"#
        .to_string();

        assert_eq!(discord_receiver_receiver, discord_receiver_receiver_yaml);
        assert_eq!(discord_receiver_route, discord_receiver_route_yaml);
    }
}
