use std::collections::HashMap;
#[cfg(not(target_arch = "wasm32"))]
use std::io::{Read, Write};
#[cfg(not(target_arch = "wasm32"))]
use std::net::{TcpListener, TcpStream};
#[cfg(not(target_arch = "wasm32"))]
use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(not(target_arch = "wasm32"))]
use std::sync::{mpsc, Arc, Mutex};
#[cfg(not(target_arch = "wasm32"))]
use std::thread;
#[cfg(not(target_arch = "wasm32"))]
use std::time::Duration;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FrameWork {
    Svelte,
    Vue,
    React,
    Astro,
    Lit,
    Quasar,
    Alpine,
    Vanilla,
}

impl FrameWork {
    pub fn as_str(&self) -> &'static str {
        match self {
            FrameWork::Svelte => "svelte",
            FrameWork::Vue => "vue",
            FrameWork::React => "react",
            FrameWork::Astro => "astro",
            FrameWork::Lit => "lit",
            FrameWork::Quasar => "quasar",
            FrameWork::Alpine => "alpine",
            FrameWork::Vanilla => "vanilla",
        }
    }
}

pub struct FrameworkConfig {
    pub kind: FrameWork,
    pub ui_dir: &'static str,
}

pub fn framework(kind: FrameWork) -> FrameworkConfig {
    FrameworkConfig { kind, ui_dir: "ui" }
}

#[macro_export]
macro_rules! oker_js {
    (fn $name:ident ( $($arg:ident : $ty:ty),* $(,)? ) -> $ret:ty $body:block) => {
        #[cfg_attr(target_arch = "wasm32", ::wasm_bindgen::prelude::wasm_bindgen)]
        pub fn $name($($arg: $ty),*) -> $ret $body
    };
    (fn $name:ident ( $($arg:ident : $ty:ty),* $(,)? ) $body:block) => {
        #[cfg_attr(target_arch = "wasm32", ::wasm_bindgen::prelude::wasm_bindgen)]
        pub fn $name($($arg: $ty),*) $body
    };
}
pub enum HttpResponse {
    Ok(String),
    Html(String),
    Json(String),
    Xml(String),
    Binary(Vec<u8>, String),
    Err(i32, String),
    Other(i32, String),
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RequestType {
    GET,
    POST,
    PUT,
    PATCH,
    DELETE,
}

pub enum ParsedBody {
    Empty,
    Text(String),
    Json(serde_json::Value),
    Form(HashMap<String, String>),
    Invalid(String),
}

pub struct Request {
    pub method: RequestType,
    pub path: String,
    pub query: HashMap<String, String>,
    pub headers: HashMap<String, String>,
    pub body: String,
    pub parsed_body: ParsedBody,
}

impl Request {
    pub fn header(&self, name: &str) -> Option<&str> {
        self.headers
            .get(&name.to_ascii_lowercase())
            .map(|v| v.as_str())
    }

    pub fn query_param(&self, name: &str) -> Option<&str> {
        self.query.get(name).map(|v| v.as_str())
    }
}

#[cfg(not(target_arch = "wasm32"))]
mod server {
    use super::*;
    use log::{error, info};
    use simplelog::{ColorChoice, Config, LevelFilter, TermLogger, TerminalMode};
    use std::fs;
    use std::path::{Component, Path, PathBuf};

    type HandlerClosure = Box<dyn Fn(&Request) -> HttpResponse + Send + Sync>;

    pub struct App {
        pub(crate) routes: Vec<HashMap<String, (RequestType, HandlerClosure)>>,
        ui_dir: Option<PathBuf>,
    }

    impl App {
        pub fn new(index: HandlerClosure) -> Self {
            let mut hash = HashMap::new();
            hash.insert("/".to_string(), (RequestType::GET, index));
            App {
                routes: vec![hash],
                ui_dir: None,
            }
        }
        pub fn route(&mut self, request: RequestType, url: String, code: HandlerClosure) {
            let mut hash = HashMap::new();
            hash.insert(url, (request, code));
            self.routes.push(hash)
        }

        pub fn serve_ui<P: Into<PathBuf>>(&mut self, dir: P) {
            self.ui_dir = Some(dir.into());
        }

