use std::any::Any;

use async_trait::async_trait;
use kube::api::ObjectMeta;
use log::debug;
use serde::Serialize;
use serde_json::json;
use serde_yaml::{Mapping, Value};

use crate::{
    interpret::{InterpretError, Outcome},
    modules::monitoring::{
        kube_prometheus::{
            crd::crd_alertmanager_config::{
                AlertmanagerConfig, AlertmanagerConfigSpec, CRDPrometheus,
            },
            prometheus::{KubePrometheus, KubePrometheusReceiver},
            types::{AlertChannelConfig, AlertManagerChannelConfig},
        },
        prometheus::prometheus::{Prometheus, PrometheusReceiver},
    },
    topology::{Url, oberservability::monitoring::AlertReceiver},
};

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

#[async_trait]
impl AlertReceiver<CRDPrometheus> for WebhookReceiver {
    async fn install(&self, sender: &CRDPrometheus) -> Result<Outcome, InterpretError> {
        let spec = AlertmanagerConfigSpec {
            data: json!({
                "route": {
                    "receiver": self.name,
                },
                "receivers": [
                    {
                        "name": self.name,
                        "webhookConfigs": [
                            {
                            "url": self.url,
                            }
                        ]
                    }
                ]
            }),
        };

        let alertmanager_configs = AlertmanagerConfig {
            metadata: ObjectMeta {
                name: Some(self.name.clone()),
                labels: Some(std::collections::BTreeMap::from([(
                    "alertmanagerConfig".to_string(),
                    "enabled".to_string(),
                )])),
                namespace: Some(sender.namespace.clone()),
                ..Default::default()
            },
            spec,
        };
        debug!(
            "alert manager configs: \n{:#?}",
            alertmanager_configs.clone()
        );

        sender
            .client
            .apply(&alertmanager_configs, Some(&sender.namespace))
            .await?;
        Ok(Outcome::success(format!(
            "installed crd-alertmanagerconfigs for {}",
            self.name
        )))
    }

    fn name(&self) -> String {
        "webhook-receiver".to_string()
    }

    fn clone_box(&self) -> Box<dyn AlertReceiver<CRDPrometheus>> {
        Box::new(self.clone())
    }

    fn as_any(&self) -> &dyn Any {
        self
    }
}

#[async_trait]
impl AlertReceiver<Prometheus> for WebhookReceiver {
    async fn install(&self, sender: &Prometheus) -> Result<Outcome, InterpretError> {
        sender.install_receiver(self).await
    }
    fn name(&self) -> String {
        "webhook-receiver".to_string()
    }
    fn clone_box(&self) -> Box<dyn AlertReceiver<Prometheus>> {
        Box::new(self.clone())
    }
    fn as_any(&self) -> &dyn Any {
        self
    }
}

#[async_trait]
impl PrometheusReceiver for WebhookReceiver {
    fn name(&self) -> String {
        self.name.clone()
    }
    async fn configure_receiver(&self) -> AlertManagerChannelConfig {
        self.get_config().await
    }
}
#[async_trait]
impl AlertReceiver<KubePrometheus> for WebhookReceiver {
    async fn install(&self, sender: &KubePrometheus) -> Result<Outcome, InterpretError> {
        sender.install_receiver(self).await
    }
    fn name(&self) -> String {
        "webhook-receiver".to_string()
    }
    fn clone_box(&self) -> Box<dyn AlertReceiver<KubePrometheus>> {
        Box::new(self.clone())
    }
    fn as_any(&self) -> &dyn Any {
        self
    }
}

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

#[async_trait]
impl AlertChannelConfig for WebhookReceiver {
    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 WebhookReceiver {
    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 webhook_config = Mapping::new();
        webhook_config.insert(
            Value::String("url".to_string()),
            Value::String(self.url.to_string()),
        );

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

        Value::Mapping(receiver)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[tokio::test]
    async fn webhook_serialize_should_match() {
        let webhook_receiver = WebhookReceiver {
            name: "test-webhook".to_string(),
            url: Url::Url(url::Url::parse("https://webhook.i.dont.exist.com").unwrap()),
        };

        let webhook_receiver_receiver =
            serde_yaml::to_string(&webhook_receiver.alert_channel_receiver().await).unwrap();
        println!("receiver \n{:#}", webhook_receiver_receiver);
        let webhook_receiver_receiver_yaml = r#"name: test-webhook
webhook_configs:
- url: https://webhook.i.dont.exist.com/
"#
        .to_string();

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

        assert_eq!(webhook_receiver_receiver, webhook_receiver_receiver_yaml);
        assert_eq!(webhook_receiver_route, webhook_receiver_route_yaml);
    }
}
