//! The "simple trust anchor" OpenPGP verification method.

use std::{collections::HashSet, fmt::Display, num::NonZeroUsize};

use garde::Validate;
use serde::{Deserialize, Serialize};

use crate::{
    Error,
    common::{ordered_set, set_to_vec},
    config::technology::openpgp::{DomainName, OpenpgpFingerprint},
    file::ConfigTrustAnchorMode,
};

/// Default minimum required amount of certifications for trust anchor mode.
const DEFAULT_REQUIRED_CERTIFICATIONS: NonZeroUsize =
    NonZeroUsize::new(3).expect("3 is greater than 0");

/// The required number of certifications for an identity on an artifact verifier.
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct NumCertifications(NonZeroUsize);

impl NumCertifications {
    /// Creates a new [`NumCertifications`] from a [`NonZeroUsize`].
    pub fn new(num: NonZeroUsize) -> Self {
        Self(num)
    }

    /// Returns the inner [`NonZeroUsize`].
    pub fn get(&self) -> NonZeroUsize {
        self.0
    }
}

impl Default for NumCertifications {
    fn default() -> Self {
        Self(DEFAULT_REQUIRED_CERTIFICATIONS)
    }
}

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

/// Validates that the required number of certifications matches the amount of pinned trust anchors.
///
/// # Errors
///
/// Returns an error, if the `num_certifications` of a [`ConfigTrustAnchorMode`] is larger than the
/// number of `trust_anchor_fingerprint_matches`.
fn validate_required_certifications(
    trust_anchor_fingerprint_matches: &HashSet<OpenpgpFingerprint>,
) -> impl FnOnce(&NumCertifications, &()) -> garde::Result + '_ {
    move |num_certifications, _| {
        let num_certifications = num_certifications.get().get();
        let num_fingerprints = trust_anchor_fingerprint_matches.len();

        if num_fingerprints > 0 && num_certifications > num_fingerprints {
            return Err(garde::Error::new(format!(
                "is {num_certifications}, but must be <= {num_fingerprints}, as the \"trust anchor\" verification mode only pins {num_fingerprints} trust anchor fingerprint(s): {}",
                trust_anchor_fingerprint_matches
                    .iter()
                    .map(ToString::to_string)
                    .collect::<Vec<_>>()
                    .join(", ")
            )));
        }

        Ok(())
    }
}

/// Settings for the trust-anchor mode.
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize, Validate)]
pub struct TrustAnchorMode {
    /// The number of certifications from a trust-anchor required to exist for an artifact
    /// verifier.
    #[garde(custom(validate_required_certifications(&self.trust_anchor_fingerprint_matches)))]
    required_certifications: NumCertifications,

    /// The identity of an artifact verifier must match one of the domains.
    #[serde(serialize_with = "ordered_set")]
    #[garde(skip)]
    artifact_verifier_identity_domain_matches: HashSet<DomainName>,

    /// The fingerprint of a trust anchor must match one of the fingerprints.
    #[serde(serialize_with = "ordered_set")]
    #[garde(skip)]
    trust_anchor_fingerprint_matches: HashSet<OpenpgpFingerprint>,
}

impl TrustAnchorMode {
    /// Creates a new [`TrustAnchorMode`].
    ///
    /// # Errors
    ///
    /// Returns an error if `required_certifications` is larger than the length of
    /// `trust_anchor_fingerprint_matches`.
    pub fn new(
        required_certifications: NumCertifications,
        artifact_verifier_identity_domain_matches: HashSet<DomainName>,
        trust_anchor_fingerprint_matches: HashSet<OpenpgpFingerprint>,
    ) -> Result<Self, Error> {
        let mode = Self {
            required_certifications,
            artifact_verifier_identity_domain_matches,
            trust_anchor_fingerprint_matches,
        };

        mode.validate().map_err(|source| Error::Validation {
            context: "creating a trust anchor verification mode object".to_string(),
            source,
        })?;

        Ok(mode)
    }

