use base64::DecodeError as base64Error;
use std::error::Error as StdError;
use std::fmt;
use std::io::Error as ioError;
use std::sync::mpsc::RecvError as channelError;

#[derive(Debug)]
pub enum YubicoError {
    ConfigurationError(reqwest::Error),
    Network(reqwest::Error),
    HTTPStatusCode(reqwest::StatusCode),
    IOError(ioError),
    ChannelError(channelError),
    DecodeError(base64Error),
    #[cfg(feature = "online-tokio")]
    MultipleErrors(Vec<YubicoError>),
    BadOTP,
    ReplayedOTP,
    BadSignature,
    MissingParameter,
    NoSuchClient,
    OperationNotAllowed,
    BackendError,
    NotEnoughAnswers,
    ReplayedRequest,
    UnknownStatus,
    OTPMismatch,
    NonceMismatch,
    SignatureMismatch,
    InvalidKeyLength,
    InvalidResponse,
    InvalidOtp,
}

impl fmt::Display for YubicoError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            YubicoError::ConfigurationError(ref err) => write!(f, "Configuration error: {}", err),
            YubicoError::Network(ref err) => write!(f, "Connectivity error: {}", err),
            YubicoError::HTTPStatusCode(code) => write!(f, "Error found: {}", code),
            YubicoError::IOError(ref err) => write!(f, "IO error: {}", err),
            YubicoError::ChannelError(ref err) => write!(f, "Channel error: {}", err),
            YubicoError::DecodeError(ref err) => write!(f, "Decode  error: {}", err),
            #[cfg(feature = "online-tokio")]
            YubicoError::MultipleErrors(ref errs) => {
                write!(f, "Multiple errors. ")?;

                for err in errs {
                    write!(f, "{} ", err)?;
                }

                Ok(())
            }
            YubicoError::BadOTP => write!(f, "The OTP has invalid format."),
            YubicoError::ReplayedOTP => write!(f, "The OTP has already been seen by the service."),
            YubicoError::BadSignature => write!(f, "The HMAC signature verification failed."),
            YubicoError::MissingParameter => write!(f, "The request lacks a parameter."),
            YubicoError::NoSuchClient => write!(f, "The request id does not exist."),
            YubicoError::OperationNotAllowed => {
                write!(f, "The request id is not allowed to verify OTPs.")
            }
            YubicoError::BackendError => write!(
                f,
                "Unexpected error in our server. Please contact us if you see this error."
            ),
            YubicoError::NotEnoughAnswers => write!(
                f,
                "Server could not get requested number of syncs during before timeout"
            ),
            YubicoError::ReplayedRequest => {
                write!(f, "Server has seen the OTP/Nonce combination before")
            }
            YubicoError::UnknownStatus => {
                write!(f, "Unknown status sent by the OTP validation server")
            }
            YubicoError::OTPMismatch => write!(f, "OTP mismatch, It may be an attack attempt"),
            YubicoError::NonceMismatch => write!(f, "Nonce mismatch, It may be an attack attempt"),
            YubicoError::SignatureMismatch => {
                write!(f, "Signature mismatch, It may be an attack attempt")
            }
            YubicoError::InvalidKeyLength => {
                write!(f, "Invalid key length encountered while building signature")
            }
            YubicoError::InvalidResponse => {
                write!(f, "Invalid response from the validation server")
            }
            YubicoError::InvalidOtp => write!(f, "Invalid OTP"),
        }
    }
}

impl StdError for YubicoError {
    fn cause(&self) -> Option<&dyn StdError> {
        match *self {
            YubicoError::Network(ref err) => Some(err),
            YubicoError::HTTPStatusCode(_) => None,
            YubicoError::IOError(ref err) => Some(err),
            YubicoError::ChannelError(ref err) => Some(err),
            YubicoError::DecodeError(ref err) => Some(err),
            #[cfg(feature = "online-tokio")]
            YubicoError::MultipleErrors(ref errs) => match errs.first() {
                Some(err) => Some(err),
                None => None,
            },
            _ => None,
        }
    }
}

impl From<reqwest::Error> for YubicoError {
    fn from(err: reqwest::Error) -> YubicoError {
        YubicoError::Network(err)
    }
}

impl From<reqwest::StatusCode> for YubicoError {
    fn from(err: reqwest::StatusCode) -> YubicoError {
        YubicoError::HTTPStatusCode(err)
    }
}

impl From<ioError> for YubicoError {
    fn from(err: ioError) -> YubicoError {
        YubicoError::IOError(err)
    }
}

impl From<channelError> for YubicoError {
    fn from(err: channelError) -> YubicoError {
        YubicoError::ChannelError(err)
    }
}

impl From<base64Error> for YubicoError {
    fn from(err: base64Error) -> YubicoError {
        YubicoError::DecodeError(err)
    }
}
