#![cfg_attr(nightly, feature(error_generic_member_access))]
use std::{
    any::Any,
    borrow::Cow,
    fmt::{self, Debug, Display},
    str::FromStr,
};
use mdsn::DsnError;
use source::Inner;
use thiserror::Error;
mod code;
mod source;
pub use code::Code;
#[derive(Error)]
#[must_use]
pub struct Error {
    code: Code,
    context: Option<String>,
    #[cfg_attr(nightly, backtrace)]
    source: Inner,
}
unsafe impl Send for Error {}
unsafe impl Sync for Error {}
impl Debug for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if f.alternate() {
            f.debug_struct("Error")
                .field("code", &self.code)
                .field("context", &self.context)
                .field("source", &self.source)
                .finish()
        } else {
            if self.code != Code::FAILED {
                write!(f, "[{:#06X}] ", self.code)?;
            }
            if let Some(context) = &self.context {
                f.write_fmt(format_args!("{}", context))?;
                writeln!(f)?;
                writeln!(f)?;
                writeln!(f, "Caused by:")?;
                let chain = self.source.chain();
                for (idx, source) in chain.enumerate() {
                    writeln!(f, "{:4}: {}", idx, source)?;
                }
            } else {
                let mut chain = self.source.chain();
                if let Some(context) = chain.next() {
                    f.write_fmt(format_args!("{}", context))?;
                }
                if self.source.deep() {
                    writeln!(f)?;
                    writeln!(f)?;
                    writeln!(f, "Caused by:")?;
                    for (idx, source) in chain.enumerate() {
                        writeln!(f, "{:4}: {}", idx, source)?;
                    }
                }
            }
            #[cfg(nightly)]
            {
                writeln!(f)?;
                writeln!(f, "Backtrace:")?;
                writeln!(f, "{}", self.source.backtrace())?;
            }
            Ok(())
        }
    }
}
impl Display for Error {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.code != Code::FAILED {
            write!(f, "[{:#06X}] ", self.code)?;
        }
        if let Some(context) = self.context.as_deref() {
            write!(f, "{}", context)?;
            if self.source.is_empty() {
                return Ok(());
            }
            f.write_str(": ")?;
        } else if self.source.is_empty() {
            return f.write_str("Unknown error");
        }
        if f.alternate() {
            write!(f, "{:#}", self.source)?;
        } else {
            write!(f, "{}", self.source)?;
        }
        Ok(())
    }
}
impl From<DsnError> for Error {
    fn from(dsn: DsnError) -> Self {
        Self::new(Code::FAILED, dsn.to_string())
    }
}
impl From<anyhow::Error> for Error {
    fn from(error: anyhow::Error) -> Self {
        Self {
            code: Code::FAILED,
            context: None,
            source: Inner::any(error),
        }
    }
}
impl<C: Into<Code>> From<C> for Error {
    fn from(value: C) -> Self {
        Self::from_code(value.into())
    }
}
impl<'a> From<&'a str> for Error {
    fn from(value: &'a str) -> Self {
        Self::from_string(value.to_string())
    }
}
pub type Result<T> = std::result::Result<T, Error>;
impl Error {
    #[inline(always)]
    pub fn new_with_context(
        code: impl Into<Code>,
        err: impl Display,
        context: impl Display,
    ) -> Self {
        Self {
            code: code.into(),
            context: Some(context.to_string()),
            source: err.to_string().into(),
        }
    }
    #[inline]
    pub fn new(code: impl Into<Code>, err: impl Into<String>) -> Self {
        Self {
            code: code.into(),
            context: None,
            source: err.into().into(),
        }
    }
    #[inline]
    pub fn context(mut self, context: impl Display) -> Self {
        self.context = Some(match self.context {
            Some(pre) => format!("{}: {}", context, pre),
            None => format!("{}", context),
        });
        self
    }
    #[inline]
    #[deprecated = "Use self.code() instead"]
    pub fn errno(&self) -> Code {
        self.code
    }
    #[inline]
    pub const fn code(&self) -> Code {
        self.code
    }
    #[inline]
    pub fn message(&self) -> String {
        self.source.to_string()
    }
    #[inline(always)]
    pub fn from_code(code: impl Into<Code>) -> Self {
        let code = code.into();
        if let Some(str) = code._priv_err_str() {
            Self::new(code, str)
        } else {
            Self {
                code,
                context: None,
                source: Inner::empty(),
            }
        }
    }
    #[inline]
    pub fn from_string(err: impl Into<Cow<'static, str>>) -> Self {
        anyhow::format_err!("{}", err.into()).into()
    }
    #[inline]
    pub fn from_any(err: impl Into<anyhow::Error>) -> Self {
        err.into().into()
    }
    #[inline]
    pub fn any(err: impl Into<anyhow::Error> + 'static) -> Self {
        if err.type_id() == std::any::TypeId::of::<Self>() {
            let err = &err as &dyn Any;
            let err = err.downcast_ref::<Self>().unwrap();
            dbg!(err);
            return Self {
                code: err.code,
                context: err.context.clone(),
                source: err.source.clone(),
            };
        }
        err.into().into()
    }
    #[inline]
    pub fn success(&self) -> bool {
        self.code == 0
    }
}
#[macro_export]
macro_rules! format_err {
    (code = $c:expr, raw = $arg:expr, context = $arg2:expr) => {
        $crate::Error::new_with_context($c, $arg, $arg2)
    };
    (code = $c:expr, raw = $arg:expr) => {
        $crate::Error::new($c, $arg)
    };
    (code = $c:expr, raw = ($($arg:tt)*), context = ($($arg2:tt)*) $(,)?) => {
        $crate::Error::new_with_context($c, __priv_format!($($arg)*), __priv_format!($($arg2)*))
    };
    (code = $c:expr, context = $($arg2:tt)*) => {
        $crate::Error::from($c).context(format!($($arg2)*))
    };
    (code = $c:expr, raw = $arg:literal, context = $($arg2:tt)*) => {
        $crate::Error::new_with_context($c, format!($arg), $crate::__priv_format!($($arg2)*))
    };
    (code = $c:expr, raw = $arg:ident, context = $($arg2:tt)*) => {
        $crate::Error::new_with_context($c, $arg, $crate::__priv_format!($($arg2)*))
    };
    (code = $c:expr) => {
        $crate::Error::from_code($c)
    };
    (code = $c:expr, raw = $($arg:tt)*) => {
        $crate::Error::new($c, format!($($arg)*))
    };
    (code = $c:expr, $($arg:tt)*) => {
        $crate::Error::new($c, format!($($arg)*))
    };
    (any = $($arg:tt)*) => {
        $crate::Error::from_string(format!($($arg)*))
    };
    (raw = $($arg:tt)*) => {
        compile_error!("`raw` error message must be used along with an error code!")
    };
    ($c:expr, raw = $arg:expr) => {
        $crate::Error::new($c, $arg)
    };
    ($c:expr, raw = $arg:expr, context = $arg2:expr) => {
        $crate::Error::new_with_context($c, $arg, $arg2)
    };
    ($c:expr, raw = ($($arg:tt)*), context = ($($arg2:tt)*) $(,)?) => {
        $crate::Error::new_with_context($c, format!($($arg)*), format!($($arg2)*))
    };
    ($c:expr, context = $arg:expr) => {
        $crate::Error::from($c).context($arg)
    };
    ($c:expr, context = $($arg2:tt)*) => {
        $crate::Error::from($c).context(format!($($arg2)*))
    };
    ($c:expr, raw = $($arg:tt)*) => {
        $crate::Error::new($c, format!($($arg)*))
    };
    ($c:expr) => {
        $crate::Error::from($c)
    };
    ($($arg:tt)*) => {
        $crate::Error::from_string(format!($($arg)*))
    };
}
#[macro_export]
macro_rules! __priv_format {
    ($msg:literal $(,)?) => {
        literal.to_string()
    };
    ($err:expr $(,)?) => {
        $err
    };
    ($fmt:expr, $($arg:tt)*) => {
        format!($fmt, $($arg)*)
    };
}
#[macro_export]
macro_rules! bail {
    ($($arg:tt)*) => {
        return std::result::Result::Err($crate::format_err!($($arg)*))
    };
}
impl FromStr for Error {
    type Err = ();
    #[inline]
    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        Ok(Self::from_string(s.to_string()))
    }
}
#[cfg(feature = "serde")]
impl serde::de::Error for Error {
    #[inline]
    fn custom<T: fmt::Display>(msg: T) -> Error {
        Error::from_string(format!("{}", msg))
    }
}
#[test]
fn test_format_err() {
    let code = 0xF000;
    let raw = "Nothing";
    let context = "Error";
    let err = dbg!(format_err!(code, raw = raw, context = context));
    assert_eq!(err.to_string(), "[0xF000] Error: Internal error: `Nothing`");
    let err = dbg!(format_err!(code, raw = raw));
    assert_eq!(err.to_string(), "[0xF000] Internal error: `Nothing`");
    let err = dbg!(format_err!(code, context = context));
    assert_eq!(err.to_string(), "[0xF000] Error");
    let err = dbg!(format_err!(code = 0xF000, context = "Error here"));
    assert_eq!(err.to_string(), "[0xF000] Error here");
    let err = dbg!(format_err!(0x6789, context = "Error here: {}", 1));
    assert_eq!(err.to_string(), "[0x6789] Error here: 1");
    let err = dbg!(format_err!(code = 0x6789, context = "Error here: {}", 1));
    assert_eq!(err.to_string(), "[0x6789] Error here: 1");
    let err = dbg!(format_err!(code = 0x6789, raw = "Error here: {}", 1));
    assert_eq!(err.to_string(), "[0x6789] Internal error: `Error here: 1`");
    let err = dbg!(format_err!(0x6789, raw = "Error here: {}", 1));
    assert_eq!(err.to_string(), "[0x6789] Internal error: `Error here: 1`");
    let err = dbg!(format_err!(
        code = 0x6789,
        raw = ("Error here: {}", 1),
        context = ("Query error with {:?}", "sql"),
    ));
    assert_eq!(
        err.to_string(),
        "[0x6789] Query error with \"sql\": Internal error: `Error here: 1`"
    );
    let err = dbg!(format_err!("Error here"));
    assert_eq!(err.to_string(), "Error here");
    let err = dbg!(format_err!(0x2603));
    assert_eq!(
        err.to_string(),
        "[0x2603] Internal error: `Table does not exist`"
    );
    let err = dbg!(format_err!(0x6789));
    assert_eq!(err.to_string(), "[0x6789] Unknown error");
    let err = dbg!(format_err!(0x6789, context = "Error here"));
    assert_eq!(err.to_string(), "[0x6789] Error here");
}
#[test]
fn test_bail() {
    fn use_bail() -> Result<()> {
        bail!(code = 0x2603, context = "Failed to insert into table `abc`");
    }
    let err = use_bail();
    dbg!(&err);
    assert!(err.is_err());
    println!("{:?}", err.unwrap_err());
    println!("{:?}", Error::any(use_bail().unwrap_err()));
}
#[test]
fn test_display() {
    let err = Error::new(Code::SUCCESS, "Success").context("nothing");
    assert!(dbg!(format!("{}", err)).contains("[0x0000] nothing"));
    let result = std::panic::catch_unwind(|| {
        let err = Error::new(Code::SUCCESS, "Success").context("nothing");
        panic!("{:?}", err);
    });
    assert!(result.is_err());
}
#[test]
fn test_error() {
    let err = Error::new(Code::SUCCESS, "success");
    assert_eq!(err.code(), Code::SUCCESS);
    assert_eq!(err.message(), "Internal error: `success`");
    let _ = Error::from_code(1);
    assert_eq!(Error::from_string("any").to_string(), "any");
    assert_eq!(Error::from_string("any").to_string(), "any");
    fn raise_error() -> Result<()> {
        Err(Error::from_any(DsnError::InvalidDriver("mq".to_string())))
    }
    assert_eq!(raise_error().unwrap_err().to_string(), "invalid driver mq");
}
#[cfg(feature = "serde")]
#[test]
fn test_serde_error() {
    use serde::de::Error as DeError;
    let _ = Error::custom("");
}