use std::error;
use std::fmt::{self, Display, Formatter};

use http::StatusCode;
use serde::Deserialize;

#[derive(Debug)]
pub enum Error<SE, BE> {
    Deserializing(serde_json::Error),
    Service(SE),
    Body(BE),
    Twitter(TwitterErrors),
    Unexpected,
}

#[derive(Debug)]
pub struct TwitterErrors {
    pub status: StatusCode,
    pub errors: Vec<ErrorCode>,
    pub rate_limit: Option<crate::RateLimit>,
}

#[derive(Debug, Deserialize)]
pub struct ErrorCode {
    pub code: u32,
    pub message: String,
}

impl<SE: error::Error + 'static, BE: error::Error + 'static> error::Error for Error<SE, BE> {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        match *self {
            Error::Deserializing(ref e) => Some(e),
            Error::Service(ref e) => Some(e),
            Error::Body(ref e) => Some(e),
            Error::Twitter(ref e) => Some(e),
            Error::Unexpected => None,
        }
    }
}

impl<SE: Display, BE: Display> Display for Error<SE, BE> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match *self {
            Error::Deserializing(_) => f.write_str("Failed to deserialize the response body."),
            Error::Service(_) => f.write_str("HTTP error"),
            Error::Body(_) => f.write_str("Error while reading the response body"),
            Error::Twitter(_) => f.write_str("Twitter returned error(s)"),
            Error::Unexpected => f.write_str("Unexpected error occured."),
        }
    }
}

impl TwitterErrors {
    pub fn codes(&self) -> impl Iterator<Item = u32> + '_ {
        self.errors.iter().map(|e| e.code)
    }
}

impl Display for TwitterErrors {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "status: {}", self.status)?;

        let mut errors = self.errors.iter();
        if let Some(e) = errors.next() {
            write!(f, "; errors: {}", e)?;
            for e in errors {
                write!(f, ", {}", e)?;
            }
        }

        Ok(())
    }
}

impl error::Error for TwitterErrors {}

impl ErrorCode {
    pub const YOU_ARENT_ALLOWED_TO_ADD_MEMBERS_TO_THIS_LIST: u32 = 104;
    pub const CANNOT_FIND_SPECIFIED_USER: u32 = 108;
    pub const NO_STATUS_FOUND_WITH_THAT_ID: u32 = 144;
    pub const YOU_HAVE_ALREADY_RETWEETED_THIS_TWEET: u32 = 327;
}

impl Display for ErrorCode {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{} {}", self.code, self.message)
    }
}