use derive_new::new;
use k8s_openapi::NamespaceResourceScope;
use kube::{
    Api, Client, Config, Error, Resource,
    api::PostParams,
    config::{KubeConfigOptions, Kubeconfig},
};
use log::error;
use serde::de::DeserializeOwned;

#[derive(new)]
pub struct K8sClient {
    client: Client,
}

impl K8sClient {
    pub async fn try_default() -> Result<Self, Error> {
        Ok(Self {
            client: Client::try_default().await?,
        })
    }

    pub async fn apply_all<
        K: Resource<Scope = NamespaceResourceScope>
            + std::fmt::Debug
            + Sync
            + DeserializeOwned
            + Default
            + serde::Serialize
            + Clone,
    >(
        &self,
        resource: &Vec<K>,
    ) -> Result<Vec<K>, kube::Error>
    where
        <K as kube::Resource>::DynamicType: Default,
    {
        let mut result = vec![];
        for r in resource.iter() {
            let api: Api<K> = Api::all(self.client.clone());
            result.push(api.create(&PostParams::default(), &r).await?);
        }
        Ok(result)
    }

    pub async fn apply_namespaced<K>(
        &self,
        resource: &Vec<K>,
        ns: Option<&str>,
    ) -> Result<Vec<K>, Error>
    where
        K: Resource<Scope = NamespaceResourceScope>
            + Clone
            + std::fmt::Debug
            + DeserializeOwned
            + serde::Serialize
            + Default,
        <K as kube::Resource>::DynamicType: Default,
    {
        let mut resources = Vec::new();
        for r in resource.iter() {
            let api: Api<K> = match ns {
                Some(ns) => Api::namespaced(self.client.clone(), ns),
                None => Api::default_namespaced(self.client.clone()),
            };
            resources.push(api.create(&PostParams::default(), &r).await?);
        }
        Ok(resources)
    }

    pub(crate) async fn from_kubeconfig(path: &str) -> Option<K8sClient> {
        let k = match Kubeconfig::read_from(path) {
            Ok(k) => k,
            Err(e) => {
                error!("Failed to load kubeconfig from {path} : {e}");
                return None;
            }
        };
        Some(K8sClient::new(
            Client::try_from(
                Config::from_custom_kubeconfig(k, &KubeConfigOptions::default())
                    .await
                    .unwrap(),
            )
            .unwrap(),
        ))
    }
}
