use crate::s3_listener::parser::{
    content_length, expects_continue, parse_request_line, split_target, url_decode,
    validate_header_field,
};

#[cfg(test)]
mod tests {
    use super::{
        content_length, expects_continue, parse_request_line, split_target, url_decode,
        validate_header_field,
    };

    #[test]
    fn rejects_oversized_request_line() {
        let long_target = "/".to_owned() + &"a".repeat(3000);
        let line = format!("GET {long_target} HTTP/1.1");
        let error = parse_request_line(&line).unwrap_err();
        assert!(error.to_string().contains("invalid HTTP request line"));
    }

    #[test]
    fn rejects_invalid_and_unsupported_methods() {
        let error = parse_request_line("CONNECT /bucket/key HTTP/1.1").unwrap_err();
        assert!(error.to_string().contains("unsupported HTTP method"));
        let error = parse_request_line("gEt /bucket/key HTTP/1.1").unwrap_err();
        assert!(error.to_string().contains("invalid HTTP method"));
        let error = parse_request_line("BAD*METHOD / HTTP/1.1").unwrap_err();
        assert!(error.to_string().contains("invalid HTTP method"));
    }

    #[test]
    fn rejects_invalid_header_field_tokens() {
        let error = validate_header_field("Invalid Header", "value").unwrap_err();
        assert!(error.to_string().contains("invalid header field-name"));
        let error = validate_header_field("X_Valid-Header", "bad\rc\nvalue").unwrap_err();
        assert!(error.to_string().contains("invalid header value"));
    }

    #[test]
    fn parses_content_length() {
        assert_eq!(
            content_length("POST /upload HTTP/1.1\r\nContent-Length: 12\r\n").unwrap(),
            12
        );
        assert_eq!(content_length("GET / HTTP/1.1\r\n\r\n").unwrap(), 0);
    }

    #[test]
    fn rejects_invalid_content_length() {
        let error = content_length("POST /upload HTTP/1.1\r\nContent-Length: ten\r\n")
            .unwrap_err()
            .to_string();
        assert!(error.contains("invalid content-length"));
    }

    #[test]
    fn splits_http_target_query_parameters() -> anyhow::Result<()> {
        let (path, query) = split_target("/bucket/key.txt?list-type=2&prefix=records%2F")?;
        assert_eq!(path, "/bucket/key.txt");
        assert_eq!(
            query,
            vec![
                ("list-type".to_string(), "2".to_string()),
                ("prefix".to_string(), "records/".to_string())
            ]
        );
        Ok(())
    }

    #[test]
    fn decodes_query_parameter_percent_escapes() -> anyhow::Result<()> {
        let (path, query) =
            split_target("/bucket/key%20space.txt?token=a%2Fb%2Bc&name=hello+world")?;
        assert_eq!(path, "/bucket/key space.txt");
        assert_eq!(query[0], ("token".to_string(), "a/b+c".to_string()));
        assert_eq!(query[1], ("name".to_string(), "hello world".to_string()));
        Ok(())
    }

    #[test]
    fn decodes_request_target_path() -> anyhow::Result<()> {
        let (path, query) = split_target("/bucket%2Fpath/object%20name.txt?x%3Dy=1")?;
        assert_eq!(path, "/bucket/path/object name.txt");
        assert_eq!(query.len(), 1);
        assert_eq!(query[0], ("x=y".to_string(), "1".to_string()));
        Ok(())
    }

    #[test]
    fn rejects_absolute_form_request_target() {
        let error = split_target("http://localhost:9000/bucket/key").unwrap_err();
        assert!(error.to_string().contains("absolute-form"));
    }

    #[test]
    fn rejects_missing_request_target_path_prefix() {
        let error = split_target("bucket/key").unwrap_err();
        assert!(error.to_string().contains("invalid request target"));
    }

    #[test]
    fn rejects_path_traversal_targets() {
        let error = split_target("/bucket/../../secret").unwrap_err();
        assert!(error.to_string().contains("path traversal"));
        let error = split_target("/bucket/%2E%2E/secret").unwrap_err();
        assert!(error.to_string().contains("path traversal"));
    }

    #[test]
    fn rejects_query_limit_violations() {
        let long_query_value = "a".repeat(5000);
        let error = split_target(&format!("/bucket/object?name={long_query_value}")).unwrap_err();
        assert!(error.to_string().contains("query string too long"));
    }

    #[test]
    fn rejects_oversized_request_target() {
        let target = "/".to_owned() + &"a".repeat(5000);
        let error = split_target(target.as_str()).unwrap_err();
        assert!(error.to_string().contains("request target too long"));
    }

    #[test]
    fn rejects_invalid_percent_encoding() {
        assert!(url_decode("a%2G").is_err());
        assert!(url_decode("%").is_err());
    }

    #[test]
    fn detects_expect_continue_header_case_insensitively() {
        assert!(expects_continue(
            "PUT /bucket/key.txt HTTP/1.1\r\nHost: 127.0.0.1\r\nExpect: 100-continue"
        ));
        assert!(expects_continue(
            "PUT /bucket/key.txt HTTP/1.1\r\nhost: 127.0.0.1\r\nexpect: 100-CONTINUE"
        ));
        assert!(!expects_continue(
            "PUT /bucket/key.txt HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Length: 5"
        ));
    }
}
