use std::{
    io::{self, Read, Write},
    net::{SocketAddr, TcpStream},
    sync::Arc,
    time::Duration,
};

use rustls::{pki_types::ServerName, ClientConfig, ClientConnection, StreamOwned};

use crate::connection::{HttpConnection, HttpConnectionOptions, HttpConnector, TcpConnector};

pub struct TlsConnector {
    config: Arc<ClientConfig>,
}

struct TcpStreamWrapper(TcpStream);

pub struct TlsConnection {
    stream: StreamOwned<ClientConnection, TcpStreamWrapper>,
}

#[derive(Debug, thiserror::Error)]
#[error("{0}")]
pub enum Error {
    Io(#[from] io::Error),
    Tls(#[from] rustls::Error),
    ServerName(#[from] rustls::pki_types::InvalidDnsNameError),
}

impl TlsConnector {
    pub fn from_rustls_config(config: impl Into<Arc<ClientConfig>>) -> Self {
        Self {
            config: config.into(),
        }
    }

    pub fn insecure() -> Self {
        Self::from_rustls_config(cryptic::rustls_insecure_client().with_no_client_auth())
    }
}

impl Default for TlsConnector {
    fn default() -> Self {
        Self::from_rustls_config(cryptic::rustls_default_client().with_no_client_auth())
    }
}

impl HttpConnector for TlsConnector {
    type Error = Error;
    type Connection = TlsConnection;

    fn connect(
        &mut self,
        remote: &SocketAddr,
        scheme: &str,
        server_name: &str,
        timeout: Option<Duration>,
        options: HttpConnectionOptions,
    ) -> Result<Self::Connection, Self::Error> {
        let tls_server_name = ServerName::try_from(server_name.to_owned())?;
        let connection = ClientConnection::new(self.config.clone(), tls_server_name)?;
        let socket = TcpConnector.connect(remote, scheme, server_name, timeout, options)?;
        let stream = StreamOwned::new(connection, TcpStreamWrapper(socket));
        Ok(TlsConnection { stream })
    }

    fn default_port(&self, scheme: &str) -> u16 {
        let _ = scheme;
        443
    }

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

impl io::Write for TcpStreamWrapper {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.0.write(buf)
    }

    // FIXME: this is a dumb implementation of write_vectored
    fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
        let mut count = 0;
        for buf in bufs {
            let len = self.0.write(&buf[..])?;
            count += len;
            if len < buf.len() {
                break;
            }
        }
        Ok(count)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.0.flush()
    }
}

impl io::Read for TcpStreamWrapper {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        self.0.read(buf)
    }

    fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
        if bufs.len() > 1 {
            todo!("This is a notification for myself to implement read_vectored on sockets");
        }
        self.read(&mut bufs[0][..])
    }
}

impl HttpConnection for TlsConnection {
    type Error = Error;

    fn send(&mut self, buffer: &[u8]) -> Result<usize, Self::Error> {
        self.stream.write(buffer).map_err(Error::from)
    }

    fn send_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> {
        self.stream.write_all(buffer).map_err(Error::from)
    }

    fn recv(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error> {
        self.stream.read(buffer).map_err(Error::from)
    }

    fn recv_exact(&mut self, buffer: &mut [u8]) -> Result<(), Self::Error> {
        self.stream.read_exact(buffer).map_err(Error::from)
    }
}
