use std::fmt;

#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
/// The error type of the rcgen crate
pub enum Error {
	/// The given certificate couldn't be parsed
	CouldNotParseCertificate,
	/// The given certificate signing request couldn't be parsed
	CouldNotParseCertificationRequest,
	/// The given key pair couldn't be parsed
	CouldNotParseKeyPair,
	#[cfg(feature = "x509-parser")]
	/// Invalid subject alternative name type
	InvalidNameType,
	/// An IP address was provided as a byte array, but the byte array was an invalid length.
	InvalidIpAddressOctetLength(usize),
	/// There is no support for generating
	/// keys for the given algorithm
	KeyGenerationUnavailable,
	#[cfg(feature = "x509-parser")]
	/// Unsupported extension requested in CSR
	UnsupportedExtension,
	/// The requested signature algorithm is not supported
	UnsupportedSignatureAlgorithm,
	/// Unspecified `ring` error
	RingUnspecified,
	/// The `ring` library rejected the key upon loading
	RingKeyRejected(String),
	/// The provided certificate's signature algorithm
	/// is incompatible with the given key pair
	CertificateKeyPairMismatch,
	/// Time conversion related errors
	Time,
	#[cfg(feature = "pem")]
	/// Error from the pem crate
	PemError(String),
	/// Error generated by a remote key operation
	RemoteKeyError,
	/// Unsupported field when generating a CSR
	UnsupportedInCsr,
	/// Invalid certificate revocation list (CRL) next update.
	InvalidCrlNextUpdate,
	/// CRL issuer specifies Key Usages that don't include cRLSign.
	IssuerNotCrlSigner,
}

impl fmt::Display for Error {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		use self::Error::*;
		match self {
			CouldNotParseCertificate => write!(f, "Could not parse certificate")?,
			CouldNotParseCertificationRequest => write!(
				f,
				"Could not parse certificate signing \
				request"
			)?,
			CouldNotParseKeyPair => write!(f, "Could not parse key pair")?,
			#[cfg(feature = "x509-parser")]
			InvalidNameType => write!(f, "Invalid subject alternative name type")?,
			InvalidIpAddressOctetLength(actual) => {
				write!(f, "Invalid IP address octet length of {actual} bytes")?
			},
			KeyGenerationUnavailable => write!(
				f,
				"There is no support for generating \
				keys for the given algorithm"
			)?,
			UnsupportedSignatureAlgorithm => write!(
				f,
				"The requested signature algorithm \
				is not supported"
			)?,
			#[cfg(feature = "x509-parser")]
			UnsupportedExtension => write!(f, "Unsupported extension requested in CSR")?,
			RingUnspecified => write!(f, "Unspecified ring error")?,
			RingKeyRejected(e) => write!(f, "Key rejected by ring: {}", e)?,
			CertificateKeyPairMismatch => write!(
				f,
				"The provided certificate's signature \
				algorithm is incompatible with the given key pair"
			)?,

			Time => write!(f, "Time error")?,
			RemoteKeyError => write!(f, "Remote key error")?,
			#[cfg(feature = "pem")]
			PemError(e) => write!(f, "PEM error: {}", e)?,
			UnsupportedInCsr => write!(f, "Certificate parameter unsupported in CSR")?,
			InvalidCrlNextUpdate => write!(f, "Invalid CRL next update parameter")?,
			IssuerNotCrlSigner => write!(
				f,
				"CRL issuer must specify no key usage, or key usage including cRLSign"
			)?,
		};
		Ok(())
	}
}

impl std::error::Error for Error {}

/// A trait describing an error that can be converted into an `rcgen::Error`.
///
/// We use this trait to avoid leaking external error types into the public API
/// through a `From<x> for Error` implementation.
pub(crate) trait ExternalError<T>: Sized {
	fn _err(self) -> Result<T, Error>;
}
