//! Error handling.

use std::path::PathBuf;

use crate::OpenpgpSignature;

/// The error that can occur when using OpenPGP verifiers in VOA.
#[derive(Debug, thiserror::Error)]
pub enum Error {
    /// An error occurred during import of an OpenPGP certificate.
    #[error("Import error:\n{0}")]
    Import(#[from] crate::import::error::Error),

    /// An I/O error occurred at a path.
    #[error("I/O error at path {path} while {context}:\n{source}")]
    IoPath {
        /// The path at which the error occurred.
        path: PathBuf,

        /// The context in which the error occurred.
        ///
        /// This is meant to complete the sentence "I/O error at path XXX while ...".
        context: &'static str,

        /// The source error.
        source: std::io::Error,
    },

    /// An OpenPGP error occurred at a specific path.
    #[error("OpenPGP error at path {path:?} while {context}:\n{source}")]
    OpenPgpPath {
        /// The path at which the error occurred.
        path: PathBuf,

        /// The context in which the error occurred.
        ///
        /// This is meant to complete the sentence "OpenPGP error at path {path} while ...".
        context: &'static str,

        /// The source error.
        source: pgp::errors::Error,
    },

    /// An OpenPGP error occurred.
    #[error("OpenPGP error while {context}:\n{source}")]
    OpenPgp {
        /// The context in which the error occurred.
        ///
        /// This is meant to complete the sentence "OpenPGP error while ...".
        context: &'static str,

        /// The source error.
        source: pgp::errors::Error,
    },

    /// Encountered an OpenPGP fingerprint that is not supported in VOA.
    ///
    /// VOA currently supports v4 and v6 keys.
    #[error("Unsupported OpenPGP fingerprint shape: '{fingerprint}'")]
    UnsupportedOpenpgpFingerprint {
        /// The string representation of the unsupported OpenPGP fingerprint.
        fingerprint: String,
    },

    /// An VOA core error occurred.
    #[error("VOA core error:\n{0}")]
    VoaCore(#[from] voa_core::Error),

    /// No applicable signature verification component key.
    #[error("No applicable OpenPGP signature verification component key found in {fingerprint}")]
    NoVerificationKey {
        /// The fingerprint of the verifier in which the error occurred.
        fingerprint: String,
    },

    /// The verification of a file failed.
    #[error(
        "Verification for file {path:?} failed with {num_successful_signatures}/{num_required_signatures} successful signature verifications and {} failed verifications for the signature(s): {}",
        failed_signatures.len(),
        failed_signatures
            .iter()
            .map(
                |signature| {
                let path = if let Some(path) = signature.source() {
                    path.to_string_lossy().to_string()
                } else {
                    "-".to_string()
                };
                let issuer_fingerprints = signature
                    .detached
                    .signature
                    .issuer_fingerprint()
                    .iter()
                    .map(|fingerprint| fingerprint.to_string())
                    .collect::<Vec<_>>()
                    .join(", ");
                format!("{} {}", path, issuer_fingerprints)
            })
            .collect::<Vec<_>>()
            .join("\n"),
    )]
    FileVerificationFailed {
        /// The path to a file for which the verification failed.
        path: PathBuf,
        /// The number of required OpenPGP signatures.
        num_required_signatures: usize,
        /// The number of successful OpenPGP signatures.
        num_successful_signatures: usize,
        /// The list of failed OpenPGP signatures
        failed_signatures: Vec<OpenpgpSignature>,
    },

    /// A verification method is not yet implemented.
    #[error("The verification method {verification_method} is not yet implemented.")]
    UnimplementedVerificationMethod {
        /// The name of the verification method.
        verification_method: String,
    },
}
