use std::fs;
use std::{collections::BTreeMap, sync::Arc};
use tempfile::tempdir;

use async_trait::async_trait;
use kube::api::ObjectMeta;
use log::{debug, info};
use serde::Serialize;
use std::process::Command;

use crate::modules::monitoring::kube_prometheus::crd::crd_alertmanager_config::CRDPrometheus;
use crate::modules::monitoring::kube_prometheus::crd::crd_default_rules::build_default_application_rules;
use crate::modules::monitoring::kube_prometheus::crd::crd_grafana::{
    Grafana, GrafanaDashboard, GrafanaDashboardSpec, GrafanaDatasource, GrafanaDatasourceConfig,
    GrafanaDatasourceSpec, GrafanaSpec,
};
use crate::modules::monitoring::kube_prometheus::crd::crd_prometheus_rules::{
    PrometheusRule, PrometheusRuleSpec, RuleGroup,
};
use crate::modules::monitoring::kube_prometheus::crd::grafana_default_dashboard::build_default_dashboard;
use crate::modules::monitoring::kube_prometheus::crd::service_monitor::{
    ServiceMonitor, ServiceMonitorSpec,
};
use crate::topology::oberservability::monitoring::AlertReceiver;
use crate::topology::{K8sclient, Topology, k8s::K8sClient};
use crate::{
    data::{Id, Version},
    interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
    inventory::Inventory,
    modules::monitoring::kube_prometheus::crd::{
        crd_alertmanagers::{Alertmanager, AlertmanagerSpec},
        crd_prometheuses::{
            AlertmanagerEndpoints, LabelSelector, Prometheus, PrometheusSpec,
            PrometheusSpecAlerting,
        },
        role::{build_prom_role, build_prom_rolebinding, build_prom_service_account},
    },
    score::Score,
};

use super::prometheus::PrometheusApplicationMonitoring;

#[derive(Clone, Debug, Serialize)]
pub struct K8sPrometheusCRDAlertingScore {
    pub sender: CRDPrometheus,
    pub receivers: Vec<Box<dyn AlertReceiver<CRDPrometheus>>>,
    pub service_monitors: Vec<ServiceMonitor>,
    pub prometheus_rules: Vec<RuleGroup>,
}

impl<T: Topology + K8sclient + PrometheusApplicationMonitoring<CRDPrometheus>> Score<T>
    for K8sPrometheusCRDAlertingScore
{
    fn create_interpret(&self) -> Box<dyn crate::interpret::Interpret<T>> {
        Box::new(K8sPrometheusCRDAlertingInterpret {
            sender: self.sender.clone(),
            receivers: self.receivers.clone(),
            service_monitors: self.service_monitors.clone(),
            prometheus_rules: self.prometheus_rules.clone(),
        })
    }

    fn name(&self) -> String {
        "prometheus alerting [CRDAlertingScore]".into()
    }
}

#[derive(Clone, Debug)]
pub struct K8sPrometheusCRDAlertingInterpret {
    pub sender: CRDPrometheus,
    pub receivers: Vec<Box<dyn AlertReceiver<CRDPrometheus>>>,
    pub service_monitors: Vec<ServiceMonitor>,
    pub prometheus_rules: Vec<RuleGroup>,
}