        pub fn start(&mut self) {
            log(
                TermLogger::init(
                    LevelFilter::Debug,
                    Config::default(),
                    TerminalMode::Mixed,
                    ColorChoice::Auto,
                ),
                "failed to start logger",
            );

            let log_opt = std::env::var("OKER_LOG").is_ok();
            if log_opt {
                info!("Starting Server...")
            }

            let listener = log(
                TcpListener::bind("127.0.0.1:6537"),
                "failed to bind to 127.0.0.1:6537",
            );
            if log_opt {
                info!("Server Started; Use q + Enter to quit the server.")
            }
            listener
                .set_nonblocking(true)
                .expect("failed to set nonblocking");
            let routes = &self.routes;
            let ui_dir = self.ui_dir.clone();
            let worker_count = 4;
            let (sender, receiver) = mpsc::channel::<TcpStream>();
            let receiver = Arc::new(Mutex::new(receiver));
            let shutdown = Arc::new(AtomicBool::new(false));

            thread::scope(|scope| {
                let shutdown_reader = Arc::clone(&shutdown);
                scope.spawn(move || {
                    let mut stdin = std::io::stdin();
                    let mut buf = [0u8; 1];
                    while stdin.read(&mut buf).is_ok() {
                        if buf[0] == b'q' || buf[0] == b'Q' {
                            shutdown_reader.store(true, Ordering::SeqCst);
                            break;
                        }
                    }
                });

                for _ in 0..worker_count {
                    let receiver = Arc::clone(&receiver);
                    let ui_dir = ui_dir.clone();
                    scope.spawn(move || loop {
                        let stream = {
                            let recv = receiver.lock();
                            if let Ok(recv) = recv {
                                recv.recv().ok()
                            } else {
                                None
                            }
                        };

                        let Some(stream) = stream else { break };
                        handle_connection(stream, routes, ui_dir.as_deref());
                    });
                }

                loop {
                    if shutdown.load(Ordering::SeqCst) {
                        break;
                    }
                    match listener.accept() {
                        Ok((stream, _)) => {
                            if sender.send(stream).is_err() {
                                break;
                            }
                        }
                        Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => {
                            thread::sleep(Duration::from_millis(50));
                        }
                        Err(_) => continue,
                    }
                }

                // Close the channel so workers can exit gracefully.
                drop(sender);
            });
        }
    }

    fn log<T, E>(res: Result<T, E>, string: &str) -> T {
        res.unwrap_or_else(|_| {
            error!("{}", string);
            std::process::exit(1)
        })
    }

    fn handle_connection(
        mut stream: TcpStream,
        routes: &Vec<HashMap<String, (RequestType, HandlerClosure)>>,
        ui_dir: Option<&Path>,
    ) {
        let request = read_request(&mut stream);
        if request.is_empty() {
            return;
        }

        let response_bytes = handle_request(&request, routes, ui_dir);
        let _ = stream.write_all(&response_bytes);
    }

    pub(crate) fn handle_request(
        request: &str,
        routes: &Vec<HashMap<String, (RequestType, HandlerClosure)>>,
        ui_dir: Option<&Path>,
    ) -> Vec<u8> {
        let (head, body) = match request.split_once("\r\n\r\n") {
            Some((h, b)) => (h, b),
            None => (request, ""),
        };

        let mut head_lines = head.lines();
        let request_line = head_lines.next().unwrap_or("").trim();
        let mut parts = request_line.split_whitespace();
        let method = parts.next().unwrap_or("");
        let raw_path = parts.next().unwrap_or("/");
        let (path, query_string) = match raw_path.split_once('?') {
            Some((p, q)) => (p.to_string(), q),
            None => (raw_path.to_string(), ""),
        };
        let headers = parse_headers(head_lines);
        let parsed_body = parse_body(&headers, body);
        let query = parse_query(query_string);

        let response = if method.is_empty() || path.is_empty() {
            HttpResponse::Err(400, "Bad Request".to_string())
        } else {
            let Some(method) = parse_method(method) else {
                return build_http_response(HttpResponse::Err(400, "Bad Request".to_string()));
            };

            let request = Request {
                method,
                path,
                query,
                headers,
                body: body.to_string(),
                parsed_body,
            };

            match find_handler(&request, routes) {
                Some(handler) => handler(&request),
                None => {
                    if let Some(ui_dir) = ui_dir {
                        if let Some(resp) = try_serve_ui(&request.path, ui_dir) {
                            resp
                        } else {
                            HttpResponse::Other(404, "Not Found".to_string())
                        }
                    } else {
                        HttpResponse::Other(404, "Not Found".to_string())
                    }
                }
            }
        };

        build_http_response(response)
    }

