use std::{fmt, io};

use actix_http::error::{HttpError, ParseError};
#[cfg(feature = "openssl")]
use actix_tls::accept::openssl::reexports::Error as OpensslError;
use derive_more::derive::{Display, From};

use crate::BoxError;

/// A set of errors that can occur while connecting to an HTTP host
#[derive(Debug, Display, From)]
#[non_exhaustive]
pub enum ConnectError {
    /// SSL feature is not enabled
    #[display("SSL is not supported")]
    SslIsNotSupported,

    /// SSL error
    #[cfg(feature = "openssl")]
    #[display("{}", _0)]
    SslError(OpensslError),

    /// Failed to resolve the hostname
    #[display("Failed resolving hostname: {}", _0)]
    Resolver(Box<dyn std::error::Error>),

    /// No dns records
    #[display("No DNS records found for the input")]
    NoRecords,

    /// Http2 error
    #[display("{}", _0)]
    H2(h2::Error),

    /// Connecting took too long
    #[display("Timeout while establishing connection")]
    Timeout,

    /// Connector has been disconnected
    #[display("Internal error: connector has been disconnected")]
    Disconnected,

    /// Unresolved host name
    #[display("Connector received `Connect` method with unresolved host")]
    Unresolved,

    /// Connection io error
    #[display("{}", _0)]
    Io(io::Error),
}

impl std::error::Error for ConnectError {}

impl From<actix_tls::connect::ConnectError> for ConnectError {
    fn from(err: actix_tls::connect::ConnectError) -> ConnectError {
        match err {
            actix_tls::connect::ConnectError::Resolver(err) => ConnectError::Resolver(err),
            actix_tls::connect::ConnectError::NoRecords => ConnectError::NoRecords,
            actix_tls::connect::ConnectError::InvalidInput => panic!(),
            actix_tls::connect::ConnectError::Unresolved => ConnectError::Unresolved,
            actix_tls::connect::ConnectError::Io(err) => ConnectError::Io(err),
        }
    }
}

#[derive(Debug, Display, From)]
#[non_exhaustive]
pub enum InvalidUrl {
    #[display("Missing URL scheme")]
    MissingScheme,

    #[display("Unknown URL scheme")]
    UnknownScheme,

    #[display("Missing host name")]
    MissingHost,

    #[display("URL parse error: {}", _0)]
    HttpError(http::Error),
}

impl std::error::Error for InvalidUrl {}

/// A set of errors that can occur during request sending and response reading
#[derive(Debug, Display, From)]
#[non_exhaustive]
pub enum SendRequestError {
    /// Invalid URL
    #[display("Invalid URL: {}", _0)]
    Url(InvalidUrl),

    /// Failed to connect to host
    #[display("Failed to connect to host: {}", _0)]
    Connect(ConnectError),

    /// Error sending request
    Send(io::Error),

    /// Error parsing response
    Response(ParseError),

    /// Http error
    #[display("{}", _0)]
    Http(HttpError),

    /// Http2 error
    #[display("{}", _0)]
    H2(h2::Error),

    /// Response took too long
    #[display("Timeout while waiting for response")]
    Timeout,

    /// Tunnels are not supported for HTTP/2 connection
    #[display("Tunnels are not supported for http2 connection")]
    TunnelNotSupported,

    /// Error sending request body
    Body(BoxError),

    /// Other errors that can occur after submitting a request.
    #[display("{:?}: {}", _1, _0)]
    Custom(BoxError, Box<dyn fmt::Debug>),
}

impl std::error::Error for SendRequestError {}

/// A set of errors that can occur during freezing a request
#[derive(Debug, Display, From)]
#[non_exhaustive]
pub enum FreezeRequestError {
    /// Invalid URL
    #[display("Invalid URL: {}", _0)]
    Url(InvalidUrl),

    /// HTTP error
    #[display("{}", _0)]
    Http(HttpError),

    /// Other errors that can occur after submitting a request.
    #[display("{:?}: {}", _1, _0)]
    Custom(BoxError, Box<dyn fmt::Debug>),
}

impl std::error::Error for FreezeRequestError {}

impl From<FreezeRequestError> for SendRequestError {
    fn from(err: FreezeRequestError) -> Self {
        match err {
            FreezeRequestError::Url(err) => err.into(),
            FreezeRequestError::Http(err) => err.into(),
            FreezeRequestError::Custom(err, msg) => SendRequestError::Custom(err, msg),
        }
    }
}