use std::{collections::BTreeMap, path::PathBuf};

use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddleFile {
    pub vars: CuddleVars,
    pub deployment: Option<CuddleDeployment>,
    pub components: Option<CuddleComponents>,
    pub please: Option<CuddlePlease>,
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddleComponents {
    pub database: Option<CuddleDatabase>,
    pub assets: Option<CuddleAssets>,
    pub packages: Option<Packages>,
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddleAssets {
    pub volumes: Option<Vec<CuddleAssetInclude>>,
    pub clean: Option<bool>,
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddlePlease {}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct Packages {
    pub debian: DebianPackages,
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct DebianPackages {
    pub dev: Vec<String>,
    pub release: Vec<String>,
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddleAssetInclude {
    pub from: PathBuf,
    pub to: PathBuf,
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(untagged)]
pub enum CuddleDatabase {
    Enabled(bool),
    Values { migrations: PathBuf },
    Default {},
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddleVars {
    pub service: String,
    pub registry: String,

    pub rust: Option<CuddleRustVars>,

    pub clusters: Option<CuddleClusters>,
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddleRustVars {
    pub publish: Option<CuddleRustPublishVars>,
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddleRustPublishVars {}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddleDeployment {
    pub registry: String,
    pub env: CuddleDeploymentEnv,
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddleDeploymentEnv(pub BTreeMap<String, CuddleDeploymentCluster>);

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddleDeploymentCluster {
    pub clusters: Vec<String>,
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddleClusters(pub BTreeMap<String, CuddleCluster>);

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

impl CuddleFile {
    pub async fn from_cuddle_file() -> eyre::Result<Self> {
        let cuddle_file_content = tokio::fs::read_to_string("cuddle.yaml").await?;

        Self::parse_cuddle_file(&cuddle_file_content)
    }

    pub fn parse_cuddle_file(content: &str) -> eyre::Result<Self> {
        let cuddle_file: CuddleFile = serde_yaml::from_str(content)?;

        Ok(cuddle_file)
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn parse_file() {
        let cuddle_file = r#"
base: "git@git.kjuulh.io:kjuulh/cuddle-base.git"

vars:
  service: "infrastructure-example"
  registry: kasperhermansen

  clusters:
    clank_prod:
      replicas: "3"
      namespace: clank_prod

deployment:
  registry: git@git.kjuulh.io:kjuulh/clank-clusters
  env:
    prod:
      clusters:
        - clank_prod
components:
  database: true
  
  assets:
    volumes:
      - from: somewhere
        to: somewhere-else

  packages:
    debian:
      dev:
      - "capnp"
      release:
      - "capnp"
  
scripts:
  render:
    type: shell
    args:
      cluster:
        name: cluster
        type: flag
      image_tag:
        name: image_tag
        type: flag"#;

        let res = CuddleFile::parse_cuddle_file(cuddle_file).expect("to parse file");

        let mut clusters = BTreeMap::new();
        clusters.insert(
            "clank_prod".into(),
            CuddleCluster {
                namespace: "clank_prod".into(),
            },
        );

        let mut deployment = BTreeMap::new();
        deployment.insert(
            "prod".into(),
            CuddleDeploymentCluster {
                clusters: vec!["clank_prod".into()],
            },
        );

        let expected = CuddleFile {
            vars: CuddleVars {
                service: "infrastructure-example".into(),
                registry: "kasperhermansen".into(),
                clusters: Some(CuddleClusters(clusters)),
                rust: None,
            },
            deployment: Some(crate::cuddle_file::CuddleDeployment {
                registry: "git@git.kjuulh.io:kjuulh/clank-clusters".into(),
                env: CuddleDeploymentEnv(deployment),
            }),
            components: Some(CuddleComponents {
                database: Some(CuddleDatabase::Enabled(true)),
                assets: Some(CuddleAssets {
                    volumes: Some(vec![CuddleAssetInclude {
                        from: "somewhere".into(),
                        to: "somewhere-else".into(),
                    }]),
                    clean: None,
                }),
                packages: Some(Packages {
                    debian: DebianPackages {
                        dev: vec!["capnp".into()],
                        release: vec!["capnp".into()],
                    },
                }),
            }),
            please: None,
        };

        pretty_assertions::assert_eq!(expected, res)
    }

    #[test]
    fn cuddle_database_default() {
        let cuddle_file = r#"
base: "git@git.kjuulh.io:kjuulh/cuddle-base.git"

vars:
  service: "infrastructure-example"
  registry: kasperhermansen

components:
  database: {}
"#;

        let res = CuddleFile::parse_cuddle_file(cuddle_file).expect("to parse file");

        let expected = CuddleFile {
            vars: CuddleVars {
                service: "infrastructure-example".into(),
                registry: "kasperhermansen".into(),
                clusters: None,
                rust: None,
            },
            deployment: None,
            components: Some(CuddleComponents {
                database: Some(CuddleDatabase::Default {}),
                assets: None,
                packages: None,
            }),
            please: None,
        };

        pretty_assertions::assert_eq!(expected, res)
    }

    #[test]
    fn cuddle_packages() {
        let cuddle_file = r#"
base: "git@git.kjuulh.io:kjuulh/cuddle-base.git"

vars:
  service: "infrastructure-example"
  registry: kasperhermansen

components:
  packages:
    debian:
      dev:
      - "capnp"
      release:
      - "capnp"
"#;

        let res = CuddleFile::parse_cuddle_file(cuddle_file).expect("to parse file");

        let expected = CuddleFile {
            vars: CuddleVars {
                service: "infrastructure-example".into(),
                registry: "kasperhermansen".into(),
                clusters: None,
                rust: None,
            },
            deployment: None,
            components: Some(CuddleComponents {
                database: None,
                assets: None,
                packages: Some(Packages {
                    debian: DebianPackages {
                        dev: vec!["capnp".into()],
                        release: vec!["capnp".into()],
                    },
                }),
            }),
            please: None,
        };

        pretty_assertions::assert_eq!(expected, res)
    }
}
