use std::{
    env::current_dir,
    ffi::OsStr,
    fs::{self, DirEntry, read},
    io::{self, BufRead, BufReader, Read, Write},
    net::{TcpListener, TcpStream},
    path::PathBuf,
};

static CLRF: &str = "\r\n";

fn main() {
    let listener =
        TcpListener::bind("0.0.0.0:8080").expect("Failed to create TcpListener on 0.0.0.0:8080");

    for stream in listener.incoming() {
        let mut stream = stream.unwrap(); // safe because TcpListener::incoming never returns None.

        handle_connection(stream);
    }
}

#[derive(Debug)]
enum RequestMethod {
    GET,
    POST,
}
#[derive(Debug)]
struct Request {
    method: RequestMethod,
    path: Option<String>,
}
impl Request {
    pub fn from(string: &str) -> Option<Self> {
        let mut parts = string.split(" ");

        Some(Self {
            method: match parts.next().expect("Invalid request") {
                "GET" => RequestMethod::GET,
                "POST" => RequestMethod::POST,
                _ => None?
            },
            path: Some(
                parts
                    .next()
                    .expect("Invalid request")
                    .split("?")
                    .next()
                    .unwrap()
                    .to_string(),
            ),
        })
    }
    pub fn file(&self) -> Option<PathBuf> {
        if self.path == None {
            return None;
        }

        let mut path = PathBuf::new();
        path.push("public");

        path.push(
            PathBuf::from(
                urlencoding::decode(self.path.as_ref().unwrap())
                    .unwrap()
                    .to_string(),
            )
            .strip_prefix("/")
            .expect("Failed to strip prefix"),
        );

        if path.extension() == None {
            path.push("index.html");
        }

        let mut cwd = current_dir().expect("Failed to get CWD");
        cwd.push(path);
        Some(cwd)
    }
    pub fn get_path(&self) -> Option<String> {
        self.path.clone()
    }
}

fn get_mime_type<'a>(extension: Option<&'a OsStr>) -> String {
    if extension.is_none() {
        return "text/text".to_string();
    }

    let s = extension.unwrap().to_str();
    match s {
        Some("js") => "text/javascript".to_string(),
        Some("md") => "text/markdown".to_string(),
        Some("css") => "text/css".to_string(),
        Some("html") => "text/html".to_string(),
        Some("png") => "image/png".to_string(),
        Some("ico") => "image/vnd.microsoft.icon".to_string(),
        _ => "text/text".to_string(),
    }
}