    /// Creates a new [`TrustAnchorMode`] from a [`ConfigTrustAnchorMode`] and another
    /// [`TrustAnchorMode`] for defaults.
    pub(crate) fn from_config_with_defaults(
        config: &ConfigTrustAnchorMode,
        defaults: &TrustAnchorMode,
    ) -> Result<Self, Error> {
        let required_certifications = config
            .required_certifications()
            .unwrap_or(defaults.required_certifications());
        let artifact_verifier_identity_domain_matches = config
            .artifact_verifier_identity_domain_matches()
            .unwrap_or(defaults.artifact_verifier_identity_domain_matches())
            .clone();
        let trust_anchor_fingerprint_matches = config
            .trust_anchor_fingerprint_matches()
            .unwrap_or(defaults.trust_anchor_fingerprint_matches())
            .clone();

        TrustAnchorMode::new(
            required_certifications,
            artifact_verifier_identity_domain_matches,
            trust_anchor_fingerprint_matches,
        )
    }

    /// Returns the required number of certifications from a trust-anchor on an artifact verifier.
    pub fn required_certifications(&self) -> NumCertifications {
        self.required_certifications
    }

    /// Returns a slice of the list of domains an artifact verifier must match at least one of.
    pub fn artifact_verifier_identity_domain_matches(&self) -> &HashSet<DomainName> {
        &self.artifact_verifier_identity_domain_matches
    }

    /// Returns a slice of the list of fingerprints a trust-anchor must match at least one of.
    pub fn trust_anchor_fingerprint_matches(&self) -> &HashSet<OpenpgpFingerprint> {
        &self.trust_anchor_fingerprint_matches
    }
}

