use std::{
    convert::Infallible,
    fmt::{Debug, Display},
};

use cipherstash_core::string::OrderiseStringError;
use hex::FromHexError;
use hmac::digest::InvalidLength;
use miette::Diagnostic;
use ore_rs::OreError;
use static_assertions::assert_impl_all;
use thiserror::Error;

use crate::{ejsonpath, zerokms};

use super::{compound_indexer::accumulator::AccumulatorError, plaintext::Plaintext};

#[derive(Debug, Error, Diagnostic)]
pub enum EncryptionError {
    #[error(transparent)]
    EncodingError(#[from] FromHexError),

    #[error(transparent)]
    SerDeError(#[from] serde_cbor::Error),

    #[error(transparent)]
    #[diagnostic(transparent)]
    ZeroKMS(#[from] zerokms::Error),

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

    #[error("Unable to index value: `{0}`")]
    IndexingError(String),

    #[error("Unable to create index: `{0}`")]
    CreateIndexError(String),

    #[error(transparent)]
    InvalidUniqueKey(#[from] InvalidLength),

    #[error("ORE Error: {0}")]
    OreError(#[from] OreError),

    #[error("Orderise string error: {0}")]
    OrderiseStringError(#[from] OrderiseStringError),

    #[error("Too many arguments provided")]
    TooManyArguments,

    #[error("Too few arguments provided")]
    TooFewArguments,

    #[error("AccumulatorError: {0}")]
    AccumulatorError(#[from] AccumulatorError),

    #[error("Invalid value: {0}")]
    InvalidValue(String),

    #[error(transparent)]
    CllwOre(#[from] cllw_ore::Error),

    #[error(transparent)]
    SelectorParseError(#[from] ejsonpath::ParseError),

    /// Convenience to keep the compiler happy for when internal errors are not used
    #[error("Infallible")]
    Infallible,
}

impl From<Infallible> for EncryptionError {
    fn from(_: Infallible) -> Self {
        EncryptionError::Infallible
    }
}

// Make sure that encryption errors can be sent between threads so encryptions can be run on
// different threads
assert_impl_all!(EncryptionError: Send, Sync);

#[derive(Debug, Error)]
pub struct TypeParseError(pub String);

impl TypeParseError {
    pub(crate) fn make(value: &[u8], variant: u8) -> Self {
        let target = Plaintext::variant_name(variant);
        Self(format!(
            "Unable to parse value, {value:?} into type {target:?}"
        ))
    }
}

impl Display for TypeParseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&self.0)
    }
}
