use std::{
    net::{SocketAddr, TcpStream},
    time::Duration,
};

use crate::connection::{
    tls::{self, TlsConnection},
    HttpConnection, HttpConnectionOptions, HttpConnector, TcpConnector, TlsConnector,
};

#[derive(Default)]
pub struct AutoConnector {
    tls: TlsConnector,
}

pub enum AutoConnection {
    Tcp(TcpStream),
    Tls(Box<TlsConnection>),
}

impl AutoConnector {
    pub fn with_tls(tls: TlsConnector) -> Self {
        Self { tls }
    }

    pub fn with_insecure_tls() -> Self {
        Self::with_tls(TlsConnector::insecure())
    }
}

impl HttpConnector for AutoConnector {
    type Error = tls::Error;
    type Connection = AutoConnection;

    fn connect(
        &mut self,
        remote: &SocketAddr,
        scheme: &str,
        server_name: &str,
        timeout: Option<Duration>,
        options: HttpConnectionOptions,
    ) -> Result<Self::Connection, Self::Error> {
        match scheme {
            "http" => TcpConnector
                .connect(remote, scheme, server_name, timeout, options)
                .map(AutoConnection::Tcp)
                .map_err(tls::Error::from),
            "https" => self
                .tls
                .connect(remote, scheme, server_name, timeout, options)
                .map(Box::new)
                .map(AutoConnection::Tls),
            _ => unreachable!(),
        }
    }

    fn default_port(&self, scheme: &str) -> u16 {
        if scheme.eq_ignore_ascii_case("http") {
            80
        } else if scheme.eq_ignore_ascii_case("https") {
            443
        } else {
            unreachable!()
        }
    }

    fn supports_scheme(&self, scheme: &str) -> bool {
        scheme.eq_ignore_ascii_case("http") || scheme.eq_ignore_ascii_case("https")
    }
}

impl HttpConnection for AutoConnection {
    type Error = tls::Error;

    fn send(&mut self, buffer: &[u8]) -> Result<usize, Self::Error> {
        match self {
            Self::Tcp(tcp) => tcp.send(buffer).map_err(tls::Error::from),
            Self::Tls(tls) => tls.send(buffer),
        }
    }

    fn send_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> {
        match self {
            Self::Tcp(tcp) => tcp.send_all(buffer).map_err(tls::Error::from),
            Self::Tls(tls) => tls.send_all(buffer),
        }
    }

    fn recv(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error> {
        match self {
            Self::Tcp(tcp) => tcp.recv(buffer).map_err(tls::Error::from),
            Self::Tls(tls) => tls.recv(buffer),
        }
    }

    fn recv_exact(&mut self, buffer: &mut [u8]) -> Result<(), Self::Error> {
        match self {
            Self::Tcp(tcp) => tcp.recv_exact(buffer).map_err(tls::Error::from),
            Self::Tls(tls) => tls.recv_exact(buffer),
        }
    }
}