#[async_trait]
impl<T: Topology + K8sclient + PrometheusApplicationMonitoring<CRDPrometheus>> Interpret<T>
    for K8sPrometheusCRDAlertingInterpret
{
    async fn execute(
        &self,
        _inventory: &Inventory,
        topology: &T,
    ) -> Result<Outcome, InterpretError> {
        let client = topology.k8s_client().await.unwrap();
        self.ensure_grafana_operator().await?;
        self.install_prometheus(&client).await?;
        self.install_alert_manager(&client).await?;
        self.install_client_kube_metrics().await?;
        self.install_grafana(&client).await?;
        self.install_receivers(&self.sender, &self.receivers)
            .await?;
        self.install_rules(&self.prometheus_rules, &client).await?;
        self.install_monitors(self.service_monitors.clone(), &client)
            .await?;
        Ok(Outcome::success(
            "K8s monitoring components installed".to_string(),
        ))
    }

    fn get_name(&self) -> InterpretName {
        InterpretName::K8sPrometheusCrdAlerting
    }

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

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

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

impl K8sPrometheusCRDAlertingInterpret {
    async fn crd_exists(&self, crd: &str) -> bool {
        let status = Command::new("sh")
            .args(["-c", &format!("kubectl get crd -A | grep -i {crd}")])
            .status()
            .map_err(|e| InterpretError::new(format!("could not connect to cluster: {}", e)))
            .unwrap();

        status.success()
    }

    async fn install_chart(
        &self,
        chart_path: String,
        chart_name: String,
    ) -> Result<(), InterpretError> {
        let temp_dir =
            tempdir().map_err(|e| InterpretError::new(format!("Tempdir error: {}", e)))?;
        let temp_path = temp_dir.path().to_path_buf();
        debug!("Using temp directory: {}", temp_path.display());
        let chart = format!("{}/{}", chart_path, chart_name);
        let pull_output = Command::new("helm")
            .args(["pull", &chart, "--destination", temp_path.to_str().unwrap()])
            .output()
            .map_err(|e| InterpretError::new(format!("Helm pull error: {}", e)))?;

        if !pull_output.status.success() {
            return Err(InterpretError::new(format!(
                "Helm pull failed: {}",
                String::from_utf8_lossy(&pull_output.stderr)
            )));
        }

        let tgz_path = fs::read_dir(&temp_path)
            .unwrap()
            .filter_map(|entry| {
                let entry = entry.ok()?;
                let path = entry.path();
                if path.extension()? == "tgz" {
                    Some(path)
                } else {
                    None
                }
            })
            .next()
            .ok_or_else(|| InterpretError::new("Could not find pulled Helm chart".into()))?;

        debug!("Installing chart from: {}", tgz_path.display());

        let install_output = Command::new("helm")
            .args([
                "install",
                &chart_name,
                tgz_path.to_str().unwrap(),
                "--namespace",
                &self.sender.namespace.clone(),
                "--create-namespace",
                "--wait",
                "--atomic",
            ])
            .output()
            .map_err(|e| InterpretError::new(format!("Helm install error: {}", e)))?;

        if !install_output.status.success() {
            return Err(InterpretError::new(format!(
                "Helm install failed: {}",
                String::from_utf8_lossy(&install_output.stderr)
            )));
        }

        debug!(
            "Installed chart {}/{} in namespace: {}",
            &chart_path,
            &chart_name,
            self.sender.namespace.clone()
        );
        Ok(())
    }

    async fn ensure_grafana_operator(&self) -> Result<Outcome, InterpretError> {
        if self.crd_exists("grafanas.grafana.integreatly.org").await {
            debug!("grafana CRDs already exist — skipping install.");
            return Ok(Outcome::success("Grafana CRDs already exist".to_string()));
        }

        let _ = Command::new("helm")
            .args([
                "repo",
                "add",
                "grafana-operator",
                "https://grafana.github.io/helm-charts",
            ])
            .output()
            .unwrap();

        let _ = Command::new("helm")
            .args(["repo", "update"])
            .output()
            .unwrap();

        let output = Command::new("helm")
            .args([
                "install",
                "grafana-operator",
                "grafana-operator/grafana-operator",
                "--namespace",
                &self.sender.namespace.clone(),
                "--create-namespace",
                "--set",
                "namespaceScope=true",
            ])
            .output()
            .unwrap();

        if !output.status.success() {
            return Err(InterpretError::new(format!(
                "helm install failed:\nstdout: {}\nstderr: {}",
                String::from_utf8_lossy(&output.stdout),
                String::from_utf8_lossy(&output.stderr)
            )));
        }

        Ok(Outcome::success(format!(
            "installed grafana operator in ns {}",
            self.sender.namespace.clone()
        )))
    }

    async fn install_prometheus(&self, client: &Arc<K8sClient>) -> Result<Outcome, InterpretError> {
        debug!(
            "installing crd-prometheuses in namespace {}",
            self.sender.namespace.clone()
        );
        debug!("building role/rolebinding/serviceaccount for crd-prometheus");
        let rolename = format!("{}-prom", self.sender.namespace.clone());
        let sa_name = format!("{}-prom-sa", self.sender.namespace.clone());
        let role = build_prom_role(rolename.clone(), self.sender.namespace.clone());
        let rolebinding = build_prom_rolebinding(
            rolename.clone(),
            self.sender.namespace.clone(),
            sa_name.clone(),
        );
        let sa = build_prom_service_account(sa_name.clone(), self.sender.namespace.clone());
        let prom_spec = PrometheusSpec {
            alerting: Some(PrometheusSpecAlerting {
                alertmanagers: Some(vec![AlertmanagerEndpoints {
                    name: Some("alertmanager-operated".into()),
                    namespace: Some(self.sender.namespace.clone()),
                    port: Some("web".into()),
                    scheme: Some("http".into()),
                }]),
            }),
            service_account_name: sa_name.clone(),
            service_monitor_namespace_selector: Some(LabelSelector {
                match_labels: BTreeMap::from([(
                    "kubernetes.io/metadata.name".to_string(),
                    self.sender.namespace.clone(),
                )]),
                match_expressions: vec![],
            }),
            service_monitor_selector: Some(LabelSelector {
                match_labels: BTreeMap::from([("client".to_string(), "prometheus".to_string())]),
                ..Default::default()
            }),

            service_discovery_role: Some("Endpoints".into()),

            pod_monitor_selector: Some(LabelSelector {
                match_labels: BTreeMap::from([("client".to_string(), "prometheus".to_string())]),
                ..Default::default()
            }),

            rule_selector: Some(LabelSelector {
                match_labels: BTreeMap::from([("role".to_string(), "prometheus-rule".to_string())]),
                ..Default::default()
            }),

            rule_namespace_selector: Some(LabelSelector {
                match_labels: BTreeMap::from([(
                    "kubernetes.io/metadata.name".to_string(),
                    self.sender.namespace.clone(),
                )]),
                match_expressions: vec![],
            }),
        };
        let prom = Prometheus {
            metadata: ObjectMeta {
                name: Some(self.sender.namespace.clone()),
                labels: Some(std::collections::BTreeMap::from([
                    ("alertmanagerConfig".to_string(), "enabled".to_string()),
                    ("client".to_string(), "prometheus".to_string()),
                ])),
                namespace: Some(self.sender.namespace.clone()),
                ..Default::default()
            },
            spec: prom_spec,
        };
        client
            .apply(&role, Some(&self.sender.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.sender.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.sender.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.sender.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.sender.namespace.clone()),
                labels: Some(std::collections::BTreeMap::from([(
                    "alertmanagerConfig".to_string(),
                    "enabled".to_string(),
                )])),
                namespace: Some(self.sender.namespace.clone()),
                ..Default::default()
            },
            spec: AlertmanagerSpec::default(),
        };
        client
            .apply(&am, Some(&self.sender.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_monitors(
        &self,
        mut monitors: Vec<ServiceMonitor>,
        client: &Arc<K8sClient>,
    ) -> Result<Outcome, InterpretError> {
        let default_service_monitor = ServiceMonitor {
            metadata: ObjectMeta {
                name: Some(self.sender.namespace.clone()),
                labels: Some(std::collections::BTreeMap::from([
                    ("alertmanagerConfig".to_string(), "enabled".to_string()),
                    ("client".to_string(), "prometheus".to_string()),
                    (
                        "app.kubernetes.io/name".to_string(),
                        "kube-state-metrics".to_string(),
                    ),
                ])),
                namespace: Some(self.sender.namespace.clone()),
                ..Default::default()
            },
            spec: ServiceMonitorSpec::default(),
        };
        monitors.push(default_service_monitor);
        for monitor in monitors.iter() {
            client
                .apply(monitor, Some(&self.sender.namespace.clone()))
                .await
                .map_err(|e| InterpretError::new(e.to_string()))?;
        }
        Ok(Outcome::success(
            "succesfully deployed service monitors".to_string(),
        ))
    }

    async fn install_rules(
        &self,
        #[allow(clippy::ptr_arg)] rules: &Vec<RuleGroup>,
        client: &Arc<K8sClient>,
    ) -> Result<Outcome, InterpretError> {
        let mut prom_rule_spec = PrometheusRuleSpec {
            groups: rules.clone(),
        };

        let default_rules_group = RuleGroup {
            name: "default-rules".to_string(),
            rules: build_default_application_rules(),
        };

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

    async fn install_client_kube_metrics(&self) -> Result<Outcome, InterpretError> {
        self.install_chart(
            "oci://hub.nationtech.io/harmony".to_string(),
            "nt-kube-metrics".to_string(),
        )
        .await?;
        Ok(Outcome::success(format!(
            "Installed client kube metrics in ns {}",
            &self.sender.namespace.clone()
        )))
    }

    async fn install_grafana(&self, client: &Arc<K8sClient>) -> Result<Outcome, InterpretError> {
        let mut label = BTreeMap::new();
        label.insert("dashboards".to_string(), "grafana".to_string());
        let labels = LabelSelector {
            match_labels: label.clone(),
            match_expressions: vec![],
        };
        let mut json_data = BTreeMap::new();
        json_data.insert("timeInterval".to_string(), "5s".to_string());
        let namespace = self.sender.namespace.clone();

        let json = build_default_dashboard(&namespace);

        let graf_data_source = GrafanaDatasource {
            metadata: ObjectMeta {
                name: Some(format!(
                    "grafana-datasource-{}",
                    self.sender.namespace.clone()
                )),
                namespace: Some(self.sender.namespace.clone()),
                ..Default::default()
            },
            spec: GrafanaDatasourceSpec {
                instance_selector: labels.clone(),
                allow_cross_namespace_import: Some(false),
                datasource: GrafanaDatasourceConfig {
                    access: "proxy".to_string(),
                    database: Some("prometheus".to_string()),
                    json_data: Some(json_data),
                    //this is fragile
                    name: format!("prometheus-{}-0", self.sender.namespace.clone()),
                    r#type: "prometheus".to_string(),
                    url: format!(
                        "http://prometheus-operated.{}.svc.cluster.local:9090",
                        self.sender.namespace.clone()
                    ),
                },
            },
        };

        client
            .apply(&graf_data_source, Some(&self.sender.namespace.clone()))
            .await
            .map_err(|e| InterpretError::new(e.to_string()))?;

        let graf_dashboard = GrafanaDashboard {
            metadata: ObjectMeta {
                name: Some(format!(
                    "grafana-dashboard-{}",
                    self.sender.namespace.clone()
                )),
                namespace: Some(self.sender.namespace.clone()),
                ..Default::default()
            },
            spec: GrafanaDashboardSpec {
                resync_period: Some("30s".to_string()),
                instance_selector: labels.clone(),
                json,
            },
        };

        client
            .apply(&graf_dashboard, Some(&self.sender.namespace.clone()))
            .await
            .map_err(|e| InterpretError::new(e.to_string()))?;

        let grafana = Grafana {
            metadata: ObjectMeta {
                name: Some(format!("grafana-{}", self.sender.namespace.clone())),
                namespace: Some(self.sender.namespace.clone()),
                labels: Some(label.clone()),
                ..Default::default()
            },
            spec: GrafanaSpec {
                config: None,
                admin_user: None,
                admin_password: None,
                ingress: None,
                persistence: None,
                resources: None,
            },
        };
        client
            .apply(&grafana, Some(&self.sender.namespace.clone()))
            .await
            .map_err(|e| InterpretError::new(e.to_string()))?;
        Ok(Outcome::success(format!(
            "successfully deployed grafana instance {:#?}",
            grafana.metadata.name
        )))
    }

    async fn install_receivers(
        &self,
        sender: &CRDPrometheus,
        receivers: &Vec<Box<dyn AlertReceiver<CRDPrometheus>>>,
    ) -> Result<Outcome, InterpretError> {
        for receiver in receivers.iter() {
            receiver.install(sender).await.map_err(|err| {
                InterpretError::new(format!("failed to install receiver: {}", err))
            })?;
        }
        Ok(Outcome::success("successfully deployed receivers".into()))
    }
}
