use std::{net::ToSocketAddrs, time::Duration};

use http::{header, response, HeaderName, HeaderValue, StatusCode, Uri};

use crate::{
    body::HttpBody,
    connection::{AutoConnector, HttpConnection, HttpConnectionOptions, HttpConnector},
    error::HttpError,
    request::{HttpRequestBuilder, RequestWriter},
    response::HttpResponse,
};

pub struct HttpClient<C: HttpConnector> {
    connector: C,
    options: HttpConnectionOptions,
    connect_timeout: Option<Duration>,
}

impl Default for HttpClient<AutoConnector> {
    fn default() -> Self {
        Self::with_default_options(AutoConnector::default())
    }
}

impl<C: HttpConnector> From<C> for HttpClient<C> {
    fn from(value: C) -> Self {
        Self::with_default_options(value)
    }
}

impl<C: HttpConnector> HttpClient<C> {
    pub fn with_default_options(connector: C) -> Self {
        Self {
            connector,
            options: Default::default(),
            connect_timeout: Some(Duration::from_secs(3)),
        }
    }

    pub fn request<U: TryInto<Uri>>(
        &mut self,
        method: http::Method,
        url: U,
    ) -> HttpRequestBuilder<C, U> {
        HttpRequestBuilder {
            client: self,
            url,
            builder: http::Request::builder().method(method),
        }
    }

    pub(crate) fn send<T: HttpBody, R: HttpBody, U: TryInto<Uri>>(
        &mut self,
        url: U,
        request: http::request::Builder,
        body: T,
    ) -> Result<HttpResponse<C::Connection, R>, HttpError<C::Error>> {
        let url: Uri = url.try_into().map_err(|_| HttpError::MalformedUrl)?;

        let scheme = url.scheme_str().ok_or(HttpError::MalformedUrl)?;

        if !self.connector.supports_scheme(scheme) {
            return Err(HttpError::UnsupportedScheme(scheme.into()));
        }

        let host = url.host().ok_or(HttpError::MalformedUrl)?;
        let port = url
            .port_u16()
            .unwrap_or(self.connector.default_port(scheme));

        let request = if let Some(path) = url.path_and_query() {
            request.uri(path.as_str())
        } else {
            request
        };
        let request = if let Some(content_length) = body.content_length() {
            request.header(header::CONTENT_LENGTH, content_length)
        } else {
            request
        };
        let request = if let Some(authority) = url.authority() {
            request.header(
                header::HOST,
                HeaderValue::from_str(authority.as_str()).map_err(HttpError::InvalidHeaderValue)?,
            )
        } else {
            request
        };

        let request = request.body(body).map_err(HttpError::Request)?;

        if request.version() != http::Version::HTTP_11 {
            unimplemented!()
        }

        for socket_addr in
            ToSocketAddrs::to_socket_addrs(&(host, port)).map_err(HttpError::Hostname)?
        {
            let Ok(mut connection) = self.connector.connect(
                &socket_addr,
                scheme,
                host,
                self.connect_timeout,
                self.options.clone(),
            ) else {
                continue;
            };

            send_request_http1(&mut connection, &request)?;
            return recv_response_http1(connection);
        }

        Err(HttpError::CouldNotConnect(url))
    }
}

fn send_request_http1<C: HttpConnection, T: HttpBody>(
    connection: &mut C,
    request: &http::Request<T>,
) -> Result<(), HttpError<C::Error>> {
    let mut buffer = [0; 4096];
    let mut writer = RequestWriter::<C>::new(&mut buffer);
    writer.request(request.method(), request.uri())?;
    for (name, value) in request.headers() {
        writer.header(name, value)?;
    }
    let preamble = writer.finish()?;
    connection
        .send_all(preamble)
        .map_err(HttpError::ConnectionError)?;

    request
        .body()
        .write(connection)
        .map_err(HttpError::ConnectionError)?;
    Ok(())
}

fn recv_http1_line<C: HttpConnection>(
    connection: &mut C,
    buffer: &mut [u8],
) -> Result<usize, HttpError<C::Error>> {
    let mut pos = 0;
    loop {
        if pos == buffer.len() {
            todo!()
        }
        connection
            .recv_exact(&mut buffer[pos..pos + 1])
            .map_err(HttpError::ConnectionError)?;
        match buffer[pos] {
            b'\r' => continue,
            b'\n' => break,
            _ => pos += 1,
        }
    }
    Ok(pos)
}

fn recv_response_http1<C: HttpConnection, T: HttpBody>(
    mut connection: C,
) -> Result<HttpResponse<C, T>, HttpError<C::Error>> {
    let mut buffer = [0; 512];
    let len = recv_http1_line(&mut connection, &mut buffer)?;

    let mut content_length = None;
    let mut builder = response::Builder::new();
    let status = parse_status_line_http1::<C>(&buffer[..len])?;
    builder = builder.status(status);

    loop {
        let len = recv_http1_line(&mut connection, &mut buffer)?;
        if len == 0 {
            break;
        }
        let Some(eq) = buffer.iter().position(|q| *q == b':') else {
            continue;
        };
        let (name, value) = buffer[..len].split_at(eq);
        let name = name.trim_ascii_end();
        let value = value[1..].trim_ascii_start();
        let Ok(name) = HeaderName::from_bytes(name) else {
            continue;
        };

        if name == header::CONTENT_LENGTH {
            if let Some(value) = std::str::from_utf8(value).ok().and_then(|s| s.parse().ok()) {
                content_length = Some(value);
            }
        }

        let Ok(value) = HeaderValue::from_bytes(value) else {
            continue;
        };

        builder = builder.header(name, value);
    }

    let body = T::from_response(content_length).ok_or(HttpError::BodyError)?;
    let inner = builder.body(body).map_err(HttpError::Response)?;

    Ok(HttpResponse {
        connection,
        inner,
        position: 0,
    })
}

fn parse_status_line_http1<C: HttpConnection>(
    line: &[u8],
) -> Result<StatusCode, HttpError<C::Error>> {
    let mut it = line.split(|c| *c == b' ');
    it.next().ok_or(HttpError::InvalidStatusLine)?;
    let status = it.next().ok_or(HttpError::InvalidStatusLine)?;
    let status = StatusCode::from_bytes(status).map_err(HttpError::InvalidStatusCode)?;
    Ok(status)
}
