use std::sync::Arc;

use crate::modules::monitoring::kube_prometheus::crd::crd_default_rules::{
    build_rule_container_restarting, build_rule_pod_failed,
};
use crate::modules::monitoring::kube_prometheus::crd::crd_prometheus_rules::RuleGroup;
use crate::modules::monitoring::kube_prometheus::service_monitor::{
    ServiceMonitor, ServiceMonitorSpec,
};
use crate::modules::monitoring::kube_prometheus::types::{Selector, ServiceMonitorEndpoint};
use crate::{
    inventory::Inventory,
    modules::{
        application::{Application, ApplicationFeature, OCICompliant},
        monitoring::{
            alert_channel::webhook_receiver::WebhookReceiver,
            kube_prometheus::{
                alert_manager_config::{CRDAlertManager, CRDAlertManagerReceiver},
                helm_prometheus_application_alerting::HelmPrometheusApplicationAlertingScore,
                types::{NamespaceSelector, ServiceMonitor as KubePrometheusServiceMonitor},
            },
            ntfy::ntfy::NtfyScore,
        },
    },
    score::Score,
    topology::{
        HelmCommand, K8sclient, Topology, Url, oberservability::monitoring::AlertReceiver,
        tenant::TenantManager,
    },
};
use async_trait::async_trait;
use base64::{Engine as _, engine::general_purpose};
use kube::api::ObjectMeta;
use log::{debug, info};

#[derive(Debug, Clone)]
pub struct Monitoring {
    pub application: Arc<dyn OCICompliant>,
    pub alert_receiver: Vec<Box<dyn CRDAlertManagerReceiver>>,
    pub service_monitors: Vec<ServiceMonitor>,
    pub alert_rules: Vec<RuleGroup>,
}

#[async_trait]
impl<T: Topology + HelmCommand + 'static + TenantManager + K8sclient + std::fmt::Debug>
    ApplicationFeature<T> for Monitoring
{
    async fn ensure_installed(&self, topology: &T) -> Result<(), String> {
        info!("Ensuring monitoring is available for application");
        let namespace = self.application.name().clone();
        let mut alerting_score = HelmPrometheusApplicationAlertingScore {
            namespace: namespace.clone(),
            receivers: self.alert_receiver.clone(),
            service_monitors: self.service_monitors.clone(),
            prometheus_rules: self.alert_rules.clone(),
        };
        let ntfy = NtfyScore {
            // namespace: topology
            //     .get_tenant_config()
            //     .await
            //     .expect("couldn't get tenant config")
            //     .name,
            namespace: self.application.name(),
            host: "localhost".to_string(),
        };
        ntfy.create_interpret()
            .execute(&Inventory::empty(), topology)
            .await
            .expect("couldn't create interpret for ntfy");

        let ntfy_default_auth_username = "harmony";
        let ntfy_default_auth_password = "harmony";
        let ntfy_default_auth_header = format!(
            "Basic {}",
            general_purpose::STANDARD.encode(format!(
                "{ntfy_default_auth_username}:{ntfy_default_auth_password}"
            ))
        );

        debug!("ntfy_default_auth_header: {ntfy_default_auth_header}");

        let ntfy_default_auth_param = general_purpose::STANDARD
            .encode(ntfy_default_auth_header)
            .replace("=", "");

        debug!("ntfy_default_auth_param: {ntfy_default_auth_param}");

        let ntfy_receiver = WebhookReceiver {
            name: "ntfy-webhook".to_string(),
            url: Url::Url(
                url::Url::parse(
                    format!(
                        "http://ntfy.{}.svc.cluster.local/rust-web-app?auth={ntfy_default_auth_param}",
                        self.application.name()
                    )
                    .as_str(),
                )
                .unwrap(),
            ),
        };

        alerting_score.receivers.push(Box::new(ntfy_receiver));

        //TODO add service monitors to PrometheusApplicationMonitoring which can be
        //deployed for the namespace using prometheus crd-servicemonitors
        let service_monitor = ServiceMonitor {
            metadata: ObjectMeta {
                name: Some(self.application.name().clone()),
                labels: Some(std::collections::BTreeMap::from([(
                    "alertmanagerConfig".to_string(),
                    "enabled".to_string(),
                )])),
                namespace: Some(namespace),
                ..Default::default()
            },
            spec: ServiceMonitorSpec::default(),
        };

        alerting_score.service_monitors.push(service_monitor);

        let rules_group = RuleGroup {
            name: format!("{}-rules", self.application.name().clone()),
            rules: vec![build_rule_container_restarting(), build_rule_pod_failed()],
        };

        alerting_score.prometheus_rules.push(rules_group);
        alerting_score
            .create_interpret()
            .execute(&Inventory::empty(), topology)
            .await
            .unwrap();
        Ok(())
    }
    fn name(&self) -> String {
        "Monitoring".to_string()
    }
}
