use std::marker::PhantomData;

use http::{HeaderName, HeaderValue, Method, Uri};

use crate::{
    body::HttpBody,
    client::HttpClient,
    connection::{HttpConnection, HttpConnector},
    error::HttpError,
    response::HttpResponse,
};

pub struct HttpRequestBuilder<'c, C: HttpConnector, U: TryInto<Uri>> {
    pub(crate) client: &'c mut HttpClient<C>,
    pub(crate) url: U,
    pub(crate) builder: http::request::Builder,
}

pub(crate) struct RequestWriter<'b, C: HttpConnection> {
    pos: usize,
    buffer: &'b mut [u8],
    _pd: PhantomData<C>,
}

impl<C: HttpConnector, U: TryInto<Uri>> HttpRequestBuilder<'_, C, U> {
    pub fn header<V: TryInto<HeaderValue>>(
        mut self,
        name: HeaderName,
        value: V,
    ) -> Result<Self, HttpError<C::Error>>
    where
        http::Error: From<V::Error>,
    {
        self.builder = self.builder.header(name, value);
        Ok(self)
    }

    pub fn call<R: HttpBody>(self) -> Result<HttpResponse<C::Connection, R>, HttpError<C::Error>> {
        self.client.send(self.url, self.builder, ())
    }
}

impl<'b, C: HttpConnection> RequestWriter<'b, C> {
    pub fn new(buffer: &'b mut [u8]) -> Self {
        Self {
            buffer,
            pos: 0,
            _pd: PhantomData,
        }
    }

    pub fn write(&mut self, bytes: &[u8]) -> Result<(), HttpError<C::Error>> {
        if bytes.len() + self.pos > self.buffer.len() {
            return Err(HttpError::RequestTooLarge);
        }
        self.buffer[self.pos..self.pos + bytes.len()].copy_from_slice(bytes);
        self.pos += bytes.len();
        Ok(())
    }

    pub fn header(
        &mut self,
        key: &HeaderName,
        value: &HeaderValue,
    ) -> Result<(), HttpError<C::Error>> {
        self.write(key.as_str().as_bytes())?;
        self.write(b":")?;
        self.write(value.as_bytes())?;
        self.write(b"\r\n")?;
        Ok(())
    }

    pub fn request(&mut self, method: &Method, uri: &Uri) -> Result<(), HttpError<C::Error>> {
        let line = format!("{method} {uri} HTTP/1.1\r\n");
        self.write(line.as_bytes())
    }

    pub fn finish(mut self) -> Result<&'b [u8], HttpError<C::Error>> {
        self.write(b"\r\n")?;
        Ok(&self.buffer[..self.pos])
    }
}
