use std::collections::{BTreeMap, HashMap};

use async_trait::async_trait;
use serde::Serialize;

use crate::{
    interpret::{InterpretError, Outcome},
    modules::monitoring::kube_prometheus::{
        prometheus::{Prometheus, PrometheusRule},
        types::{AlertGroup, AlertManagerAdditionalPromRules},
    },
    topology::oberservability::monitoring::AlertRule,
};

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

#[async_trait]
impl PrometheusRule for AlertManagerRuleGroup {
    fn name(&self) -> String {
        self.name.clone()
    }
    async fn configure_rule(&self) -> AlertManagerAdditionalPromRules {
        let mut additional_prom_rules = BTreeMap::new();

        additional_prom_rules.insert(
            self.name.clone(),
            AlertGroup {
                groups: vec![self.clone()],
            },
        );
        AlertManagerAdditionalPromRules {
            rules: additional_prom_rules,
        }
    }
}

impl AlertManagerRuleGroup {
    pub fn new(name: &str, rules: Vec<PrometheusAlertRule>) -> AlertManagerRuleGroup {
        AlertManagerRuleGroup {
            name: name.to_string().to_lowercase(),
            rules,
        }
    }
}

#[derive(Debug, Clone, Serialize)]
///logical group of alert rules
///evaluates to:
///name:
///  groups:
///  - name: name
///    rules: PrometheusAlertRule
pub struct AlertManagerRuleGroup {
    pub name: String,
    pub rules: Vec<PrometheusAlertRule>,
}

#[derive(Debug, Clone, Serialize)]
pub struct PrometheusAlertRule {
    pub alert: String,
    pub expr: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub r#for: Option<String>,
    pub labels: HashMap<String, String>,
    pub annotations: HashMap<String, String>,
}

impl PrometheusAlertRule {
    pub fn new(alert_name: &str, expr: &str) -> Self {
        Self {
            alert: alert_name.into(),
            expr: expr.into(),
            r#for: Some("1m".into()),
            labels: HashMap::new(),
            annotations: HashMap::new(),
        }
    }
    pub fn for_duration(mut self, duration: &str) -> Self {
        self.r#for = Some(duration.into());
        self
    }
    pub fn label(mut self, key: &str, value: &str) -> Self {
        self.labels.insert(key.into(), value.into());
        self
    }

    pub fn annotation(mut self, key: &str, value: &str) -> Self {
        self.annotations.insert(key.into(), value.into());
        self
    }
}
