use std::{collections::BTreeMap, marker::PhantomData, vec::IntoIter};

use serde::Deserialize;

pub trait EnvEntryConfig {
    const SECRETS: &'static [&'static str];
    const BAO_KEY: &'static str;
}

pub struct EnvEntry<T>(Vec<(&'static str, String)>, PhantomData<T>);

impl<T: EnvEntryConfig> EnvEntry<T> {
    pub fn try_new_from_env() -> anyhow::Result<Self> {
        let mut result = Vec::with_capacity(T::SECRETS.len());
        for key in T::SECRETS {
            let value = common::env::read_env(key)?;
            result.push((*key, value));
        }
        Ok(Self(result, PhantomData))
    }

    fn new_from_values(values: Vec<(&'static str, String)>) -> Self {
        Self(values, PhantomData)
    }

    pub fn read_from_bao() -> anyhow::Result<Self> {
        read_bao_data()
    }
}

impl<T> From<EnvEntry<T>> for Vec<(&'static str, String)> {
    fn from(value: EnvEntry<T>) -> Self {
        value.0
    }
}

impl<T> IntoIterator for EnvEntry<T> {
    type Item = (&'static str, String);

    type IntoIter = IntoIter<Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        self.0.into_iter()
    }
}

impl<'de, T: EnvEntryConfig> serde::Deserialize<'de> for EnvEntry<T> {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserializer.deserialize_map(EnvEntryVisitor(PhantomData))
    }
}
struct EnvEntryVisitor<T>(PhantomData<T>);

impl<'de, T: EnvEntryConfig> serde::de::Visitor<'de> for EnvEntryVisitor<T> {
    type Value = EnvEntry<T>;

    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
        formatter.write_fmt(format_args!(
            "a map with unique keys {} with string values",
            T::SECRETS.join(", "),
        ))
    }

    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
    where
        A: serde::de::MapAccess<'de>,
    {
        let mut values = BTreeMap::<&'static str, String>::new();
        while let Some((key, value)) = map.next_entry::<&'de str, String>()? {
            let mapped_key = T::SECRETS.iter().find(|n| **n == key).copied();
            let Some(key) = mapped_key else {
                return Err(serde::de::Error::unknown_field(key, T::SECRETS));
            };
            if values.contains_key(key) {
                return Err(serde::de::Error::duplicate_field(key));
            }
            values.insert(key, value);
        }
        for key in T::SECRETS {
            if !values.contains_key(key) {
                return Err(serde::de::Error::missing_field(key));
            }
        }
        let values = values.into_iter().collect();
        let entry = EnvEntry::<T>::new_from_values(values);
        Ok(entry)
    }
}

#[derive(Debug, Deserialize)]
struct OpenBaoKvEntry<T> {
    data: OpenBaoKvEntryData<T>,
}

#[derive(Debug, Deserialize)]
struct OpenBaoKvEntryData<T> {
    data: T,
}

fn read_bao_data<T: EnvEntryConfig>() -> anyhow::Result<EnvEntry<T>> {
    let mut cmd = common::proc::Command::new("bao");
    cmd.args(["kv", "get", "-format=json", "-mount=opentofu", T::BAO_KEY]);
    let result: OpenBaoKvEntry<EnvEntry<T>> = cmd.try_spawn_to_json()?;
    Ok(result.data.data)
}
