// Copyright 2022 Adobe. All rights reserved.
// This file is licensed to you under the Apache License,
// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
// or the MIT license (http://opensource.org/licenses/MIT),
// at your option.

// Unless required by applicable law or agreed to in writing,
// this software is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
// specific language governing permissions and limitations under
// each license.

// #![deny(missing_docs)] (we'll turn this on once fully documented)

use thiserror::Error;

#[cfg(feature = "pdf")]
use crate::asset_handlers::pdf_io::PdfError;
use crate::{
    asset_handlers::{
        bmff_io::BmffError, flac_io::FlacError, gif_io::GifError, jpeg_io::JpegError,
        mp3_io::Mp3Error, png_io::PngError, riff_io::RiffError, svg_io::SvgError,
        tiff_io::TiffError,
    },
    crypto::{cose::CoseError, time_stamp::TimeStampError},
    http::HttpResolverError,
    ValidationResults,
};

/// `Error` enumerates errors returned by most C2PA toolkit operations.
///
/// # Stability
///
/// Prior to the 1.0 release of this SDK, this type is expected to undergo
/// non-trivial refactoring: variants may be added, removed, renamed, or have
/// their payloads reshaped between minor releases. The project's
/// [deprecation policy](https://github.com/contentauth/c2pa-rs/blob/main/docs/deprecation-policy.md)
/// is followed on a best-effort basis pre-1.0, and `Error` in particular is
/// likely to see breaking changes ahead of 1.0 as the surface area is pared
/// down. Downstream code matching on specific variants should expect churn
/// until 1.0.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
    // --- c2pa errors ---
    /// Could not find a claim with this label.
    #[error("claim missing: label = {label}")]
    ClaimMissing { label: String },

    /// An assertion could not be found at the expected URL.
    #[error("assertion missing: url = {url}")]
    AssertionMissing { url: String },

    /// The attempt to serialize the assertion (typically to JSON or CBOR) failed.
    #[error("unable to encode assertion data")]
    AssertionEncoding(String),

    #[error(transparent)]
    AssertionDecoding(#[from] crate::assertion::AssertionDecodeError),

    #[error("assertion could not be redacted")]
    AssertionInvalidRedaction,

    #[error("could not find the assertion to redact")]
    AssertionRedactionNotFound,

    #[error("assertion-specific error: {0}")]
    AssertionSpecificError(String),

    #[error("bad parameter: {0}")]
    BadParam(String),

    /// The operation was cancelled by a progress callback or cancellation token.
    #[error("operation cancelled")]
    OperationCancelled,

    #[error("validation rule was violated: {0}")]
    ValidationRule(String),

    #[error("feature implementation incomplete")]
    NotImplemented(String),

    /// The attempt to serialize the claim to CBOR failed.
    #[error("claim could not be converted to CBOR")]
    ClaimEncoding,

    /// The attempt to deserialize the claim from CBOR failed.
    #[error("claim could not be converted from CBOR")]
    ClaimDecoding(String),

    #[error("attempt to add new claim without signing last claim")]
    ClaimUnsigned,

    #[error("missing signature box link")]
    ClaimMissingSignatureBox,

    #[error("incompatible claim version")]
    ClaimVersion,

    #[error("invalid claim content")]
    ClaimInvalidContent,

    #[error("claim missing hard binding")]
    ClaimMissingHardBinding,

    #[error("claim contains multiple hard bindings")]
    ClaimMultipleHardBinding,

    #[error("claim contains self redactions")]
    ClaimSelfRedact,

    #[error("claim contains disallowed redactions")]
    ClaimDisallowedRedaction,

    #[error("update manifest is invalid")]
    UpdateManifestInvalid,

    #[error("more than one manifest store detected")]
    TooManyManifestStores,

    #[error("assertion limit exceeded: maximum allowed is {max}")]
    TooManyAssertions { max: usize },

    /// The COSE signature uses an algorithm that is not supported by this crate.
    #[error("COSE signature algorithm is not supported")]
    CoseSignatureAlgorithmNotSupported,

    /// The COSE signature did not contain a signing certificate.
    #[error("could not find signing certificate chain in COSE signature")]
    CoseX5ChainMissing,

    #[error("COSE error parsing certificate")]
    CoseInvalidCert,

    #[error("COSE signature invalid")]
    CoseSignature,

    #[error("COSE verifier failure")]
    CoseVerifier,

    /// Unable to generate a trusted time stamp.
    #[error("could not generate a trusted time stamp")]
    CoseTimeStampGeneration,

    #[error("COSE Signature too big for JUMBF box")]
    CoseSigboxTooSmall,

    #[error("COSE Signer does not contain signing certificate")]
    CoseNoCerts,

    /// Unable to generate valid JUMBF for a claim.
    #[error("could not create valid JUMBF for claim")]
    JumbfCreationError,

    #[error("thread receive error")]
    ThreadReceiveError,

    #[error("no JUMBF data found")]
    JumbfNotFound,

    #[error("required JUMBF box not found")]
    JumbfBoxNotFound,

    #[error("could not fetch the remote manifest {0}")]
    RemoteManifestFetch(String),

    #[error("must fetch remote manifests from url {0}")]
    RemoteManifestUrl(String),

    #[error("failed to remotely sign data")]
    FailedToRemoteSign,

    #[error("not found")]
    NotFound,

    #[error("type is unsupported")]
    UnsupportedType,

    #[error("thumbnail format {0} is unsupported")]
    UnsupportedThumbnailFormat(String),

    #[error("`trust.signer_info` is missing from settings")]
    MissingSignerSettings,

    #[error("`builder.auto_created_action.source_type` must be set if this feature is enabled")]
    MissingAutoCreatedActionSourceType,

    #[error("embedding error")]
    EmbeddingError,

    // Working claim errors
    #[error("ingredient file not found")]
    IngredientNotFound,

    #[error("file not found: {0}")]
    FileNotFound(String),

    #[error("resource not found: {0}")]
    ResourceNotFound(String),

    #[error("XMP read error")]
    XmpReadError(String),

    #[error("XMP write error")]
    XmpWriteError(String),

    #[error("XMP is not supported")]
    XmpNotSupported,

    #[error("C2PA provenance not found in XMP")]
    ProvenanceMissing,

    #[error("hash verification( {0} )")]
    HashMismatch(String),

    #[error("cyclic ingredient found in path: {claim_label_path:?}")]
    CyclicIngredients { claim_label_path: Vec<String> },

    #[error("claim verification failure: {0}")]
    ClaimVerification(String),

    #[error("PDF read error")]
    PdfReadError,

    #[error(transparent)]
    InvalidClaim(#[from] crate::store::InvalidClaimError),

    #[error("asset could not be parsed: {0}")]
    InvalidAsset(String),

    #[error(transparent)]
    JumbfParseError(#[from] crate::jumbf::boxes::JumbfParseError),

    #[error("The Verifiable Content structure is not valid")]
    VerifiableCredentialInvalid,

    /// Could not parse ECDSA signature. (Only appears when using WASM web crypto.)
    #[error("could not parse ECDSA signature")]
    InvalidEcdsaSignature,

    #[error("missing data box")]
    MissingDataBox,

    #[error("could not generate XML")]
    XmlWriteError,

    #[error("unknown algorithm")]
    UnknownAlgorithm,

    #[error("invalid signing key")]
    InvalidSigningKey,

    // --- third-party errors ---
    #[error(transparent)]
    Utf8Error(#[from] std::str::Utf8Error),

    #[error(transparent)]
    HttpError(#[from] http::Error),

    #[error(transparent)]
    HttpResolverError(#[from] HttpResolverError),

    #[error(transparent)]
    TryFromIntError(#[from] std::num::TryFromIntError),

    #[error(transparent)]
    IoError(#[from] std::io::Error),

    #[error(transparent)]
    JsonError(#[from] serde_json::Error),

    #[error(transparent)]
    #[cfg(feature = "add_thumbnails")]
    ImageError(#[from] image::ImageError),

    #[error(transparent)]
    CborError(#[from] c2pa_cbor::Error),

    #[error(transparent)]
    TomlSerializationError(#[from] toml::ser::Error),

    #[error(transparent)]
    OtherError(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),

    #[error("prerelease content detected")]
    PrereleaseError,

    #[error("capability is not supported by this version: {0}")]
    VersionCompatibility(String),

    #[error("insufficient memory space for operation")]
    InsufficientMemory,

    #[error(transparent)]
    TimeStampError(#[from] crate::crypto::time_stamp::TimeStampError),

    #[error(transparent)]
    RawSignatureValidationError(#[from] c2pa_raw_crypto::RawSignatureValidationError),

    #[error(transparent)]
    RawSignerError(#[from] c2pa_raw_crypto::RawSignerError),

    #[error(transparent)]
    CertificateProfileError(#[from] crate::crypto::cose::CertificateProfileError),

    #[error(transparent)]
    CertificateTrustError(#[from] crate::crypto::cose::CertificateTrustError),

    #[error(transparent)]
    InvalidCertificateError(#[from] crate::crypto::cose::InvalidCertificateError),

    /// An unexpected internal error occured while requesting the time stamp
    /// response.
    #[error("internal error ({0})")]
    InternalError(String),

    // A C2PA specific error that needs to propagate up to the caller.
    // The string should be one of the C2PA validation codes
    #[error("C2PA Validation Error: {0}")]
    C2PAValidation(String),

    #[error("manifest failed validation with: {}", .0.failure_summary())]
    InvalidManifest(ValidationResults),

    #[error("error parsing BMFF: {0}")]
    BmffError(#[from] BmffError),

    #[error("error parsing GIF: {0}")]
    GifError(#[from] GifError),

    #[error("error parsing JPEG: {0}")]
    JpegError(#[from] JpegError),

    #[error("error parsing MP3: {0}")]
    Mp3Error(#[from] Mp3Error),

    #[error("error parsing FLAC: {0}")]
    FlacError(#[from] FlacError),

    #[cfg(feature = "pdf")]
    #[error("error parsing PDF: {0}")]
    PdfError(#[from] PdfError),

    #[error("error parsing PNG: {0}")]
    PngError(#[from] PngError),

    #[error("error parsing RIFF: {0}")]
    RiffError(#[from] RiffError),

    #[error("error parsing SVG: {0}")]
    SvgError(#[from] SvgError),

    #[error("error parsing TIFF: {0}")]
    TiffError(#[from] TiffError),
}

/// A specialized `Result` type for C2PA toolkit operations.
pub type Result<T, E = Error> = std::result::Result<T, E>;

/// Implement `From<Infallible>` to support infallible conversions (like ManifestDefinition -> ManifestDefinition)
impl From<std::convert::Infallible> for Error {
    fn from(never: std::convert::Infallible) -> Self {
        match never {}
    }
}

impl From<CoseError> for Error {
    fn from(err: CoseError) -> Self {
        match err {
            CoseError::MissingSigningCertificateChain => Self::CoseX5ChainMissing,
            CoseError::MultipleSigningCertificateChains => Self::CoseVerifier,
            CoseError::NoTimeStampToken => Self::NotFound,
            CoseError::UnsupportedSigningAlgorithm => Self::CoseSignatureAlgorithmNotSupported,
            CoseError::InvalidEcdsaSignature => Self::InvalidEcdsaSignature,
            CoseError::CborParsingError(_) => Self::CoseTimeStampGeneration,
            CoseError::CborGenerationError(_) => Self::CoseTimeStampGeneration,
            CoseError::TimeStampError(e) => e.into(),
            CoseError::CertificateProfileError(e) => e.into(),
            CoseError::CertificateTrustError(e) => e.into(),
            CoseError::BoxSizeTooSmall => Self::CoseSigboxTooSmall,
            CoseError::RawSignerError(e) => e.into(),
            CoseError::RawSignatureValidationError(e) => e.into(),
            CoseError::InternalError(e) => Self::InternalError(e),
        }
    }
}

impl From<Error> for CoseError {
    fn from(err: Error) -> Self {
        match err {
            Error::CoseX5ChainMissing => Self::MissingSigningCertificateChain,
            Error::CoseVerifier => Self::MultipleSigningCertificateChains,
            Error::NotFound => Self::NoTimeStampToken,
            Error::CoseSignatureAlgorithmNotSupported => Self::UnsupportedSigningAlgorithm,
            Error::InvalidEcdsaSignature => Self::InvalidEcdsaSignature,
            Error::CoseTimeStampGeneration => Self::CborGenerationError(err.to_string()),
            Error::TimeStampError(e) => Self::TimeStampError(e),
            Error::CertificateProfileError(e) => Self::CertificateProfileError(e),
            Error::CertificateTrustError(e) => Self::CertificateTrustError(e),
            Error::CoseSigboxTooSmall => Self::BoxSizeTooSmall,
            Error::RawSignerError(e) => Self::RawSignerError(e),
            Error::RawSignatureValidationError(e) => Self::RawSignatureValidationError(e),
            _ => Self::InternalError(err.to_string()),
        }
    }
}

impl From<Error> for TimeStampError {
    fn from(err: Error) -> Self {
        // See if better mappings exist, but I doubt it.
        Self::InternalError(err.to_string())
    }
}
