use crate::Error;
use serde::{Deserialize, Serialize, Serializer};
use std::fmt::{Display, Formatter};
use std::str::FromStr;

#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize)]
#[serde(from = "String")]
/// Type representing licenses used for advisory content
#[non_exhaustive]
pub enum License {
    /// Creative Commons Zero v1.0 Universal
    /// SPDX identifier: CC0-1.0
    #[default]
    CcZero10,
    /// Creative Commons Attribution 4.0 International
    /// SPDX identifier: CC-BY-4.0
    ///
    /// Note: For GitHub Security Advisories database,
    /// providing a link is [documented](https://docs.github.com/en/site-policy/github-terms/github-terms-for-additional-products-and-features#advisory-database)
    /// as fulfilling the attribution obligation for the CC-BY 4.0 license used.
    ///
    /// For advisories imported from a GitHub Security Advisory, we follow this by putting the
    /// original URL in the `url` filed of the RustSec advisory, as it assures the link will be
    /// visible to downstream users.
    CcBy40,
    /// Other SPDX requirement
    Other(String),
}

impl From<String> for License {
    fn from(s: String) -> Self {
        match s.as_str() {
            "CC0-1.0" => License::CcZero10,
            "CC-BY-4.0" => License::CcBy40,
            _ => License::Other(s),
        }
    }
}

impl Serialize for License {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(self.spdx())
    }
}

impl Display for License {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.spdx())
    }
}

impl License {
    /// Get license as an `&str` containing the SPDX identifier
    pub fn spdx(&self) -> &str {
        match self {
            License::CcBy40 => "CC-BY-4.0",
            License::CcZero10 => "CC0-1.0",
            License::Other(ref l) => l,
        }
    }
}

impl FromStr for License {
    type Err = Error;

    // Parse standard SPDX identifiers
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(match s {
            "CC0-1.0" => License::CcZero10,
            "CC-BY-4.0" => License::CcBy40,
            l => License::Other(l.to_string()),
        })
    }
}

#[cfg(test)]
mod tests {
    use crate::advisory::License;

    #[test]
    fn serialize_licenses() {
        assert_eq!(
            serde_json::to_string(&License::CcBy40).unwrap(),
            "\"CC-BY-4.0\"".to_string()
        );
        assert_eq!(
            serde_json::to_string(&License::Other("MPL-2.0".to_string())).unwrap(),
            "\"MPL-2.0\"".to_string()
        );
    }

    #[test]
    fn deserialize_licenses() {
        let l: License = serde_json::from_str("\"CC-BY-4.0\"").unwrap();
        assert_eq!(l, License::CcBy40);
        let l: License = serde_json::from_str("\"MPL-2.0\"").unwrap();
        assert_eq!(l, License::Other("MPL-2.0".to_string()));
    }
}
