use std::sync::Arc;

use async_trait::async_trait;
use kube::{Api, api::ObjectMeta};
use log::{debug, info};
use serde::Serialize;

use crate::{
    data::{Id, Version},
    interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
    inventory::Inventory,
    modules::monitoring::kube_prometheus::crd::{
        crd_alertmanagers::{Alertmanager, AlertmanagerSpec},
        crd_prometheuses::{Prometheus, PrometheusSpec},
        role::{build_prom_role, build_prom_rolebinding, build_prom_service_account},
    },
    score::Score,
    topology::{K8sclient, Topology, k8s::K8sClient, oberservability::monitoring::AlertReceiver},
};

use super::{
    alert_manager_config::{
        AlertmanagerConfig, AlertmanagerConfigSpec, CRDAlertManager, CRDAlertManagerReceiver,
    },
    crd::crd_prometheus_rules::{PrometheusRule, PrometheusRuleSpec, RuleGroup},
    prometheus::KubePrometheus,
};

#[derive(Clone, Debug, Serialize)]
pub struct HelmPrometheusApplicationAlertingScore {
    pub namespace: String,
    pub receivers: Vec<Box<dyn CRDAlertManagerReceiver>>,
    pub service_monitors: Vec<super::service_monitor::ServiceMonitor>,
    pub prometheus_rules: Vec<RuleGroup>,
}

impl<T: Topology + K8sclient> Score<T> for HelmPrometheusApplicationAlertingScore {
    fn create_interpret(&self) -> Box<dyn crate::interpret::Interpret<T>> {
        Box::new(HelmPrometheusApplicationAlertingInterpret {
            namespace: self.namespace.clone(),
            receivers: self.receivers.clone(),
            service_monitors: self.service_monitors.clone(),
            prometheus_rules: self.prometheus_rules.clone(),
        })
    }

    fn name(&self) -> String {
        "HelmPrometheusApplicationAlertingScore".into()
    }
}

#[derive(Clone, Debug)]
pub struct HelmPrometheusApplicationAlertingInterpret {
    pub namespace: String,
    pub receivers: Vec<Box<dyn CRDAlertManagerReceiver>>,
    pub service_monitors: Vec<super::service_monitor::ServiceMonitor>,
    pub prometheus_rules: Vec<RuleGroup>,
}

#[async_trait]
impl<T: Topology + K8sclient> Interpret<T> for HelmPrometheusApplicationAlertingInterpret {
    async fn execute(
        &self,
        inventory: &Inventory,
        topology: &T,
    ) -> Result<Outcome, InterpretError> {
        let client = topology.k8s_client().await.unwrap();
        self.install_prometheus(&client).await?;
        self.install_alert_manager(&client).await?;
        for receiver in self.receivers.iter() {
            let alertmanager_config: AlertmanagerConfig = receiver
                .configure_receiver(&client, self.namespace.clone())
                .await;
            let sender = CRDAlertManager {
                alertmanager_configs: alertmanager_config,
                namespace: self.namespace.clone(),
                client: client.clone(),
            };
            receiver.install(&sender).await.map_err(|err| {
                InterpretError::new(format!("failed to install receiver: {}", err))
            })?;
        }
        self.install_rules(self.prometheus_rules.clone(), client.clone())
            .await
            .map_err(|err| InterpretError::new(format!("failed to install rules: {}", err)))?;

        debug!("\n\n\n monitors: {:#?}", self.service_monitors.clone());
        for monitor in self.service_monitors.iter() {
            self.install_monitor(monitor.clone(), client.clone())
                .await?;
        }
        Ok(Outcome::success(format!("deployed alert channels")))
    }

    fn get_name(&self) -> InterpretName {
        todo!()
    }

    fn get_version(&self) -> Version {
        todo!()
    }

    fn get_status(&self) -> InterpretStatus {
        todo!()
    }

    fn get_children(&self) -> Vec<Id> {
        todo!()
    }
}

