//! Configuration file handling for OpenPGP related settings.
//!
//! All types in this module are used solely in the _configuration file_ representation.
//! They have pendants in [`crate::config::technology::openpgp`] which are used in _configuration
//! object_ representation. The difference between _configuration file_ and _configuration object_
//! representation is usually, that in the former it is allowed to have optional fields, whereas in
//! the latter all fields must be specific.

use std::collections::HashSet;

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

use crate::{
    Error,
    common::ordered_option_set,
    config::technology::openpgp::{
        DomainName,
        NumCertifications,
        NumDataSignatures,
        OpenpgpFingerprint,
        TrustAmountFlow,
        TrustAmountPartial,
        TrustAmountRoot,
    },
};

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

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

impl ConfigPlainMode {
    /// Creates a new [`ConfigPlainMode`].
    // NOTE: Used in tests.
    #[allow(dead_code)]
    pub(crate) fn new(
        identity_domain_matches: Option<HashSet<DomainName>>,
        fingerprint_matches: Option<HashSet<OpenpgpFingerprint>>,
    ) -> Self {
        Self {
            identity_domain_matches,
            fingerprint_matches,
        }
    }

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

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

/// 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 set it is
/// larger than the number of `trust_anchor_fingerprint_matches`.
fn validate_required_certifications(
    trust_anchor_fingerprint_matches: Option<&HashSet<OpenpgpFingerprint>>,
) -> impl FnOnce(&Option<NumCertifications>, &()) -> garde::Result + '_ {
    move |num_certifications, _| {
        let num_certifications = if let Some(num_certifications) = num_certifications {
            num_certifications.get().get()
        } else {
            return Ok(());
        };
        let fingerprints =
            if let Some(trust_anchor_fingerprint_matches) = trust_anchor_fingerprint_matches {
                trust_anchor_fingerprint_matches
            } else {
                return Ok(());
            };
        let num_fingerprints = fingerprints.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): {}",
                fingerprints
                    .iter()
                    .map(ToString::to_string)
                    .collect::<Vec<_>>()
                    .join(", ")
            )));
        }

        Ok(())
    }
}

/// Settings for the trust-anchor mode.
///
/// # Note
///
/// This struct _must_ be validated after creation.
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize, Validate)]
pub struct ConfigTrustAnchorMode {
    /// The number of certifications from a trust-anchor required to exist for an artifact
    /// verifier.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[garde(custom(validate_required_certifications(self.trust_anchor_fingerprint_matches.as_ref())))]
    required_certifications: Option<NumCertifications>,

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

    /// The fingerprint of an trust anchor must match one of the fingerprints.
    #[serde(
        skip_serializing_if = "Option::is_none",
        serialize_with = "ordered_option_set",
        default
    )]
    #[garde(skip)]
    trust_anchor_fingerprint_matches: Option<HashSet<OpenpgpFingerprint>>,
}

impl ConfigTrustAnchorMode {
    /// Creates a new [`ConfigTrustAnchorMode`].
    ///
    /// # Errors
    ///
    /// Returns an error if `required_certifications` is provided, but it is larger than the number
    /// of pinned trust anchor fingerprints in `trust_anchor_fingerprint_matches`.
    // NOTE: Used in tests.
    #[allow(dead_code)]
    pub fn new(
        required_certifications: Option<NumCertifications>,
        artifact_verifier_identity_domain_matches: Option<HashSet<DomainName>>,
        trust_anchor_fingerprint_matches: Option<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".to_string(),
            source,
        })?;

        Ok(mode)
    }

    /// Returns the optional [`NumCertifications`].
    pub fn required_certifications(&self) -> Option<NumCertifications> {
        self.required_certifications
    }

    /// Returns a reference to the optional set of artifact verifier [`DomainName`] entries.
    pub fn artifact_verifier_identity_domain_matches(&self) -> Option<&HashSet<DomainName>> {
        self.artifact_verifier_identity_domain_matches.as_ref()
    }

    /// Returns a reference to the optional set of trust anchor [`OpenpgpFingerprint`] entries.
    pub fn trust_anchor_fingerprint_matches(&self) -> Option<&HashSet<OpenpgpFingerprint>> {
        self.trust_anchor_fingerprint_matches.as_ref()
    }
}

/// A root for the "Web of Trust".
#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub(crate) struct ConfigWebOfTrustRoot {
    /// The fingerprint of the trust anchor that acts as "Web of Trust" root.
    fingerprint: OpenpgpFingerprint,

    /// The trust amount for the root.
    amount: Option<TrustAmountRoot>,
}

impl ConfigWebOfTrustRoot {
    /// Creates a new [`ConfigWebOfTrustRoot`].
    // NOTE: This method is used in unit tests.
    #[allow(dead_code)]
    pub(crate) fn new(fingerprint: OpenpgpFingerprint, amount: Option<TrustAmountRoot>) -> Self {
        Self {
            fingerprint,
            amount,
        }
    }

    /// Returns a reference to the [`OpenpgpFingerprint`].
    pub fn fingerprint(&self) -> &OpenpgpFingerprint {
        &self.fingerprint
    }

    /// Returns a reference to the optional [`TrustAmountRoot`].
    pub fn amount(&self) -> Option<TrustAmountRoot> {
        self.amount
    }
}

