use std::collections::BTreeMap;
use std::sync::Arc;

use async_trait::async_trait;
use k8s_openapi::api::core::v1::Secret;
use kube::api::ObjectMeta;
use kube::{Api, Client, ResourceExt};
use log::{debug, info};
use serde::Serialize;
use serde_json::json;
use serde_yaml::{Mapping, Value};

use crate::modules::monitoring::kube_prometheus::alert_manager_config::AlertmanagerConfigSpec;
use crate::topology::k8s::K8sClient;
use crate::{
    interpret::{InterpretError, Outcome},
    modules::monitoring::{
        kube_prometheus::{
            alert_manager_config::{AlertmanagerConfig, CRDAlertManager, CRDAlertManagerReceiver},
            prometheus::{KubePrometheus, KubePrometheusReceiver},
            types::{AlertChannelConfig, AlertManagerChannelConfig},
        },
        prometheus::prometheus::{Prometheus, PrometheusReceiver},
    },
    topology::{Url, oberservability::monitoring::AlertReceiver},
};

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

#[async_trait]
impl CRDAlertManagerReceiver for DiscordWebhook {
    fn name(&self) -> String {
        self.name.clone()
    }

    async fn configure_receiver(&self, client: &Arc<K8sClient>, ns: String) -> AlertmanagerConfig {
        let secret_name = format!("{}-secret", self.name.clone());
        let webhook_key = format!("{}", self.url.clone());

        let mut string_data = BTreeMap::new();
        string_data.insert("webhook-url".to_string(), webhook_key.clone());

        let secret = Secret {
            metadata: kube::core::ObjectMeta {
                name: Some(secret_name.clone()),
                ..Default::default()
            },
            string_data: Some(string_data),
            type_: Some("Opaque".to_string()),
            ..Default::default()
        };

        let _ = client.apply(&secret, Some(&ns)).await;

        let spec = AlertmanagerConfigSpec {
            data: json!({
                "route": {
                    "receiver": self.name,
                },
                "receivers": [
                    {
                        "name": self.name,
                        "discordConfigs": [
                            {
                            "apiURL": {
                                "name": secret_name,
                                "key":  "webhook-url",
                            },
                            "title": "{{ template \"discord.default.title\" . }}",
                            "message": "{{ template \"discord.default.message\" . }}"
                            }
                        ]
                    }
                ]
            }),
        };

        let am = AlertmanagerConfig {
            metadata: ObjectMeta {
                name: Some(self.name.clone()),
                labels: Some(std::collections::BTreeMap::from([(
                    "alertmanagerConfig".to_string(),
                    "enabled".to_string(),
                )])),
                namespace: Some(ns),
                ..Default::default()
            },
            spec,
        };
        am
    }
    fn clone_box(&self) -> Box<dyn CRDAlertManagerReceiver> {
        Box::new(self.clone())
    }
}

#[async_trait]
impl AlertReceiver<CRDAlertManager> for DiscordWebhook {
    async fn install(&self, sender: &CRDAlertManager) -> Result<Outcome, InterpretError> {
        sender
            .client
            .apply(&sender.alertmanager_configs, Some(&sender.namespace))
            .await?;
        Ok(Outcome::success(format!(
            "installed crd-alertmanagerconfigs for {}",
            self.name
        )))
    }
    fn name(&self) -> String {
        "discord-webhook".to_string()
    }
    fn clone_box(&self) -> Box<dyn AlertReceiver<CRDAlertManager>> {
        Box::new(self.clone())
    }
}

#[async_trait]
impl AlertReceiver<Prometheus> for DiscordWebhook {
    async fn install(&self, sender: &Prometheus) -> Result<Outcome, InterpretError> {
        sender.install_receiver(self).await
    }
    fn name(&self) -> String {
        "discord-webhook".to_string()
    }
    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 AlertReceiver<KubePrometheus> for DiscordWebhook {
    async fn install(&self, sender: &KubePrometheus) -> Result<Outcome, InterpretError> {
        sender.install_receiver(self).await
    }
    fn clone_box(&self) -> Box<dyn AlertReceiver<KubePrometheus>> {
        Box::new(self.clone())
    }
    fn name(&self) -> String {
        "discord-webhook".to_string()
    }
}

#[async_trait]
impl KubePrometheusReceiver 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);
    }
}