    fn parse_method(method: &str) -> Option<RequestType> {
        match method {
            "GET" => Some(RequestType::GET),
            "POST" => Some(RequestType::POST),
            "PUT" => Some(RequestType::PUT),
            "PATCH" => Some(RequestType::PATCH),
            "DELETE" => Some(RequestType::DELETE),
            _ => None,
        }
    }

    fn find_handler<'a>(
        request: &Request,
        routes: &'a Vec<HashMap<String, (RequestType, HandlerClosure)>>,
    ) -> Option<&'a HandlerClosure> {
        for map in routes {
            if let Some((route_method, route_handler)) = map.get(&request.path) {
                if *route_method == request.method {
                    return Some(route_handler);
                }
            }
        }
        None
    }

    fn build_http_response(response: HttpResponse) -> Vec<u8> {
        let (status_code, reason, body_bytes, content_type) = match response {
            HttpResponse::Ok(body) => (200, "OK", body.into_bytes(), "text/html; charset=utf-8".to_string()),
            HttpResponse::Html(body) => (200, "OK", body.into_bytes(), "text/html; charset=utf-8".to_string()),
            HttpResponse::Json(body) => (200, "OK", body.into_bytes(), "application/json; charset=utf-8".to_string()),
            HttpResponse::Xml(body) => (200, "OK", body.into_bytes(), "application/xml; charset=utf-8".to_string()),
            HttpResponse::Binary(bytes, content_type) => (200, "OK", bytes, content_type),
            HttpResponse::Err(code, body) => (code, "Error", body.into_bytes(), "text/plain; charset=utf-8".to_string()),
            HttpResponse::Other(code, body) => (code, "Other", body.into_bytes(), "text/plain; charset=utf-8".to_string()),
        };

        let header = format!(
            "HTTP/1.1 {} {}\r\nContent-Type: {}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
            status_code,
            reason,
            content_type,
            body_bytes.len()
        );
        let mut response = header.into_bytes();
        response.extend_from_slice(&body_bytes);
        response
    }

    fn read_request(stream: &mut TcpStream) -> String {
        let mut buffer: Vec<u8> = Vec::new();
        let mut temp = [0u8; 1024];
        let mut header_end: Option<usize> = None;
        let mut content_length: Option<usize> = None;

        loop {
            let read = match stream.read(&mut temp) {
                Ok(0) => break,
                Ok(n) => n,
                Err(_) => break,
            };
            buffer.extend_from_slice(&temp[..read]);

            if header_end.is_none() {
                if let Some(pos) = find_subslice(&buffer, b"\r\n\r\n") {
                    header_end = Some(pos + 4);
                    let headers = String::from_utf8_lossy(&buffer[..pos]);
                    content_length = parse_content_length(&headers);
                }
            }

            if let Some(end) = header_end {
                if let Some(cl) = content_length {
                    if buffer.len() >= end + cl {
                        break;
                    }
                } else {
                    break;
                }
            }

            if buffer.len() > 1024 * 1024 {
                break;
            }
        }

        String::from_utf8_lossy(&buffer).to_string()
    }

    fn find_subslice(haystack: &[u8], needle: &[u8]) -> Option<usize> {
        if needle.is_empty() {
            return None;
        }
        haystack.windows(needle.len()).position(|w| w == needle)
    }

    fn parse_content_length(headers: &str) -> Option<usize> {
        for line in headers.lines() {
            let mut parts = line.splitn(2, ':');
            let Some(name) = parts.next() else { continue };
            let name = name.trim().to_ascii_lowercase();
            if name == "content-length" {
                let value = parts.next().unwrap_or("").trim();
                if let Ok(len) = value.parse::<usize>() {
                    return Some(len);
                }
            }
        }
        None
    }

    fn parse_headers<'a, I>(lines: I) -> HashMap<String, String>
    where
        I: Iterator<Item = &'a str>,
    {
        let mut headers = HashMap::new();
        for line in lines {
            let line = line.trim();
            if line.is_empty() {
                continue;
            }
            if let Some((name, value)) = line.split_once(':') {
                headers.insert(name.trim().to_ascii_lowercase(), value.trim().to_string());
            }
        }
        headers
    }

    fn parse_body(headers: &HashMap<String, String>, body: &str) -> ParsedBody {
        if body.is_empty() {
            return ParsedBody::Empty;
        }

        let content_type = headers
            .get("content-type")
            .map(|v| v.as_str())
            .unwrap_or("");

        if content_type.starts_with("application/json") {
            match serde_json::from_str::<serde_json::Value>(body) {
                Ok(value) => ParsedBody::Json(value),
                Err(err) => ParsedBody::Invalid(format!("invalid json: {}", err)),
            }
        } else if content_type.starts_with("application/x-www-form-urlencoded") {
            ParsedBody::Form(parse_form_body(body))
        } else if content_type.starts_with("text/") || content_type.is_empty() {
            ParsedBody::Text(body.to_string())
        } else {
            ParsedBody::Text(body.to_string())
        }
    }

    fn parse_form_body(body: &str) -> HashMap<String, String> {
        let mut map = HashMap::new();
        for pair in body.split('&') {
            if pair.is_empty() {
                continue;
            }
            let (key, value) = match pair.split_once('=') {
                Some((k, v)) => (k, v),
                None => (pair, ""),
            };
            let key = decode_form_component(key);
            let value = decode_form_component(value);
            map.insert(key, value);
        }
        map
    }

    fn parse_query(query: &str) -> HashMap<String, String> {
        if query.is_empty() {
            return HashMap::new();
        }
        parse_form_body(query)
    }

    fn decode_form_component(input: &str) -> String {
        let bytes = input.as_bytes();
        let mut out: Vec<u8> = Vec::with_capacity(bytes.len());
        let mut i = 0;
        while i < bytes.len() {
            match bytes[i] {
                b'+' => {
                    out.push(b' ');
                    i += 1;
                }
                b'%' if i + 2 < bytes.len() => {
                    if let (Some(hi), Some(lo)) = (from_hex(bytes[i + 1]), from_hex(bytes[i + 2]))
                    {
                        out.push((hi << 4) | lo);
                        i += 3;
                    } else {
                        out.push(bytes[i]);
                        i += 1;
                    }
                }
                b => {
                    out.push(b);
                    i += 1;
                }
            }
        }
        String::from_utf8_lossy(&out).to_string()
    }

    fn from_hex(byte: u8) -> Option<u8> {
        match byte {
            b'0'..=b'9' => Some(byte - b'0'),
            b'a'..=b'f' => Some(byte - b'a' + 10),
            b'A'..=b'F' => Some(byte - b'A' + 10),
            _ => None,
        }
    }

    fn try_serve_ui(path: &str, ui_dir: &Path) -> Option<HttpResponse> {
        let clean_path = if path == "/" {
            "index.html".to_string()
        } else {
            path.trim_start_matches('/').to_string()
        };

        let mut file_path = safe_join(ui_dir, &clean_path)?;
        if clean_path.ends_with('/') {
            file_path.push("index.html");
        }

        if file_path.is_dir() {
            file_path.push("index.html");
        }

        if file_path.is_file() {
            let bytes = fs::read(&file_path).ok()?;
            let content_type = content_type_for_path(&file_path);
            return Some(HttpResponse::Binary(bytes, content_type));
        }

        // SPA fallback for client-side routes without extension.
        if Path::new(&clean_path).extension().is_none() {
            let index_path = ui_dir.join("index.html");
            if index_path.is_file() {
                let bytes = fs::read(&index_path).ok()?;
                return Some(HttpResponse::Binary(
                    bytes,
                    "text/html; charset=utf-8".to_string(),
                ));
            }
        }

        None
    }

    fn safe_join(base: &Path, rel: &str) -> Option<PathBuf> {
        let mut out = PathBuf::from(base);
        let rel_path = Path::new(rel);
        if rel_path.is_absolute() {
            return None;
        }
        for comp in rel_path.components() {
            match comp {
                Component::Normal(part) => out.push(part),
                Component::CurDir => {}
                _ => return None,
            }
        }
        Some(out)
    }

    fn content_type_for_path(path: &Path) -> String {
        match path.extension().and_then(|ext| ext.to_str()).unwrap_or("").to_ascii_lowercase().as_str() {
            "html" => "text/html; charset=utf-8",
            "js" => "application/javascript; charset=utf-8",
            "css" => "text/css; charset=utf-8",
            "json" => "application/json; charset=utf-8",
            "svg" => "image/svg+xml",
            "png" => "image/png",
            "jpg" | "jpeg" => "image/jpeg",
            "gif" => "image/gif",
            "webp" => "image/webp",
            "ico" => "image/x-icon",
            "wasm" => "application/wasm",
            "map" => "application/json; charset=utf-8",
            "txt" => "text/plain; charset=utf-8",
            "woff" => "font/woff",
            "woff2" => "font/woff2",
            "ttf" => "font/ttf",
            "otf" => "font/otf",
            _ => "application/octet-stream",
        }
        .to_string()
    }
}

