use super::config::KubePrometheusConfig;
use log::debug;
use non_blank_string_rs::NonBlankString;
use serde_yaml::{Mapping, Value};
use std::{
    collections::BTreeMap,
    str::FromStr,
    sync::{Arc, Mutex},
};

use crate::modules::{
    helm::chart::HelmChartScore,
    monitoring::kube_prometheus::types::{
        AlertGroup, AlertManager, AlertManagerAdditionalPromRules, AlertManagerConfig,
        AlertManagerRoute, AlertManagerValues,
    },
};

pub fn kube_prometheus_helm_chart_score(
    config: Arc<Mutex<KubePrometheusConfig>>,
) -> HelmChartScore {
    let config = config.lock().unwrap();

    let default_rules = config.default_rules.to_string();
    let windows_monitoring = config.windows_monitoring.to_string();
    let grafana = config.grafana.to_string();
    let kubernetes_service_monitors = config.kubernetes_service_monitors.to_string();
    let kubernetes_api_server = config.kubernetes_api_server.to_string();
    let kubelet = config.kubelet.to_string();
    let kube_controller_manager = config.kube_controller_manager.to_string();
    let core_dns = config.core_dns.to_string();
    let kube_etcd = config.kube_etcd.to_string();
    let kube_scheduler = config.kube_scheduler.to_string();
    let kube_proxy = config.kube_proxy.to_string();
    let kube_state_metrics = config.kube_state_metrics.to_string();
    let node_exporter = config.node_exporter.to_string();
    let prometheus_operator = config.prometheus_operator.to_string();
    let prometheus = config.prometheus.to_string();
    let mut values = format!(
        r#"
defaultRules:
  create: {default_rules}
  rules:
    alertmanager: true
    etcd: true
    configReloaders: true
    general: true
    k8sContainerCpuUsageSecondsTotal: true
    k8sContainerMemoryCache: true
    k8sContainerMemoryRss: true
    k8sContainerMemorySwap: true
    k8sContainerResource: true
    k8sContainerMemoryWorkingSetBytes: true
    k8sPodOwner: true
    kubeApiserverAvailability: true
    kubeApiserverBurnrate: true
    kubeApiserverHistogram: true
    kubeApiserverSlos: true
    kubeControllerManager: true
    kubelet: true
    kubeProxy: true
    kubePrometheusGeneral: true
    kubePrometheusNodeRecording: true
    kubernetesApps: true
    kubernetesResources: true
    kubernetesStorage: true
    kubernetesSystem: true
    kubeSchedulerAlerting: true
    kubeSchedulerRecording: true
    kubeStateMetrics: true
    network: true
    node: true
    nodeExporterAlerting: true
    nodeExporterRecording: true
    prometheus: true
    prometheusOperator: true
    windows: true
windowsMonitoring:
  enabled: {windows_monitoring}
grafana:
  enabled: {grafana}
kubernetesServiceMonitors:
  enabled: {kubernetes_service_monitors}
kubeApiServer:
  enabled: {kubernetes_api_server}
kubelet:
  enabled: {kubelet}
kubeControllerManager:
  enabled: {kube_controller_manager}
coreDns:
  enabled: {core_dns}
kubeEtcd:
  enabled: {kube_etcd}
kubeScheduler:
  enabled: {kube_scheduler}
kubeProxy:
  enabled: {kube_proxy}
kubeStateMetrics:
  enabled: {kube_state_metrics}
nodeExporter:
  enabled: {node_exporter}
prometheusOperator:
  enabled: {prometheus_operator}
prometheus:
  enabled: {prometheus}
"#,
    );

    // add required null receiver for prometheus alert manager
    let mut null_receiver = Mapping::new();
    null_receiver.insert(
        Value::String("receiver".to_string()),
        Value::String("null".to_string()),
    );
    null_receiver.insert(
        Value::String("matchers".to_string()),
        Value::Sequence(vec![Value::String("alertname!=Watchdog".to_string())]),
    );
    null_receiver.insert(Value::String("continue".to_string()), Value::Bool(true));

    //add alert channels
    let mut alert_manager_channel_config = AlertManagerConfig {
        global: Mapping::new(),
        route: AlertManagerRoute {
            routes: vec![Value::Mapping(null_receiver)],
        },
        receivers: vec![serde_yaml::from_str("name: 'null'").unwrap()],
    };
    for receiver in config.alert_receiver_configs.iter() {
        if let Some(global) = receiver.channel_global_config.clone() {
            alert_manager_channel_config
                .global
                .insert(global.0, global.1);
        }
        alert_manager_channel_config
            .route
            .routes
            .push(receiver.channel_route.clone());
        alert_manager_channel_config
            .receivers
            .push(receiver.channel_receiver.clone());
    }

    let alert_manager_values = AlertManagerValues {
        alertmanager: AlertManager {
            enabled: config.alert_manager,
            config: alert_manager_channel_config,
        },
    };

    let alert_manager_yaml =
        serde_yaml::to_string(&alert_manager_values).expect("Failed to serialize YAML");
    debug!("serialized alert manager: \n {:#}", alert_manager_yaml);
    values.push_str(&alert_manager_yaml);

    //format alert manager additional rules for helm chart
    let mut merged_rules: BTreeMap<String, AlertGroup> = BTreeMap::new();

    for additional_rule in config.alert_rules.clone() {
        for (key, group) in additional_rule.rules {
            merged_rules.insert(key, group);
        }
    }

    let merged_rules = AlertManagerAdditionalPromRules {
        rules: merged_rules,
    };

    let mut alert_manager_additional_rules = serde_yaml::Mapping::new();
    let rules_value = serde_yaml::to_value(merged_rules).unwrap();

    alert_manager_additional_rules.insert(
        serde_yaml::Value::String("additionalPrometheusRulesMap".to_string()),
        rules_value,
    );

    let alert_manager_additional_rules_yaml =
        serde_yaml::to_string(&alert_manager_additional_rules).expect("Failed to serialize YAML");
    debug!(
        "alert_rules_yaml:\n{:#}",
        alert_manager_additional_rules_yaml
    );

    values.push_str(&alert_manager_additional_rules_yaml);
    debug!("full values.yaml: \n {:#}", values);

    HelmChartScore {
        namespace: Some(NonBlankString::from_str(&config.namespace).unwrap()),
        release_name: NonBlankString::from_str("kube-prometheus").unwrap(),
        chart_name: NonBlankString::from_str(
            "oci://ghcr.io/prometheus-community/charts/kube-prometheus-stack",
        )
        .unwrap(),
        chart_version: None,
        values_overrides: None,
        values_yaml: Some(values.to_string()),
        create_namespace: true,
        install_only: true,
        repository: None,
    }
}
