use lz4_flex::block::DecompressError;
use std::{
    io::{self},
    string::FromUtf8Error,
};

#[derive(Debug)]
pub enum ArqError {
    Io(io::Error),
    Json(serde_json::Error),
    InvalidKeySetHeader,
    InvalidEncryptedObjectHeader,
    InvalidBoolean(u8),
    InvalidStringFlag(u8),
    InvalidUtf8String(FromUtf8Error),
    UnsupportedKeySetVersion(u32),
    InvalidKey(Vec<u8>),
    MissingDataLocs,
    BlobIdMissing,
    UnsupportedCompressionType(u32),
    DecompressError(DecompressError),
    MissingTreeLoc,
    Utf8Error(std::str::Utf8Error),
    Subtree(String),
    KeySetMacError(hmac::digest::MacError),
    InvalidCompressedData,
}

impl std::fmt::Display for ArqError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match *self {
            ArqError::Io(ref err) => write!(f, "{err}"),

            ArqError::Json(ref err) => write!(f, "{err}"),

            ArqError::InvalidKeySetHeader => f.write_str("Invalid key set header"),
            ArqError::InvalidEncryptedObjectHeader => {
                f.write_str("Invalid encrypted object header")
            }
            ArqError::InvalidBoolean(ref v) => write!(f, "Invalid value for boolean: {v}"),
            ArqError::InvalidStringFlag(ref v) => {
                write!(f, "Invalid not-null flag for string: {v}")
            }
            ArqError::UnsupportedKeySetVersion(ref v) => {
                write!(f, "Unsupported key set version: {v}")
            }
            ArqError::InvalidKey(ref v) => {
                write!(f, "Invalid encryption or hmac key: {:?}", v)
            }
            ArqError::MissingDataLocs => write!(f, "DataLocs missing for non-tree node"),
            ArqError::BlobIdMissing => write!(f, "BlobId missing"),
            ArqError::InvalidCompressedData => write!(f, "Invalid compressed data"),
            ArqError::KeySetMacError(err) => write!(f, "Incorrect encryption password ({err})"),
            ArqError::UnsupportedCompressionType(ref v) => {
                write!(f, "Unsupported compression type: {v}")
            }
            ArqError::DecompressError(ref err) => write!(f, "{err}"),
            ArqError::InvalidUtf8String(ref err) => write!(f, "{err}"),
            ArqError::Utf8Error(ref err) => write!(f, "{err}"),
            ArqError::Subtree(ref err) => write!(f, "Subtree error: {err}"),
            _ => write!(f, "{:#?}", self),
        }
    }
}

impl From<FromUtf8Error> for ArqError {
    fn from(err: FromUtf8Error) -> ArqError {
        ArqError::InvalidUtf8String(err)
    }
}

impl From<io::Error> for ArqError {
    fn from(err: io::Error) -> ArqError {
        ArqError::Io(err)
    }
}

impl From<serde_json::Error> for ArqError {
    fn from(err: serde_json::Error) -> ArqError {
        ArqError::Json(err)
    }
}

impl From<lz4_flex::block::DecompressError> for ArqError {
    fn from(err: lz4_flex::block::DecompressError) -> ArqError {
        ArqError::DecompressError(err)
    }
}

impl From<std::str::Utf8Error> for ArqError {
    fn from(err: std::str::Utf8Error) -> ArqError {
        ArqError::Utf8Error(err)
    }
}