#[cfg(not(target_arch = "wasm32"))]
pub use server::App;

#[cfg(all(test, not(target_arch = "wasm32")))]
pub(crate) use server::handle_request;

#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests {
    use super::*;

    fn response_body(response: &[u8]) -> String {
        let text = String::from_utf8_lossy(response);
        text.split_once("\r\n\r\n")
            .map(|(_, b)| b.to_string())
            .unwrap_or_default()
    }

    #[test]
    fn routes_get_root() {
        let app = App::new(Box::new(|_| HttpResponse::Ok("index".to_string())));

        let request = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
        let response = handle_request(request, &app.routes, None);
        let response_text = String::from_utf8_lossy(&response);

        assert!(response_text.starts_with("HTTP/1.1 200 OK"));
        assert_eq!(response_body(&response), "index");
    }

    #[test]
    fn returns_404_for_missing_route() {
        let app = App::new(Box::new(|_| HttpResponse::Ok("index".to_string())));

        let request = "GET /missing HTTP/1.1\r\nHost: localhost\r\n\r\n";
        let response = handle_request(request, &app.routes, None);
        let response_text = String::from_utf8_lossy(&response);

        assert!(response_text.starts_with("HTTP/1.1 404 Other"));
        assert_eq!(response_body(&response), "Not Found");
    }

    #[test]
    fn passes_body_to_post_handler() {
        let mut app = App::new(Box::new(|_| HttpResponse::Ok("index".to_string())));
        app.route(
            RequestType::POST,
            "/submit".to_string(),
            Box::new(|req| HttpResponse::Ok(format!("body={}", req.body))),
        );

        let request = "POST /submit HTTP/1.1\r\nHost: localhost\r\nContent-Length: 11\r\n\r\nhello=world";
        let response = handle_request(request, &app.routes, None);
        let response_text = String::from_utf8_lossy(&response);

        assert!(response_text.starts_with("HTTP/1.1 200 OK"));
        assert_eq!(response_body(&response), "body=hello=world");
    }

    #[test]
    fn parses_json_body() {
        let mut app = App::new(Box::new(|_| HttpResponse::Ok("index".to_string())));
        app.route(
            RequestType::POST,
            "/json".to_string(),
            Box::new(|req| match &req.parsed_body {
                ParsedBody::Json(value) => HttpResponse::Ok(
                    value
                        .get("name")
                        .and_then(|v| v.as_str())
                        .unwrap_or("")
                        .to_string(),
                ),
                _ => HttpResponse::Err(400, "invalid".to_string()),
            }),
        );

        let request = "POST /json HTTP/1.1\r\nHost: localhost\r\nContent-Type: application/json\r\nContent-Length: 17\r\n\r\n{\"name\":\"oker\"}";
        let response = handle_request(request, &app.routes, None);
        let response_text = String::from_utf8_lossy(&response);

        assert!(response_text.starts_with("HTTP/1.1 200 OK"));
        assert_eq!(response_body(&response), "oker");
    }

    #[test]
    fn parses_form_body() {
        let mut app = App::new(Box::new(|_| HttpResponse::Ok("index".to_string())));
        app.route(
            RequestType::POST,
            "/form".to_string(),
            Box::new(|req| match &req.parsed_body {
                ParsedBody::Form(map) => HttpResponse::Ok(format!(
                    "{}:{}",
                    map.get("a").cloned().unwrap_or_default(),
                    map.get("b").cloned().unwrap_or_default()
                )),
                _ => HttpResponse::Err(400, "invalid".to_string()),
            }),
        );

        let request = "POST /form HTTP/1.1\r\nHost: localhost\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 17\r\n\r\na=1&b=hello+you";
        let response = handle_request(request, &app.routes, None);
        let response_text = String::from_utf8_lossy(&response);

        assert!(response_text.starts_with("HTTP/1.1 200 OK"));
        assert_eq!(response_body(&response), "1:hello you");
    }
}