impl Display for TrustAnchorMode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(
            f,
            "✅ Each artifact is verified using the \"trust anchor\" verification method.\n"
        )?;

        if !self.artifact_verifier_identity_domain_matches().is_empty() {
            writeln!(
                f,
                "📧 A valid certificate must have a valid User ID that uses one of the following domains and has {} certification(s) from individual trust anchors on it for the certificate to be considered as artifact verifier:",
                self.required_certifications()
            )?;
            for domain_name in set_to_vec(self.artifact_verifier_identity_domain_matches()).iter() {
                writeln!(f, "⤷ {domain_name}")?;
            }
            writeln!(f)?;
        } else {
            writeln!(
                f,
                "📧 A valid certificate is not required to use a specific domain in any of its User IDs, but needs {} certification(s) from individual trust anchors on one User ID for the certificate to be considered as artifact verifier.\n",
                self.required_certifications()
            )?;
        }

        if !self.trust_anchor_fingerprint_matches().is_empty() {
            writeln!(
                f,
                "🐾 A valid certificate must match one of the following OpenPGP fingerprints to be considered as trust anchor:"
            )?;
            for fingerprint in set_to_vec(self.trust_anchor_fingerprint_matches()).iter() {
                writeln!(f, "⤷ {fingerprint}")?;
            }
        } else {
            writeln!(
                f,
                "🐾 A valid certificate is not required to match a specific OpenPGP fingerprint to be considered as trust anchor."
            )?;
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use rstest::rstest;
    use testresult::TestResult;

    use super::*;

    /// Ensures that [`TrustAnchorMode::new`] fails if the provided number of required
    /// certifications is larger than the number of pinned trust anchors (if there are any).
    #[test]
    fn trust_anchor_mode_new_fails_on_too_many_certifications() -> TestResult {
        let result = TrustAnchorMode::new(
            NumCertifications::new(NonZeroUsize::new(5).expect("5 is larger than 0")),
            HashSet::new(),
            HashSet::from_iter([
                "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15".parse()?,
                "e242ed3bffccdf271b7fbaf34ed72d089537b42f".parse()?,
                "6eadeac2dade6347e87c0d24fd455feffa7069f0".parse()?,
            ]),
        );

        match result {
            Err(Error::Validation { .. }) => {}
            Ok(mode) => {
                panic!("Should have failed with an Error::Validation but succeeded: {mode:?}")
            }
            Err(error) => panic!(
                "Should have failed with an Error::Validation but failed with a different error: {error}"
            ),
        }
        Ok(())
    }

    /// Ensures that [`TrustAnchorMode::from_config_with_defaults`] can create a
    /// [`TrustAnchorMode`] from a [`ConfigTrustAnchorMode`] robustly.
    #[rstest]
    #[case::all_defaults(
        ConfigTrustAnchorMode::default(),
        TrustAnchorMode::default(),
        TrustAnchorMode::default()
    )]
    #[case::config_and_defaults_with_empty_lists(
        ConfigTrustAnchorMode::new(
            Some(NumCertifications::new(NonZeroUsize::new(5).expect("5 is larger than 0"))),
            Some(HashSet::new()),
            Some(HashSet::new())
        )?,
        TrustAnchorMode::new(
            NumCertifications::new(NonZeroUsize::new(6).expect("6 is larger than 0")),
            HashSet::new(),
            HashSet::new(),
        )?,
        TrustAnchorMode::new(
            NumCertifications::new(NonZeroUsize::new(5).expect("5 is larger than 0")),
            HashSet::new(),
            HashSet::new(),
        )?,
    )]
    #[case::config_and_defaults_with_filled_lists(
        ConfigTrustAnchorMode::new(
            None,
            Some(HashSet::from_iter(["example.org".parse()?, "sub.example.org".parse()?])),
            Some(HashSet::from_iter([
                "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15".parse()?,
                "e242ed3bffccdf271b7fbaf34ed72d089537b42f".parse()?,
                "6eadeac2dade6347e87c0d24fd455feffa7069f0".parse()?,
            ])),
        )?,
        TrustAnchorMode::new(
            NumCertifications::new(NonZeroUsize::new(2).expect("2 is larger than 0")),
            HashSet::from_iter(["other-example.org".parse()?, "sub.other-example.org".parse()?]),
            HashSet::from_iter([
                "d3b0f7c0b825ecbb0f0d7398072947e7b1537b6f".parse()?,
                "b787a81c32997fd39a5f4c0188363902d3586e7b".parse()?,
                "6132b58967cf1ebc05062492c17145e5ee9f82a8".parse()?,
            ]),
        )?,
        TrustAnchorMode::new(
            NumCertifications::new(NonZeroUsize::new(2).expect("2 is larger than 0")),
            HashSet::from_iter(["example.org".parse()?, "sub.example.org".parse()?]),
            HashSet::from_iter([
                "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15".parse()?,
                "e242ed3bffccdf271b7fbaf34ed72d089537b42f".parse()?,
                "6eadeac2dade6347e87c0d24fd455feffa7069f0".parse()?,
            ]),
        )?,
    )]
    #[case::config_with_empty_and_defaults_with_filled_lists(
        ConfigTrustAnchorMode::new(
            None,
            Some(HashSet::new()),
            Some(HashSet::new()),
        )?,
        TrustAnchorMode::new(
            NumCertifications::new(NonZeroUsize::new(2).expect("2 is larger than 0")),
            HashSet::from_iter(["example.org".parse()?, "sub.example.org".parse()?]),
            HashSet::from_iter([
                "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15".parse()?,
                "e242ed3bffccdf271b7fbaf34ed72d089537b42f".parse()?,
                "6eadeac2dade6347e87c0d24fd455feffa7069f0".parse()?,
            ]),
        )?,
        TrustAnchorMode::new(
            NumCertifications::new(NonZeroUsize::new(2).expect("2 is larger than 0")),
            HashSet::new(),
            HashSet::new(),
        )?,
    )]
    fn trust_anchor_mode_from_config_with_defaults(
        #[case] config: ConfigTrustAnchorMode,
        #[case] defaults: TrustAnchorMode,
        #[case] output: TrustAnchorMode,
    ) -> TestResult {
        let created = TrustAnchorMode::from_config_with_defaults(&config, &defaults)?;
        eprintln!("{}", created.required_certifications());

        assert_eq!(created, output);

        Ok(())
    }
}
