use std::sync::Arc;

use async_native_tls::{Identity, TlsConnector};
use async_trait::async_trait;
use bytes::Bytes;
use http::version::Version;
use http_body_util::Full;
use hyper::{body::Incoming, client::conn::http1::handshake, Request, Response};
use smol::net::TcpStream;
use smol_hyper::rt::FuturesIo;
use url::Url;

use crate::{
    cert::ClientCert,
    client::conn::http::{BaseHttpConnection, DeboaHttpConnection, Http1Request},
    errors::{ConnectionError, DeboaError},
    rt::smol::stream::SmolStream,
    Result,
};

#[async_trait]
impl DeboaHttpConnection for BaseHttpConnection<Http1Request> {
    type Sender = Http1Request;

    #[inline]
    fn url(&self) -> &Url {
        &self.url
    }

    #[inline]
    fn protocol(&self) -> Version {
        Version::HTTP_11
    }

    async fn connect(
        url: Arc<Url>,
        client_cert: &Option<ClientCert>,
    ) -> Result<BaseHttpConnection<Self::Sender>> {
        let host = url
            .host()
            .expect("uri has no host");
        let io = {
            match url.scheme() {
                "http" => {
                    let stream = {
                        let port = url
                            .port()
                            .unwrap_or(80);
                        TcpStream::connect((host.to_string(), port)).await
                    };

                    if let Err(e) = stream {
                        return Err(DeboaError::Connection(ConnectionError::Tcp {
                            host: host.to_string(),
                            message: e.to_string(),
                        }));
                    }

                    let stream = stream.unwrap();
                    SmolStream::Plain(stream)
                }
                "https" => {
                    // In case of HTTPS, establish a secure TLS connection first.
                    let stream = {
                        let port = url
                            .port()
                            .unwrap_or(443);
                        TcpStream::connect((host.to_string(), port)).await
                    };

                    if let Err(e) = stream {
                        return Err(DeboaError::Connection(ConnectionError::Tcp {
                            host: host.to_string(),
                            message: e.to_string(),
                        }));
                    }

                    let stream = stream.unwrap();
                    let connector = if let Some(client_cert) = client_cert {
                        let file = std::fs::read(client_cert.cert());
                        if let Err(e) = file {
                            return Err(DeboaError::ClientCert { message: e.to_string() });
                        }
                        let identity = Identity::from_pkcs12(&file.unwrap(), client_cert.pw());
                        if let Err(e) = identity {
                            return Err(DeboaError::ClientCert { message: e.to_string() });
                        }
                        TlsConnector::new().identity(identity.unwrap())
                    } else {
                        TlsConnector::new()
                    };

                    let stream = connector
                        .connect(host.to_string(), stream)
                        .await;

                    if let Err(e) = stream {
                        return Err(DeboaError::Connection(ConnectionError::Tls {
                            host: host.to_string(),
                            message: e.to_string(),
                        }));
                    }

                    let stream = stream.unwrap();
                    SmolStream::Tls(stream)
                }
                scheme => {
                    return Err(DeboaError::Connection(ConnectionError::UnsupportedScheme {
                        message: format!("unsupported scheme: {scheme:?}"),
                    }));
                }
            }
        };

        let result = handshake(FuturesIo::new(io)).await;

        let (sender, conn) = result.unwrap();

        smol::spawn(async move {
            match conn
                .with_upgrades()
                .await
            {
                Ok(_) => (),
                Err(_err) => {}
            };
        })
        .detach();

        Ok(BaseHttpConnection::<Http1Request> { url, sender })
    }

    async fn send_request(&mut self, request: Request<Full<Bytes>>) -> Result<Response<Incoming>> {
        let method = request
            .method()
            .to_string();
        let result = self
            .sender
            .send_request(request)
            .await;

        self.process_response(&self.url, &method, result)
            .await
    }
}

impl crate::client::conn::http::private::Sealed for BaseHttpConnection<Http1Request> {}
