//! The "plain" OpenPGP verification method.

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

use serde::{Deserialize, Serialize};

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

/// The configuration for the "plain" verification method.
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct PlainMode {
    /// The identity of an artifact verifier must match one of the domains.
    #[serde(serialize_with = "ordered_set", default)]
    identity_domain_matches: HashSet<DomainName>,

    /// The fingerprint of an artifact verifier must match one of the fingerprints.
    #[serde(serialize_with = "ordered_set", default)]
    fingerprint_matches: HashSet<OpenpgpFingerprint>,
}

impl PlainMode {
    /// Creates a new [`PlainMode`].
    pub fn new(
        identity_domain_matches: HashSet<DomainName>,
        fingerprint_matches: HashSet<OpenpgpFingerprint>,
    ) -> Self {
        Self {
            identity_domain_matches,
            fingerprint_matches,
        }
    }

    /// Creates a new [`PlainMode`] from a [`ConfigPlainMode`] and defaults.
    pub(crate) fn from_config_with_defaults(
        config: &ConfigPlainMode,
        defaults: &PlainMode,
    ) -> Self {
        Self::new(
            config
                .identity_domain_matches()
                .unwrap_or(defaults.identity_domain_matches())
                .clone(),
            config
                .fingerprint_matches()
                .unwrap_or(defaults.fingerprint_matches())
                .clone(),
        )
    }

    /// Returns a reference to the list of [`DomainName`] entries.
    pub fn identity_domain_matches(&self) -> &HashSet<DomainName> {
        &self.identity_domain_matches
    }

    /// Returns a reference to the list of [`OpenpgpFingerprint`] entries.
    pub fn fingerprint_matches(&self) -> &HashSet<OpenpgpFingerprint> {
        &self.fingerprint_matches
    }
}

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

        if !self.identity_domain_matches().is_empty() {
            writeln!(
                f,
                "📧 A valid certificate must have a valid User ID that uses one of the following domains to be considered as artifact verifier:"
            )?;
            for domain_name in set_to_vec(self.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 to be considered as artifact verifier.\n"
            )?;
        }

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

        Ok(())
    }
}

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

    use super::*;

    #[rstest]
    #[case::some_domains_and_fingerprints(
        HashSet::from_iter(["example.org".parse()?]),
        HashSet::from_iter(["f1d2d2f924e986ac86fdf7b36c94bcdf32beec15".parse()?]),
    )]
    #[case::no_domains_and_fingerprints(HashSet::new(), HashSet::new())]
    fn plain_mode_new(
        #[case] domain_names: HashSet<DomainName>,
        #[case] fingerprints: HashSet<OpenpgpFingerprint>,
    ) -> TestResult {
        let mode = PlainMode::new(domain_names.clone(), fingerprints.clone());

        assert_eq!(mode.identity_domain_matches(), &domain_names);
        assert_eq!(mode.fingerprint_matches(), &fingerprints);

        Ok(())
    }

    #[rstest]
    #[case::all_defaults(ConfigPlainMode::default(), PlainMode::default(), PlainMode::default())]
    #[case::config_empty_lists_vs_default_defaults(
        ConfigPlainMode::new(Some(HashSet::new()), Some(HashSet::new())),
        PlainMode::default(),
        PlainMode::default()
    )]
    #[case::default_config_vs_custom_defaults(
        ConfigPlainMode::default(),
        PlainMode::new(
            HashSet::from_iter(["example.org".parse()?]),
            HashSet::from_iter(["f1d2d2f924e986ac86fdf7b36c94bcdf32beec15".parse()?]),
        ),
        PlainMode::new(
            HashSet::from_iter(["example.org".parse()?]),
            HashSet::from_iter(["f1d2d2f924e986ac86fdf7b36c94bcdf32beec15".parse()?]),
        )
    )]
    #[case::config_empty_lists_vs_custom_defaults(
        ConfigPlainMode::new(Some(HashSet::new()), Some(HashSet::new())),
        PlainMode::new(
            HashSet::from_iter(["example.org".parse()?]),
            HashSet::from_iter(["f1d2d2f924e986ac86fdf7b36c94bcdf32beec15".parse()?]),
        ),
        PlainMode::new(
            HashSet::new(),
            HashSet::new(),
        )
    )]
    #[case::custom_config_vs_custom_defaults(
        ConfigPlainMode::new(
            Some(HashSet::from_iter(["other.org".parse()?])),
            Some(HashSet::from_iter(["e242ed3bffccdf271b7fbaf34ed72d089537b42f".parse()?]))
        ),
        PlainMode::new(
            HashSet::from_iter(["example.org".parse()?]),
            HashSet::from_iter(["f1d2d2f924e986ac86fdf7b36c94bcdf32beec15".parse()?]),
        ),
        PlainMode::new(
            HashSet::from_iter(["other.org".parse()?]),
            HashSet::from_iter(["e242ed3bffccdf271b7fbaf34ed72d089537b42f".parse()?])
        )
    )]
    fn plain_mode_from_config_with_defaults(
        #[case] config: ConfigPlainMode,
        #[case] defaults: PlainMode,
        #[case] expected: PlainMode,
    ) -> TestResult {
        let mode = PlainMode::from_config_with_defaults(&config, &defaults);

        assert_eq!(mode, expected);

        Ok(())
    }
}