/// Settings for the "Web of Trust" model.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ConfigWebOfTrustMode {
    /// The required flow amount for authentication for a target identity.
    flow_amount: Option<TrustAmountFlow>,

    /// The numerical trust amount of a partially trusted introducer.
    partial_amount: Option<TrustAmountPartial>,

    /// The trust roots for the "Web of Trust".
    ///
    /// If a trust root defined in this list cannot be found in the set of trust anchors, a
    /// warning is emitted.
    ///
    /// # Note
    ///
    /// If this list is empty, all trust-anchors found for a specific context are considered to be
    /// trust roots.
    /// If this list is empty and no trust-anchors are found, no artifact
    /// verifiers will be trusted.
    #[serde(
        skip_serializing_if = "Option::is_none",
        serialize_with = "ordered_option_set",
        default
    )]
    roots: Option<HashSet<ConfigWebOfTrustRoot>>,

    /// The identity of an artifact verifier must match one of the domains.
    #[serde(
        skip_serializing_if = "Option::is_none",
        serialize_with = "ordered_option_set",
        default
    )]
    artifact_verifier_identity_domain_matches: Option<HashSet<DomainName>>,
}

impl ConfigWebOfTrustMode {
    /// Creates a new [`ConfigWebOfTrustMode`].
    pub(crate) fn new(
        flow_amount: Option<TrustAmountFlow>,
        partial_amount: Option<TrustAmountPartial>,
        roots: Option<HashSet<ConfigWebOfTrustRoot>>,
        artifact_verifier_identity_domain_matches: Option<HashSet<DomainName>>,
    ) -> Self {
        Self {
            flow_amount,
            partial_amount,
            roots,
            artifact_verifier_identity_domain_matches,
        }
    }

    /// Returns the optional [`TrustAmountFlow`].
    pub fn flow_amount(&self) -> Option<TrustAmountFlow> {
        self.flow_amount
    }

    /// Returns the optional [`TrustAmountPartial`].
    pub fn partial_amount(&self) -> Option<TrustAmountPartial> {
        self.partial_amount
    }

    /// Returns a reference to the list of trust roots.
    pub(crate) fn roots(&self) -> Option<&HashSet<ConfigWebOfTrustRoot>> {
        self.roots.as_ref()
    }

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

impl Default for ConfigWebOfTrustMode {
    fn default() -> Self {
        Self::new(None, None, None, None)
    }
}

/// The OpenPGP verification method to use for OpenPGP verifiers.
///
/// # Note
///
/// This enum _must_ be validated after creation.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Validate)]
#[serde(rename_all = "snake_case")]
pub(crate) enum ConfigVerificationMethod {
    /// Only use artifact verifiers, ignore trust-anchors.
    Plain(#[garde(skip)] ConfigPlainMode),

    /// Always use trust-anchors for artifact verifiers.
    TrustAnchor(#[garde(dive)] ConfigTrustAnchorMode),

    /// Use the "Web of Trust" model.
    WebOfTrust(#[garde(skip)] ConfigWebOfTrustMode),
}

impl Default for ConfigVerificationMethod {
    fn default() -> Self {
        Self::TrustAnchor(ConfigTrustAnchorMode::default())
    }
}

/// Validates that the required number of data signatures matches the verification method.
///
/// # Errors
///
/// Returns an error, if the `verification_method` is [`ConfigPlainMode`] and its number of
/// `fingerprint_matches` is fewer than that of `num_data_signatures`.
fn validate_num_data_signatures(
    verification_method: &ConfigVerificationMethod,
) -> impl FnOnce(&Option<NumDataSignatures>, &()) -> garde::Result + '_ {
    move |num_data_signatures, _| {
        let num_data_signatures = if let Some(num_data_signatures) = num_data_signatures {
            num_data_signatures.get().get()
        } else {
            return Ok(());
        };

        if let ConfigVerificationMethod::Plain(mode) = verification_method {
            let Some(fingerprint_matches) = mode.fingerprint_matches() else {
                return Ok(());
            };
            let num_fingerprints = fingerprint_matches.len();
            if num_fingerprints > 0 && num_data_signatures > num_fingerprints {
                return Err(garde::Error::new(format!(
                    "is {num_data_signatures}, but must be <= {num_fingerprints}, as the plain verification mode only pins {num_fingerprints} artifact verifier fingerprint(s): {}",
                    fingerprint_matches
                        .iter()
                        .map(ToString::to_string)
                        .collect::<Vec<_>>()
                        .join(", ")
                )));
            }
        }
        Ok(())
    }
}

/// OpenPGP settings.
///
/// # Note
///
/// This struct _must_ be validated after creation.
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize, Validate)]
pub(crate) struct ConfigOpenpgpSettings {
    /// The number of signatures required for an artifact to be considered valid.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[garde(custom(validate_num_data_signatures(&self.verification_method)))]
    num_data_signatures: Option<NumDataSignatures>,

    /// The verification method to use.
    #[garde(dive)]
    verification_method: ConfigVerificationMethod,
}

impl ConfigOpenpgpSettings {
    /// Creates a new [`ConfigOpenpgpSettings`].
    ///
    /// # Errors
    ///
    /// Returns an error if the validation of the created [`ConfigOpenpgpSettings`] fails.
    // NOTE: This is used in tests.
    #[allow(dead_code)]
    pub(crate) fn new(
        num_data_signatures: Option<NumDataSignatures>,
        verification_method: ConfigVerificationMethod,
    ) -> Result<Self, Error> {
        let settings = Self {
            num_data_signatures,
            verification_method,
        };

        settings.validate().map_err(|source| Error::Validation {
            context: "creating OpenPGP settings from file".to_string(),
            source,
        })?;

        Ok(settings)
    }

    /// Returns the optional [`NumDataSignatures`].
    pub fn num_data_signatures(&self) -> Option<NumDataSignatures> {
        self.num_data_signatures
    }

    /// Returns a reference to the [`ConfigVerificationMethod`].
    pub fn config_verification_method(&self) -> &ConfigVerificationMethod {
        &self.verification_method
    }
}
