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

use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncReadExt, BufReader};

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

type Key = String;
type Value = String;

#[derive(Debug)]
pub enum RequestError {
    IO,
    Malformed,
    UnsupportedProtocol,
    UnsupportedMethod,
}

#[allow(unused)]
pub struct Request {
    reader: BufReader<Box<dyn AsyncRead + Unpin + Send>>,
    method: Method,
    path: String,
    protocol: Protocol,
    headers: HashMap<Key, Value>,
    params: HashMap<String, String>,
    query: HashMap<String, Vec<String>>,
}

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

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

        Ok(())
    }
}

#[allow(unused)]
impl Request {
    pub async fn try_from_reader<R>(reader: R) -> Result<Self, RequestError>
    where
        R: AsyncRead + Unpin + Send + 'static,
    {
        let mut reader = BufReader::new(Box::new(reader) as Box<dyn AsyncRead + Unpin + Send>);

        let mut request_line_buf = String::new();
        reader
            .read_line(&mut request_line_buf)
            .await
            .map_err(|_| RequestError::IO)?;

        let request_line: Vec<&str> = request_line_buf.split_whitespace().collect();

        let (method, path, proto) = match request_line[..] {
            [method, path, proto] => {
                let method = match method {
                    "GET" => Method::Get,
                    "POST" => Method::Post,
                    _ => return Err(RequestError::UnsupportedMethod),
                };

                let proto = match proto {
                    "HTTP/1.1" => Protocol::HTTP1_1,
                    _ => return Err(RequestError::UnsupportedProtocol),
                };

                (method, path, proto)
            }
            [] => return Err(RequestError::Malformed),
            _ => return Err(RequestError::Malformed),
        };

        let mut headers: HashMap<Key, Value> = HashMap::new();
        loop {
            let mut header_line_buf = String::new();
            reader
                .read_line(&mut header_line_buf)
                .await
                .map_err(|_| RequestError::IO)?;

            if header_line_buf.trim().is_empty() {
                break;
            }

            match header_line_buf.split_once(':') {
                Some((key, value)) => {
                    let key = key.trim().to_ascii_lowercase().to_owned();
                    let value = value.trim().to_owned();
                    headers.insert(key, value);
                }
                None => return Err(RequestError::Malformed),
            }
        }

        let query = match path.split_once('?') {
            Some((_, dirty_query)) => {
                let query_str = dirty_query
                    .split_once('#')
                    .map(|(q, _)| q)
                    .unwrap_or(dirty_query);

                let mut query: HashMap<String, Vec<String>> = HashMap::new();
                for kv_pair in query_str.split('&') {
                    if let Some((k, v)) = kv_pair.split_once('=') {
                        let current_values = query.entry(k.to_owned()).or_default();
                        current_values.push(v.to_owned());
                    }
                }

                query
            }
            None => HashMap::new(),
        };

        Ok(Request {
            reader,
            method,
            path: path.to_owned(),
            protocol: proto,
            headers,
            params: HashMap::new(),
            query,
        })
    }

    pub fn get_path(&self) -> String {
        self.path.clone()
    }

    pub fn get_method(&self) -> Method {
        self.method.clone()
    }

    pub fn get_headers(&self) -> HashMap<String, String> {
        self.headers.clone()
    }

    pub fn get_query(&self, name: &str) -> Option<Vec<String>> {
        self.query.get(name).cloned()
    }

    pub fn get_param(&self, name: &str) -> Option<String> {
        self.params.get(name).cloned()
    }

    pub fn params(mut self, p: HashMap<String, String>) -> Self {
        self.params = p;
        self
    }

    pub async fn get_raw_body(mut self) -> Result<Vec<u8>, RequestError> {
        let content_length = match self.headers.get("content-length") {
            Some(cl) => cl.parse::<usize>().map_err(|_| RequestError::Malformed)?,
            None => return Err(RequestError::Malformed),
        };

        let mut buf = vec![0; content_length];
        self.reader
            .read_exact(&mut buf)
            .await
            .map_err(|_| RequestError::IO)?;

        Ok(buf)
    }

    pub async fn get_form_body(self) -> Result<HashMap<String, String>, RequestError> {
        let raw_body = self.get_raw_body().await?;
        let mut form = HashMap::new();

        let raw_form = String::from_utf8(raw_body).map_err(|_| RequestError::Malformed)?;
        let raw_form_pairs: Vec<&str> = raw_form.split('&').collect();
        for raw_pair in raw_form_pairs {
            match raw_pair.split_once('=') {
                Some((key, value)) => {
                    form.insert(key.to_owned(), value.to_owned());
                }
                None => return Err(RequestError::Malformed),
            }
        }

        Ok(form)
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::io::Cursor;

    #[tokio::test]
    async fn can_parse_basic_request() {
        let raw = b"GET /product/create HTTP/1.1\r\nhost: localhost:8080\r\n\r\n";
        let cursor = Cursor::new(raw);

        let req = Request::try_from_reader(cursor).await.unwrap();
        assert_eq!(req.get_method(), Method::Get);
        assert_eq!(req.get_path(), "/product/create");
        assert_eq!(req.get_headers()["host"], "localhost:8080");
    }

    #[tokio::test]
    async fn can_parse_query_parameters() {
        let raw = b"GET /product/list?name=chicken&name=chicken_breast&type=meat#subtitle HTTP/1.1\r\nhost: localhost:8080\r\n\r\n";
        let cursor = Cursor::new(raw);

        let req = Request::try_from_reader(cursor).await.unwrap();
        assert_eq!(
            req.get_query("name").unwrap(),
            vec!["chicken", "chicken_breast"]
        );
        assert_eq!(req.get_query("type").unwrap(), vec!["meat"]);
    }

    #[tokio::test]
    async fn can_parse_form_data() {
        let mut req_buf: Vec<u8> = Vec::new();
        req_buf.extend_from_slice(b"GET /product/create HTTP/1.1\r\n");
        let form_data = "name=Tom&age=20";
        let header = format!("Content-Length: {}\r\n\r\n", form_data.len());
        req_buf.extend_from_slice(header.as_bytes());
        req_buf.extend_from_slice(form_data.as_bytes());
        let cursor = Cursor::new(req_buf);

        let req = Request::try_from_reader(cursor).await.unwrap();
        let form = req.get_form_body().await.unwrap();

        assert_eq!(form.get("name").unwrap(), &"Tom".to_owned());
        assert_eq!(form.get("age").unwrap(), &"20".to_owned());
    }
}
