use std::sync::Arc;

use async_trait::async_trait;
use log::debug;
use serde::Serialize;
use strum::{Display, EnumString};

use crate::{
    data::{Id, Version},
    interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
    inventory::Inventory,
    modules::monitoring::ntfy::helm::ntfy_helm_chart::ntfy_helm_chart_score,
    score::Score,
    topology::{HelmCommand, K8sclient, Topology, k8s::K8sClient},
};

#[derive(Debug, Clone, Serialize)]
pub struct NtfyScore {
    pub namespace: String,
}

impl<T: Topology + HelmCommand + K8sclient> Score<T> for NtfyScore {
    fn create_interpret(&self) -> Box<dyn Interpret<T>> {
        Box::new(NtfyInterpret {
            score: self.clone(),
        })
    }

    fn name(&self) -> String {
        format!("Ntfy")
    }
}

#[derive(Debug, Serialize)]
pub struct NtfyInterpret {
    pub score: NtfyScore,
}

#[derive(Debug, EnumString, Display)]
enum NtfyAccessMode {
    #[strum(serialize = "read-write", serialize = "rw", to_string = "read-write")]
    ReadWrite,
    #[strum(
        serialize = "read-only",
        serialize = "ro",
        serialize = "read",
        to_string = "read-only"
    )]
    ReadOnly,
    #[strum(
        serialize = "write-only",
        serialize = "wo",
        serialize = "write",
        to_string = "write-only"
    )]
    WriteOnly,
    #[strum(serialize = "none", to_string = "deny")]
    Deny,
}

#[derive(Debug, EnumString, Display)]
enum NtfyRole {
    #[strum(serialize = "user", to_string = "user")]
    User,
    #[strum(serialize = "admin", to_string = "admin")]
    Admin,
}

impl NtfyInterpret {
    async fn add_user(
        &self,
        k8s_client: Arc<K8sClient>,
        username: &str,
        password: &str,
        role: Option<NtfyRole>,
    ) -> Result<(), String> {
        let role = match role {
            Some(r) => r,
            None => NtfyRole::User,
        };

        k8s_client
            .exec_app(
                "ntfy".to_string(),
                Some(&self.score.namespace),
                vec![
                    "sh",
                    "-c",
                    format!("NTFY_PASSWORD={password} ntfy user add --role={role} {username}")
                        .as_str(),
                ],
            )
            .await?;

        Ok(())
    }

    async fn set_access(
        &self,
        k8s_client: Arc<K8sClient>,
        username: &str,
        topic: &str,
        mode: NtfyAccessMode,
    ) -> Result<(), String> {
        k8s_client
            .exec_app(
                "ntfy".to_string(),
                Some(&self.score.namespace),
                vec![
                    "sh",
                    "-c",
                    format!("ntfy access {username} {topic} {mode}").as_str(),
                ],
            )
            .await?;

        Ok(())
    }
}

/// We need a ntfy interpret to wrap the HelmChartScore in order to run the score, and then bootstrap the config inside ntfy
#[async_trait]
impl<T: Topology + HelmCommand + K8sclient> Interpret<T> for NtfyInterpret {
    async fn execute(
        &self,
        inventory: &Inventory,
        topology: &T,
    ) -> Result<Outcome, InterpretError> {
        ntfy_helm_chart_score(self.score.namespace.clone())
            .create_interpret()
            .execute(inventory, topology)
            .await?;

        debug!("installed ntfy helm chart");
        let client = topology
            .k8s_client()
            .await
            .expect("couldn't get k8s client");

        client
            .wait_until_deployment_ready(
                "ntfy".to_string(),
                Some(&self.score.namespace.as_str()),
                None,
            )
            .await?;
        debug!("created k8s client");

        self.add_user(client, "harmony", "harmony", Some(NtfyRole::Admin))
            .await?;

        debug!("exec into pod done");

        Ok(Outcome::success("installed ntfy".to_string()))
    }

    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!()
    }
}
