use std::{backtrace, collections::HashMap};

use serde::Serialize;
use serde_yaml::{Mapping, Value};
use url::Url;

use crate::modules::application::features::CDApplicationConfig;

#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Helm {
    pub pass_credentials: Option<bool>,
    pub parameters: Vec<Value>,
    pub file_parameters: Vec<Value>,
    pub release_name: Option<String>,
    pub value_files: Vec<String>,
    pub ignore_missing_value_files: Option<bool>,
    pub values: Option<String>,
    pub values_object: Option<Value>,
    pub skip_crds: Option<bool>,
    pub skip_schema_validation: Option<bool>,
    pub version: Option<String>,
    pub kube_version: Option<String>,
    pub api_versions: Vec<String>,
    pub namespace: Option<String>,
}

#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Source {
    pub repo_url: Url,
    pub target_revision: Option<String>,
    pub chart: String,
    pub helm: Helm,
}

#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Automated {
    pub prune: bool,
    pub self_heal: bool,
    pub allow_empty: bool,
}

#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Backoff {
    pub duration: String,
    pub factor: u32,
    pub max_duration: String,
}

#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Retry {
    pub limit: u32,
    pub backoff: Backoff,
}

#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SyncPolicy {
    pub automated: Automated,
    pub sync_options: Vec<String>,
    pub retry: Retry,
}

#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ArgoApplication {
    pub name: String,
    pub namespace: Option<String>,
    pub project: String,
    pub source: Source,
    pub sync_policy: SyncPolicy,
    pub revision_history_limit: u32,
}

impl Default for ArgoApplication {
    fn default() -> Self {
        Self {
            name: Default::default(),
            namespace: Default::default(),
            project: Default::default(),
            source: Source {
                repo_url: Url::parse("http://asdf").expect("Couldn't parse to URL"),
                target_revision: None,
                chart: "".to_string(),
                helm: Helm {
                    pass_credentials: None,
                    parameters: vec![],
                    file_parameters: vec![],
                    release_name: None,
                    value_files: vec![],
                    ignore_missing_value_files: None,
                    values: None,
                    values_object: None,
                    skip_crds: None,
                    skip_schema_validation: None,
                    version: None,
                    kube_version: None,
                    api_versions: vec![],
                    namespace: None,
                },
            },
            sync_policy: SyncPolicy {
                automated: Automated {
                    prune: false,
                    self_heal: false,
                    allow_empty: false,
                },
                sync_options: vec![],
                retry: Retry {
                    limit: 5,
                    backoff: Backoff {
                        duration: "5s".to_string(),
                        factor: 2,
                        max_duration: "3m".to_string(),
                    },
                },
            },
            revision_history_limit: 10,
        }
    }
}

impl From<CDApplicationConfig> for ArgoApplication {
    fn from(value: CDApplicationConfig) -> Self {
        Self {
            name: value.name,
            namespace: Some(value.namespace),
            project: "default".to_string(),
            source: Source {
                repo_url: Url::parse(value.helm_chart_repo_url.to_string().as_str())
                    .expect("couldn't convert to URL"),
                target_revision: None,
                chart: value.helm_chart_name,
                helm: Helm {
                    pass_credentials: None,
                    parameters: vec![],
                    file_parameters: vec![],
                    release_name: None,
                    value_files: vec![],
                    ignore_missing_value_files: None,
                    values: None,
                    values_object: Some(value.values_overrides),
                    skip_crds: None,
                    skip_schema_validation: None,
                    version: None,
                    kube_version: None,
                    api_versions: vec![],
                    namespace: None,
                },
            },
            sync_policy: SyncPolicy {
                automated: Automated {
                    prune: false,
                    self_heal: false,
                    allow_empty: true,
                },
                sync_options: vec![],
                retry: Retry {
                    limit: 5,
                    backoff: Backoff {
                        duration: "5s".to_string(),
                        factor: 2,
                        max_duration: "3m".to_string(),
                    },
                },
            },
            ..Self::default()
        }
    }
}

impl ArgoApplication {
    fn to_yaml(self) -> serde_yaml::Value {
        let name = self.name;
        let namespace = if let Some(ns) = self.namespace {
            ns
        } else {
            "argocd".to_string()
        };
        let project = self.project;
        let source = self.source;

        let mut yaml_str = format!(
            r#"
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: {name}
  # You'll usually want to add your resources to the argocd namespace.
  namespace: {namespace}
spec:
  # The project the application belongs to.
  project: {project}

  # Destination cluster and namespace to deploy the application
  destination:
    # cluster API URL
    server: https://kubernetes.default.svc
    # or cluster name
    # name: in-cluster
    # The namespace will only be set for namespace-scoped resources that have not set a value for .metadata.namespace
    namespace: {namespace}

"#
        );

        yaml_str.push_str(
            &serde_yaml::to_string(&source.clone())
                .expect("couldn't serialize source to yaml string"),
        );
        yaml_str.push_str(
            &serde_yaml::to_string(&self.sync_policy)
                .expect("couldn't serialize sync policy to yaml string"),
        );
        yaml_str.push_str(
            &serde_yaml::to_string(&self.revision_history_limit)
                .expect("couldn't serialize revision history to yaml string"),
        );

        serde_yaml::from_str(&yaml_str).expect("Couldn't parse YAML")
    }
}
