use std::{collections::HashMap, fmt::Display};

use tokio::{
    io::{AsyncWriteExt, WriteHalf},
    net::TcpStream,
};

use crate::router::{Protocol, StatusCode};

type Key = String;
type Value = String;

#[derive(Debug)]
pub struct Response {
    status_code: StatusCode,
    protocol: Protocol,
    headers: HashMap<Key, Value>,
    body: Option<Vec<u8>>,
}

impl Display for Response {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{} {} {}\r\n",
            self.protocol,
            usize::from(self.status_code.clone()),
            self.status_code
        )?;

        for (key, value) in &self.headers {
            write!(f, "{}: {}\r\n", key, value)?;
        }

        Ok(())
    }
}

#[allow(unused)]
impl Response {
    pub fn new(status_code: StatusCode) -> Self {
        Response {
            status_code,
            protocol: Protocol::HTTP1_1,
            headers: HashMap::new(),
            body: None,
        }
    }

    pub fn new_html(html: &str) -> Self {
        let bytes = html.bytes().collect::<Vec<u8>>();
        Response::new(StatusCode::OK)
            .body(bytes)
            .header("Content-Type", "text/html")
    }

    pub fn body<B: Into<Vec<u8>>>(mut self, bytes: B) -> Self {
        let bytes_vec = bytes.into();
        self.headers
            .insert("Content-Length".to_owned(), bytes_vec.len().to_string());
        self.body = Some(bytes_vec);
        self
    }

    pub fn header(mut self, key: &str, value: &str) -> Self {
        self.headers.insert(key.to_owned(), value.to_owned());
        self
    }

    pub async fn write_to_stream(
        &self,
        stream: &mut WriteHalf<TcpStream>,
    ) -> Result<(), std::io::Error> {
        let mut response_bytes = Vec::new();

        response_bytes.extend_from_slice(
            format!(
                "{} {} {}\r\n",
                self.protocol,
                usize::from(self.status_code.clone()),
                self.status_code
            )
            .as_bytes(),
        );

        for (key, value) in &self.headers {
            response_bytes.extend_from_slice(format!("{}: {}\r\n", key, value).as_bytes());
        }

        response_bytes.extend_from_slice(b"\r\n");

        if let Some(body) = &self.body {
            response_bytes.extend_from_slice(body);
        }

        stream.write_all(&response_bytes).await?;
        stream.flush().await?;

        Ok(())
    }
}
