//! OpenPGP certificate used as a verifier in VOA

use std::fmt::{Debug, Formatter};

use pgp::types::Fingerprint;
use rpgpie::{
    certificate::{Certificate, Checked},
    merge::CertificateInfo,
};
use voa_config::openpgp::OpenpgpFingerprint;
use voa_core::Verifier;

use crate::Error;

/// An OpenPGP certificate for "Verification of OS Artifacts (VOA)"
#[derive(Clone)]
pub struct OpenpgpCert {
    /// OpenPGP certificate that is synthesized from the data in `sources` below.
    ///
    /// This is a "checked" view of the certificate, which applies policy and checks validity of
    /// self-signatures.
    pub certificate: Checked,

    /// Pointers to verifier files.
    ///
    /// There may be multiple sources for one OpenPGP certificate, if multiple files contain data
    /// about one common Certificate (as defined by a shared primary key fingerprint).
    pub sources: Vec<Verifier>,
}

impl From<UncheckedOpenpgpCert> for OpenpgpCert {
    fn from(value: UncheckedOpenpgpCert) -> Self {
        let certificate = value.certificate;

        OpenpgpCert {
            certificate: Checked::new(certificate),
            sources: value.sources,
        }
    }
}

impl Debug for OpenpgpCert {
    /// The output consists of two blocks:
    ///
    /// 1) Information about the (merged) certificate: Information about the primary key, all valid
    ///    subkeys that can be used for data signatures, and all valid User IDs.
    ///
    /// 2) A list of sources that the certificate information was loaded from.
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let mut status = rpgpie::model::status_summary(&self.certificate);

        // filter out any subkeys that don't have the signing key flag
        status.subkeys.retain(|sk| {
            if let Some(flags) = &sk.key_flags {
                // TODO: the "Sign" string is not guaranteed to be stable in rpgpie
                flags.contains(&"Sign".to_string())
            } else {
                // don't keep subkeys with no key flag subpacket
                false
            }
        });

        // filter out invalid subkeys
        status.subkeys.retain(|sk| sk.status.is_valid());

        // filter out invalid user ids
        status.user_ids.retain(|uid| uid.status.is_valid());

        // Show (filtered) certificate information
        write!(f, "{}", &status)?;

        // Show information about the sources the certificate was loaded and merged from
        writeln!(f, "  Source(s):")?;
        for source in &self.sources {
            writeln!(f, "  - {:?}", source.canonicalized())?;
        }

        Ok(())
    }
}

impl OpenpgpCert {
    /// Returns the OpenPGP fingerprint of the certificate's primary key.
    ///
    /// # Errors
    ///
    /// Returns an error if an OpenPGP fingerprint format other than those supported by
    /// [`OpenpgpFingerprint`] is used.
    pub fn fingerprint(&self) -> Result<OpenpgpFingerprint, Error> {
        Ok(match self.certificate.fingerprint() {
            Fingerprint::V4(data) => OpenpgpFingerprint::V4(*data),
            Fingerprint::V6(data) => OpenpgpFingerprint::V6(*data),
            fingerprint => {
                return Err(Error::UnsupportedOpenpgpFingerprint {
                    fingerprint: fingerprint.to_string(),
                });
            }
        })
    }
}

/// Intermediate form of an OpenPGP certificate while assembling and merging inputs from VOA.
///
/// Once merging is complete, an [`OpenpgpCert`] is constructed from this type.
pub(crate) struct UncheckedOpenpgpCert {
    /// The raw OpenPGP Certificate, synthesized from the data from
    /// [`UncheckedOpenpgpCert::sources`].
    pub(crate) certificate: Certificate,

    /// Information on verifier files.
    ///
    /// There may be multiple sources for one OpenPGP certificate, if multiple files contain data
    /// about one common Certificate (as defined by a shared primary key fingerprint).
    pub(crate) sources: Vec<Verifier>,
}

impl UncheckedOpenpgpCert {
    /// Merges the information from `certificate` into [`Self::certificate`], and adds the set of
    /// `verifiers` to [`Self::sources`].
    ///
    /// By repeated calls to [`Self::merge`], an [`UncheckedOpenpgpCert`] can iteratively ingest
    /// information from an arbitrary number of certificates and act as a unified view onto the
    /// collective information in them.
    pub(crate) fn merge(&mut self, certificate: Certificate, verifiers: Vec<Verifier>) {
        // Merge the certificate contents into our certificate representation
        self.certificate
            .merge(vec![CertificateInfo::Cert(certificate)]);

        // Merge the verifier source information into our set of sources
        verifiers
            .into_iter()
            .for_each(|verifier| self.sources.push(verifier));
    }
}
