use crate::auth::claims::common::ArrayOrValue;
use serde::{Deserialize, Serialize};

// The audience claim in an assertion (e.g. the `aud` claim in the JWT).
/// This can be a single audience or multiple audiences.
// If its a URL inner type then http://foo.com and http://foo.com/ should be considered equal.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(transparent)]
pub struct Audience(ArrayOrValue<String>);

impl From<ArrayOrValue<String>> for Audience {
    fn from(audience: ArrayOrValue<String>) -> Self {
        Self(audience)
    }
}

impl Audience {
    /// Creates a new audience with a single value.
    pub fn new(audience: impl Into<String>) -> Self {
        Self(ArrayOrValue::single(audience.into()))
    }

    /// Adds an audience to the list of audiences.
    pub fn add_audience(self, audience: impl Into<String>) -> Self {
        Self(self.0.add(audience.into()))
    }

    pub fn contains(&self, audience: &str) -> bool {
        self.0.contains(audience)
    }
}

impl From<String> for Audience {
    fn from(audience: String) -> Self {
        Self::new(audience)
    }
}

impl From<Vec<String>> for Audience {
    fn from(audiences: Vec<String>) -> Self {
        Self(ArrayOrValue::from_iter(audiences))
    }
}

#[cfg(feature = "test_utils")]
impl<T> fake::Dummy<T> for Audience {
    fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &T, _rng: &mut R) -> Self {
        Self::new("https://console-api.stashdata.net")
    }
}

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

    mod single {
        use super::*;

        #[test]
        fn serializes_as_literal() {
            let audience = Audience::new("http://example.com");
            let serialized = serde_json::to_value(&audience).unwrap();
            assert_eq!(serialized, json!("http://example.com"));
        }

        #[test]
        fn deserializes_from_literal() {
            let audience: Audience = serde_json::from_str(r#""http://example.com""#).unwrap();
            assert_eq!(
                audience.0,
                ArrayOrValue::single("http://example.com".to_string())
            );
        }
    }

    mod multiple {
        use super::*;

        #[test]
        fn serializes_as_array() {
            let target_a = "http://example.com";
            let target_b = "http://another-example.com";
            let audience = Audience::new(target_a).add_audience(target_b);
            let serialized = serde_json::to_value(&audience).unwrap();
            assert!(
                serialized == json!([target_a, target_b])
                    || serialized == json!([target_b, target_a]),
                "Serialized audience should be an array of two URLs, got: {serialized}"
            );
        }

        #[test]
        fn deserializes_from_array() {
            let audience: Audience =
                serde_json::from_str(r#"["http://example.com","http://another-example.com"]"#)
                    .unwrap();
            assert_eq!(
                audience.0,
                ArrayOrValue::from_iter(vec![
                    "http://example.com".to_string(),
                    "http://another-example.com".to_string(),
                ])
            );
        }
    }
}
