use crate::shared_enums::ContentType;
use std::{
    io::{self, BufRead, BufReader, Read, Take},
    net::TcpStream,
    str::FromStr,
};

const MAX_LINE_WIDTH: u64 = 4096; // 4 KiB
const MAX_BODY_LENGTH: u64 = 8388608; // 5 MiB
const MAX_HEADER_COUNT: u64 = 512;

pub struct Request {
    method: Method,
    http_version: String,
    path: ServerPath,
    headers: Vec<RequestHeader>,
    data: Vec<u8>,
}

pub enum RequestHeader {
    Host(String),
    UserAgent(String),
    ContentType(ContentType),
    Accept((String, String)),
    Other(String, String),
}

impl FromStr for RequestHeader {
    type Err = io::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let 
    }
}

impl Request {
    pub fn from_bufreader(buffer: BufReader<&TcpStream>) -> io::Result<Self> {
        let mut limited_buffer = buffer.take(MAX_LINE_WIDTH);

        let first_line = Self::read_line(&mut limited_buffer)?;
        let parsed_first_line = Self::parse_first_line(first_line)?;

        Ok(Self {
            method: parsed_first_line.0,
            path: parsed_first_line.1,
            http_version: parsed_first_line.2,
            headers: vec![],
            data: vec![],
        })
    }

    fn read_line(buffer: &mut Take<BufReader<&TcpStream>>) -> io::Result<String> {
        let mut read_buffer = vec![];
        buffer.set_limit(MAX_LINE_WIDTH);
        buffer.read_until(b'\n', &mut read_buffer)?;

        Ok(String::from_utf8_lossy(&read_buffer).to_string())
    }

    fn parse_first_line(line: String) -> io::Result<(Method, ServerPath, String)> {
        let splitted_line: Vec<&str> = line.split_whitespace().collect();

        match splitted_line.as_slice() {
            [method, path, "HTTP/1.1"] => Ok((
                Method::from_str(method)?,
                ServerPath::from_str(path)?,
                "HTTP/1.1".to_string(),
            )),
            _ => Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "Incorrect HTTP first line",
            )),
        }
    }
}

pub struct ServerPath {
    path: Vec<String>,
    query: Vec<(String, String)>,
}

impl FromStr for ServerPath {
    type Err = io::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.split("?").collect::<Vec<&str>>().as_slice() {
            [path, query] => {
                let mut query_hashset: std::collections::hash_set::HashSet<String> =
                    std::collections::hash_set::HashSet::new();

                let query_parsed = query
                    .split('&')
                    .filter_map(|s| match s.split('=').collect::<Vec<&str>>().as_slice() {
                        [parameter, value] => {
                            if query_hashset.contains(*parameter) {
                                None
                            } else {
                                query_hashset.insert(parameter.to_string());
                                Some((parameter.to_string(), value.to_string()))
                            }
                        }
                        _ => None,
                    })
                    .collect();

                Ok(Self {
                    path: path
                        .split('/')
                        .filter(|s| !s.is_empty() && !s.starts_with('.'))
                        .map(|s| s.to_string())
                        .collect(),
                    query: query_parsed,
                })
            }
            _ => Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid path")),
        }
    }
}

pub enum Method {
    Get,
    Post,
    Connect,
    Delete,
    Head,
    Options,
    Patch,
    Put,
    Trace,
}

impl FromStr for Method {
    type Err = io::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "GET" => Ok(Self::Get),
            "POST" => Ok(Self::Post),
            "CONNECT" => Ok(Self::Connect),
            "DELETE" => Ok(Self::Delete),
            "HEAD" => Ok(Self::Head),
            "OPTIONS" => Ok(Self::Options),
            "PATCH" => Ok(Self::Patch),
            "PUT" => Ok(Self::Put),
            "TRACE" => Ok(Self::Trace),
            _ => Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "Invalid HTTP method",
            )),
        }
    }
}