impl HelmPrometheusApplicationAlertingInterpret {
    async fn install_prometheus(&self, client: &Arc<K8sClient>) -> Result<Outcome, InterpretError> {
        debug!(
            "installing crd-prometheuses in namespace {}",
            self.namespace.clone()
        );
        debug!("building role/rolebinding/serviceaccount for crd-prometheus");
        let rolename = format!("{}-prom", self.namespace.clone());
        let sa_name = format!("{}-prom-sa", self.namespace.clone());
        let role = build_prom_role(rolename.clone(), self.namespace.clone());
        let rolebinding =
            build_prom_rolebinding(rolename.clone(), self.namespace.clone(), sa_name.clone());
        let sa = build_prom_service_account(sa_name.clone(), self.namespace.clone());
        let mut prom_spec = PrometheusSpec::default();
        prom_spec.service_account_name = sa_name.clone();
        let prom = Prometheus {
            metadata: ObjectMeta {
                name: Some(self.namespace.clone()),
                labels: Some(std::collections::BTreeMap::from([(
                    "alertmanagerConfig".to_string(),
                    "enabled".to_string(),
                )])),
                namespace: Some(self.namespace.clone()),
                ..Default::default()
            },
            spec: prom_spec,
        };
        client
            .apply(&role, Some(&self.namespace.clone()))
            .await
            .map_err(|e| InterpretError::new(e.to_string()))?;
        info!(
            "installed prometheus role: {:#?} in ns {:#?}",
            role.metadata.name.unwrap(),
            role.metadata.namespace.unwrap()
        );
        client
            .apply(&rolebinding, Some(&self.namespace.clone()))
            .await
            .map_err(|e| InterpretError::new(e.to_string()))?;
        info!(
            "installed prometheus rolebinding: {:#?} in ns {:#?}",
            rolebinding.metadata.name.unwrap(),
            rolebinding.metadata.namespace.unwrap()
        );
        client
            .apply(&sa, Some(&self.namespace.clone()))
            .await
            .map_err(|e| InterpretError::new(e.to_string()))?;
        info!(
            "installed prometheus service account: {:#?} in ns {:#?}",
            sa.metadata.name.unwrap(),
            sa.metadata.namespace.unwrap()
        );
        client
            .apply(&prom, Some(&self.namespace.clone()))
            .await
            .map_err(|e| InterpretError::new(e.to_string()))?;
        info!(
            "installed prometheus: {:#?} in ns {:#?}",
            &prom.metadata.name.clone().unwrap(),
            &prom.metadata.namespace.clone().unwrap()
        );

        Ok(Outcome::success(format!(
            "successfully deployed crd-prometheus {:#?}",
            prom
        )))
    }

    async fn install_alert_manager(
        &self,
        client: &Arc<K8sClient>,
    ) -> Result<Outcome, InterpretError> {
        let am = Alertmanager {
            metadata: ObjectMeta {
                name: Some(self.namespace.clone()),
                labels: Some(std::collections::BTreeMap::from([(
                    "alertmanagerConfig".to_string(),
                    "enabled".to_string(),
                )])),
                namespace: Some(self.namespace.clone()),
                ..Default::default()
            },
            spec: AlertmanagerSpec::default(),
        };
        client
            .apply(&am, Some(&self.namespace.clone()))
            .await
            .map_err(|e| InterpretError::new(e.to_string()))?;
        Ok(Outcome::success(format!(
            "successfully deployed service monitor {:#?}",
            am.metadata.name
        )))
    }

    async fn install_monitor(
        &self,
        monitor: super::service_monitor::ServiceMonitor,
        client: Arc<K8sClient>,
    ) -> Result<Outcome, InterpretError> {
        debug!("service monitor: \n{:#?}", monitor.clone());
        let namespace = self.namespace.clone();
        client
            .apply(&monitor, Some(&namespace))
            .await
            .map_err(|e| InterpretError::new(e.to_string()))?;
        Ok(Outcome::success(format!(
            "successfully deployed service monitor {:#?}",
            monitor.metadata.name
        )))
    }

    async fn install_rules(
        &self,
        rules: Vec<RuleGroup>,
        client: Arc<K8sClient>,
    ) -> Result<Outcome, InterpretError> {
        let prom_rule_spec = PrometheusRuleSpec { groups: rules };

        let prom_rules = PrometheusRule {
            metadata: ObjectMeta {
                name: Some(self.namespace.clone()),
                labels: Some(std::collections::BTreeMap::from([(
                    "alertmanagerConfig".to_string(),
                    "enabled".to_string(),
                )])),
                namespace: Some(self.namespace.clone()),
                ..Default::default()
            },
            spec: prom_rule_spec,
        };
        client
            .apply(&prom_rules, Some(&self.namespace))
            .await
            .map_err(|e| InterpretError::new(e.to_string()))?;
        Ok(Outcome::success(format!(
            "successfully deployed service monitor {:#?}",
            prom_rules.metadata.name
        )))
    }
}
