use std::collections::HashMap;

fn percent_decode(s: &str) -> String {
    let mut result = String::new();
    let mut chars = s.chars().peekable();
    while let Some(c) = chars.next() {
        if c == '%' {
            let hex: String = chars.by_ref().take(2).collect();
            if hex.len() == 2
                && let Ok(byte) = u8::from_str_radix(&hex, 16)
            {
                result.push(byte as char);
                continue;
            }
            result.push('%');
            result.push_str(&hex);
        } else if c == '+' {
            result.push(' ');
        } else {
            result.push(c);
        }
    }
    result
}

fn parse_query(query_str: &str) -> HashMap<String, String> {
    query_str
        .split('&')
        .filter_map(|pair| {
            if pair.is_empty() {
                return None;
            }
            let (k, v) = pair.split_once('=').unwrap_or((pair, ""));
            Some((k.to_string(), percent_decode(v)))
        })
        .collect()
}

pub fn parse_url(url: &str) -> (String, HashMap<String, String>) {
    let (path, query_str) = url.split_once('?').unwrap_or((url, ""));
    let path = path.trim_start_matches('/');

    (path.into(), parse_query(query_str))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_query() {
        let mut params: HashMap<String, String> = HashMap::new();

        params.insert("param1".into(), "hello".into());
        params.insert("param2".into(), "world".into());
        assert_eq!(parse_query("param1=hello&param2=world"), params);

        params.insert("param3".into(), "".into());
        assert_eq!(parse_query("param1=hello&param2=world&param3"), params);

        params.insert("param4".into(), "next param".into());
        assert_eq!(
            parse_query("param1=hello&param2=world&param3&param4=next%20param"),
            params
        );
    }

    #[test]
    fn test_parse_url() {
        let mut params: HashMap<String, String> = HashMap::new();

        assert_eq!(parse_url("/.pijul"), (".pijul".into(), params.clone()));

        params.insert("a_param".into(), "a value".into());

        assert_eq!(parse_url("/.pijul?a_param=a%20value"), (".pijul".into(), params.clone()));
    }

    #[test]
    fn test_percent_decode() {
        assert_eq!(percent_decode("Hello%20World%21"), "Hello World!");
        assert_eq!(
            percent_decode("~%20is%20where%20the%20%3C3%20is"),
            "~ is where the <3 is"
        );

        // ascii extended not allowed
        // assert_eq!(percent_decode("My%20pijul%20channel%20is%20named%20%22G%C3%B6ta+kanal%22"),"My pijul channel is named \"Göta kanal\"");
    }
}