use crate::Error;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value;
use std::fmt::Display;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Disclosure {
  pub salt: String,
  pub claim_name: Option<String>,
  pub claim_value: Value,
  pub disclosure: String,
}
impl Disclosure {
  pub fn new(salt: String, claim_name: Option<String>, claim_value: Value) -> Self {
    let input = if let Some(name) = &claim_name {
      format!("[\"{}\", \"{}\", {}]", &salt, &name, &claim_value.to_string())
    } else {
      format!("[\"{}\", {}]", &salt, &claim_value.to_string())
    };
    let encoded = multibase::Base::Base64Url.encode(input);
    Self {
      salt,
      claim_name,
      claim_value,
      disclosure: encoded,
    }
  }
  pub fn parse(disclosure: String) -> Result<Self, Error> {
    let decoded: Vec<Value> = multibase::Base::Base64Url
      .decode(&disclosure)
      .map_err(|_e| {
        Error::InvalidDisclosure(format!(
          "Base64 decoding of the disclosure was not possible {}",
          disclosure
        ))
      })
      .and_then(|data| {
        serde_json::from_slice(&data).map_err(|_e| {
          Error::InvalidDisclosure(format!(
            "decoded disclosure could not be serialized as an array {}",
            disclosure
          ))
        })
      })?;
    if decoded.len() == 2 {
      Ok(Self {
        salt: decoded
          .first()
          .ok_or(Error::InvalidDisclosure("invalid salt".to_string()))?
          .as_str()
          .ok_or(Error::InvalidDisclosure(
            "salt could not be parsed as a string".to_string(),
          ))?
          .to_owned(),
        claim_name: None,
        claim_value: decoded
          .get(1)
          .ok_or(Error::InvalidDisclosure("invalid claim name".to_string()))?
          .clone(),
        disclosure,
      })
    } else if decoded.len() == 3 {
      Ok(Self {
        salt: decoded
          .first()
          .ok_or(Error::InvalidDisclosure("invalid salt".to_string()))?
          .as_str()
          .ok_or(Error::InvalidDisclosure(
            "salt could not be parsed as a string".to_string(),
          ))?
          .to_owned(),
        claim_name: Some(
          decoded
            .get(1)
            .ok_or(Error::InvalidDisclosure("invalid claim name".to_string()))?
            .as_str()
            .ok_or(Error::InvalidDisclosure(
              "claim name could not be parsed as a string".to_string(),
            ))?
            .to_owned(),
        ),
        claim_value: decoded
          .get(2)
          .ok_or(Error::InvalidDisclosure("invalid claim name".to_string()))?
          .clone(),
        disclosure,
      })
    } else {
      Err(Error::InvalidDisclosure(format!(
        "deserialized array has an invalid length of {}",
        decoded.len()
      )))
    }
  }
  pub fn as_str(&self) -> &str {
    &self.disclosure
  }
  pub fn into_string(self) -> String {
    self.disclosure
  }
}
impl Display for Disclosure {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    f.write_str(&self.disclosure)
  }
}
#[cfg(test)]
mod test {
  use super::Disclosure;
  #[test]
  fn test_parsing() {
    let disclosure = Disclosure::new(
      "2GLC42sKQveCfGfryNRN9w".to_string(),
      Some("time".to_owned()),
      "2012-04-23T18:25Z".to_owned().into(),
    );
    let parsed =
      Disclosure::parse("WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgInRpbWUiLCAiMjAxMi0wNC0yM1QxODoyNVoiXQ".to_owned());
    assert_eq!(parsed.unwrap(), disclosure);
  }
  #[test]
  fn test_creating() {
    let disclosure = Disclosure::new("lklxF5jMYlGTPUovMNIvCA".to_owned(), None, "US".to_owned().into());
    assert_eq!(
      "WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgIlVTIl0".to_owned(),
      disclosure.to_string()
    );
  }
}