fn handle_connection(mut stream: TcpStream) {
    let buf_reader = BufReader::new(&stream);
    let http_request: Vec<_> = buf_reader
        .lines()
        .map(|result| result.unwrap())
        .take_while(|line| !line.is_empty())
        .collect();

    println!("Request: {http_request:#?}");

    if http_request.len() == 0 {
        return;
    }

    let req = Request::from(http_request.get(0).expect("Empty request"));

    if req.is_none() {
        return;
    };

    let req = req.unwrap();

    if req.path == Some("/posts/index".to_string()) {
        stream
            .write_all(
                {
                    match fs::read_dir("./public/posts/") {
                        Ok(files) => {
                            let mut entries = files
                                .filter_map(|x| if x.is_ok() { Some(x.unwrap()) } else { None })
                                .filter(|x| {
                                    let file_name = x.file_name();
                                    let y = file_name.to_str();
                                    let ft = x.file_type();
                                    if y.is_some() {
                                        if y.unwrap().starts_with("_") {
                                            false
                                        } else if ft.is_ok() && ft.unwrap().is_dir() {
                                            true
                                        } else if y.unwrap().ends_with(".md") {
                                            true
                                        } else {
                                            false
                                        }
                                    } else {
                                        true
                                    }
                                })
                                .collect::<Vec<DirEntry>>();
                            entries.sort_by(|a, b| {
                                eprintln!("{a:?} {b:?}");
                                let path = if a.file_type().unwrap().is_dir() { 
                                    let mut path = a.path().clone();
                                    path.push("post.md");
                                    path 
                                } else { 
                                    a.path().clone()
                                };
                                let mut a = if let Ok(a) = fs::File::open(path) {
                                    a
                                } else {
                                    eprintln!("Error when opening a, returning equal");
                                    return std::cmp::Ordering::Equal;
                                };
                                let path = if b.file_type().unwrap().is_dir() { 
                                    let mut path = b.path().clone();
                                    path.push("post.md");
                                    path 
                                } else { 
                                    b.path().clone()
                                };
                                let mut b = if let Ok(b) = fs::File::open(path) {
                                    b
                                } else {
                                    let mut path = b.path().clone();
                                    path.push("post.md");
                                    if let Ok(b) = fs::File::open(path) {
                                        b
                                    } else {
                                        eprintln!("Error when opening b, returning equal");
                                        return std::cmp::Ordering::Equal;
                                    }
                                };
                                eprintln!("{a:?} {b:?}");

                                let mut buf: &mut [u8] = &mut [0; 16];
                                let _ = a.read(&mut buf);

                                let a = String::from_utf8_lossy(buf);

                                let buf: &mut [u8] = &mut [0; 16];
                                let _ = b.read(&mut *buf);
                                let b = String::from_utf8_lossy(buf);

                                println!("{a} {b}");

                                let a = if let Ok(a) = a
                                    .chars()
                                    .take_while(|x| x != &'\n')
                                    .collect::<String>()
                                    .parse::<u32>()
                                {
                                    a
                                } else {
                                    eprintln!("Error when reading a, returning equal");
                                    return std::cmp::Ordering::Equal;
                                };

                                let b = if let Ok(b) = b
                                    .chars()
                                    .take_while(|x| x != &'\n')
                                    .collect::<String>()
                                    .parse::<u32>()
                                {
                                    b
                                } else {
                                    eprintln!("Error when reading b, returning equal");
                                    return std::cmp::Ordering::Equal;
                                };

                                return a.cmp(&b);
                            });
                            let data = entries
                                .into_iter()
                                .filter_map(|x| {
                                    if let Some(n) = x.file_name().to_str() {
                                        if n.ends_with(".md") {
                                            Some(n.to_owned().replace(".md", ""))
                                        } else {
                                            Some(n.to_owned())
                                        }
                                    } else {
                                        None
                                    }
                                })
                                .reduce(|a, b| format!("{}\n{}", b, a))
                                .unwrap_or("".to_owned());
                            format!("HTTP/1.1 200 OK\r\nContent-Type: text/text\r\nContent-Length: {}\r\n\r\n{}", data.len(), data)
                        }
                        Err(e) => {
                            eprintln!("\x1b[0;31mError when fetching list of posts: {e:?}\x1b[0m");
                            format!("HTTP/1.1 500\r\n\r\n{}", e)
                        },
                    }
                }
                .as_bytes(),
            )
            .unwrap();
        return;
    }

    println!("Requested file: {:#?}", req.file());

    let mut response_content = vec![];

    let response = match req.file() {
        Some(path) => match read(&path) {
            Ok(content) => {
                let len = &content.len();
                response_content = content;
                format!(
                    "HTTP/1.1 200 OK{0}Content-Type: {2}{0}Content-Length: {1}{0}{0}",
                    CLRF,
                    len,
                    get_mime_type(path.extension())
                )
            }
            Err(e) => match e.kind() {
                io::ErrorKind::NotFound => format!(
                    "HTTP/1.1 404\r\n\r\n{:?} not found",
                    req.get_path().unwrap()
                ),
                io::ErrorKind::PermissionDenied => format!(
                    "HTTP/1.1 403\r\n\r\n{:?} not readable",
                    req.get_path().unwrap()
                ),
                io::ErrorKind::FileTooLarge => format!(
                    "HTTP/1.1 413\r\n\r\n{:?} was too large",
                    req.get_path().unwrap()
                ),
                _ => format!("HTTP/1.1 500{0}{0}{e}", CLRF),
            },
        },
        None => format!("HTTP/1.1 404{0}{0}", CLRF),
    };

    let _ = stream.write_all(response.as_bytes());
    let _ = stream.write_all(&response_content);
}
