use crate::{HttpVersion, Method, request::{Request, RequestTarget}, response::Response, statuscode::{StatusClass, StatusCode}};

#[derive(Debug, PartialEq)]
pub enum ParseError {
    TooLong,
    Malformed,
    BadStatusCode,
    BadVersion,
    InvalidEncoding,
    MissingChunkSize,
}

#[derive(Debug, PartialEq)]
pub struct StatusLine {
    version: HttpVersion,
    status: Status,
    reason: Vec<u8>,
}

pub struct RequestLine {
    pub method: Method,
    pub target: RequestTarget,
    pub version: HttpVersion,
}

#[derive(Debug, PartialEq)]
pub struct Status {
    pub code: u16,
}

impl Status {
    pub fn known(self) -> Option<StatusCode> {
        StatusCode::from_u16(self.code)
    }

    pub fn class(self) -> Option<StatusClass> {
        match self.code / 100 {
            1 => Some(StatusClass::Informational),
            2 => Some(StatusClass::Success),
            3 => Some(StatusClass::Redirection),
            4 => Some(StatusClass::ClientError),
            5 => Some(StatusClass::ServerError),
            _ => None,
        }
    }
}

const MAX_STATUS_LINE_LEN: usize = 8192;

//TODO: Further testing + fuzzing + general onceover
pub fn parse_status_line(input: &[u8], request_type: Method) -> Result<Option<(StatusLine, usize)>, ParseError> {
    let search_end = input.len().min(MAX_STATUS_LINE_LEN);
    let line_end = match find_crlf(&input[..search_end]) {
        Some(pos) => pos,
        None => {
            if input.len() >= MAX_STATUS_LINE_LEN {
                return Err(ParseError::TooLong);
            }
            return Ok(None);
        }
    };

    let line = &input[..line_end];

    let first_space = line.iter().position(|&b| b.is_ascii_whitespace())
        .ok_or(ParseError::Malformed)?;
    let version_bytes = &line[..first_space];
    let after_version = line[first_space + 1..].trim_ascii_start();

    let (status_bytes, reason_bytes) = match after_version.iter().position(|&b| b.is_ascii_whitespace()) {
        Some(p) => (after_version[..p].trim_ascii_end(), after_version[p + 1..].trim_ascii()),
        None => (after_version.trim_ascii_end(), &[][..]),
    };

    let version = match version_bytes {
        b"HTTP/1.1" => HttpVersion::Http11,
        b"HTTP/1.0" => HttpVersion::Http10,
        _ => return Err(ParseError::BadVersion),
    };

    if status_bytes.len() != 3 {
        return Err(ParseError::BadStatusCode);
    }
    let mut status: u16 = 0;
    for &b in status_bytes {
        if !b.is_ascii_digit() {
            return Err(ParseError::BadStatusCode);
        }
        status = status * 10 + (b - b'0') as u16;
    }

    Ok(Some((
        StatusLine {
            version,
            status: Status { code: status },
            reason: reason_bytes.to_vec(),
        }, line_end + 2,
    )))
}

const MAX_REQUEST_LINE_LEN: usize = 8192;

//TODO: Further testing + fuzzing + general onceover
pub fn parse_request_line(input: &[u8]) -> Result<Option<(RequestLine, usize)>, ParseError> {
    let search_end = input.len().min(MAX_REQUEST_LINE_LEN);
    let line_end = match find_crlf(&input[..search_end]) {
        Some(pos) => pos,
        None => {
            if input.len() >= MAX_REQUEST_LINE_LEN {
                return Err(ParseError::TooLong);
            }
            return Ok(None);
        }
    };

    let line = &input[..line_end];

    let first_space = line.iter().position(|&b| b.is_ascii_whitespace())
        .ok_or(ParseError::Malformed)?;
    let method_bytes = &line[..first_space];
    let after_method = line[first_space + 1..].trim_ascii_start();

    let (target_bytes, version_bytes) = match after_method.iter().position(|&b| b.is_ascii_whitespace()) {
        Some(p) => (after_method[..p].trim_ascii_end(), after_method[p + 1..].trim_ascii()),
        None => (after_method.trim_ascii_end(), &[][..]),
    };


    let method = Method::from_bytes(method_bytes)?;
    let target = RequestTarget::from_bytes(target_bytes)?;
    let version = match version_bytes {
        b"HTTP/1.1" => HttpVersion::Http11,
        b"HTTP/1.0" => HttpVersion::Http10,
        _ => return Err(ParseError::BadVersion),
    };

    Ok(Some((
        RequestLine {
            method,
            target,
            version,
        }, line_end + 2,
    )))
}

const MAX_HEADERS_SECTION_LEN: usize = 8192; //8KiB

//TODO: Further testing + fuzzing + general onceover
fn parse_headers(input: &[u8]) -> Result<Option<(Vec<(String,String)>,usize)>, ParseError> {
    //Frame the header section.
    //let headers_end = match input.windows(4).position(|w| w == b"\r\n\r\n") {
    //    Some(pos) => pos,
    //    None => {
    //        if input.len() >= MAX_HEADERS_SECTION_LEN {
    //            return Err(ParseError::TooLong);
    //        }
    //        return Ok(None);
    //    }
    //};

    let mut headers = &input[..];
    let mut fields = vec!();
    let mut consumed = 0;
    loop {
        //if headers.is_empty() { break };
        let line_end = match find_crlf(headers) {
            Some(e) => e,
            None => return Ok(None),
        };
        let line = &headers[..line_end];
        consumed += line_end + 2;
        if consumed > MAX_HEADERS_SECTION_LEN { return Err(ParseError::TooLong)}
        if line.is_empty() { break };
        let colon = line.iter().position(|&b| b == b':').ok_or(ParseError::Malformed)?; //Fails if no colon on line.

        let field_name = str::from_utf8(&line[..colon]).map_err(|_| ParseError::InvalidEncoding)?.to_owned();
        if field_name.trim_ascii_end().len() != field_name.len() { return Err(ParseError::Malformed) }
        let field_value = str::from_utf8(&line[colon+1..]).map_err(|_| ParseError::InvalidEncoding)?.trim_ascii().to_owned();

        fields.push((field_name,field_value));
        headers = &headers[line_end+2..];
    }

    Ok(Some((fields,consumed)))
}

//TODO: Further testing + fuzzing + general onceover
fn parse_body(input: &[u8], encoding: Encoding) -> Result<Option<(Vec<u8>, usize)>, ParseError> {
    match encoding {
        Encoding::Length(l) => Ok(Some((input[..l].to_owned(), l))),
        Encoding::Chunked => { parse_chunked_body(input) }
    }
}

//TODO: Further testing + fuzzing + general onceover
fn parse_chunked_body(input: &[u8]) -> Result<Option<(Vec<u8>, usize)>, ParseError> {
    let mut chunks = vec!();
    let mut consumed = 0usize;
    let mut remaining = &input[..];
    loop {
        //Chunk size + extensions (ignored) line.
        let line_end = match find_crlf(remaining) {
            Some(pos) => pos,
            None => return Ok(None),
        };
        if line_end == 0 { return Err(ParseError::MissingChunkSize)};
        let chunk_meta = &remaining[..line_end];
        let semicolon = chunk_meta.iter().position(|&p| p == b';').unwrap_or(chunk_meta.len());
        let chunk_size = usize::from_str_radix(str::from_utf8((&chunk_meta[..semicolon]).trim_ascii()).unwrap(), 16).unwrap();
        if chunk_size == 0 { break }

        //Chunk body.
        remaining = &remaining[line_end+2..]; consumed += line_end+2;
        chunks.push(&remaining[..chunk_size]);
        if remaining[chunk_size..chunk_size+2] != [b'\r',b'\n'] { return Err(ParseError::Malformed) }
        remaining = &remaining[chunk_size+2..]; consumed += chunk_size+2;
    }
    Ok(Some((chunks.into_iter().flatten().copied().collect(), consumed)))
}

//TODO: Write
pub fn parse_request(input: &[u8]) -> Result<Option<Request>, ParseError> {
    todo!()
}

//TODO: Write
pub fn parse_response(input: &[u8]) -> Result<Option<Response>, ParseError> {
    todo!()
}

enum Encoding {
    Length(usize),
    Chunked,
}

fn find_crlf(input: &[u8]) -> Option<usize> {
    input.windows(2).position(|w| w == b"\r\n")
}

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

    #[test]
    fn test_find_crlf() {
        assert_eq!(find_crlf(b"\r\n"), Some(0));
        assert_eq!(find_crlf(b"\0\r\n"), Some(1));
        assert_eq!(find_crlf(b"not here"), None);
        assert_eq!(find_crlf(b"\n"), None);
        assert_eq!(find_crlf(b"\r"), None);
        assert_eq!(find_crlf(b"\n\r"), None);
    }

    #[test]
    fn test_parse_status_line() {
        assert_eq!(parse_status_line(b"HTTP/1.1 100 reason\r\nSOMEOTHERSHIT", Method::Get),
            Ok(Some(( StatusLine { version: HttpVersion::Http11, status: Status { code: 100 }, reason: b"reason".as_slice().to_owned() },
            21,
            ))));
        assert_eq!(parse_status_line(b"HTTP/1.1     300      reason     \r\nSOMEOTHERSHIT", Method::Get),
            Ok(Some(( StatusLine { version: HttpVersion::Http11, status: Status { code: 300 }, reason: b"reason".as_slice().to_owned() },
            35,
            ))));
        assert_eq!(parse_status_line(b"HTTP/1.0 400 Some other reason\r\nSOMEOTHERSHIT", Method::Get),
            Ok(Some(( StatusLine { version:HttpVersion::Http10, status: Status { code: 400 }, reason: b"Some other reason".as_slice().to_owned() },
            32,
            ))));
    }

    #[test]
    fn test_parse_status_line_invalid_version() {
        assert_eq!(parse_status_line(b"HP/1.0 400 Some other reason\r\nSOMEOTHERSHIT", Method::Get),
            Err(ParseError::BadVersion)
        );
        assert_eq!(parse_status_line(b"HTTP/1.2 400 Some other reason\r\nSOMEOTHERSHIT", Method::Get),
            Err(ParseError::BadVersion)
        );
        assert_eq!(parse_status_line(b"HTTP/2.1 400 Some other reason\r\nSOMEOTHERSHIT", Method::Get),
            Err(ParseError::BadVersion)
        );
        assert_eq!(parse_status_line(b"HTTP/1 400 Some other reason\r\nSOMEOTHERSHIT", Method::Get),
            Err(ParseError::BadVersion)
        );
        assert_eq!(parse_status_line(b"HTTP/3 400 Some other reason\r\nSOMEOTHERSHIT", Method::Get),
            Err(ParseError::BadVersion)
        );
    }

    #[test]
    fn test_parse_status_line_invalid_code() {
        assert_eq!(parse_status_line(b"HTTP/1.1 0400 Some other reason\r\nSOMEOTHERSHIT", Method::Get),
            Err(ParseError::BadStatusCode)
        );

        assert_eq!(parse_status_line(b"HTTP/1.1 4000 Some other reason\r\nSOMEOTHERSHIT", Method::Get),
            Err(ParseError::BadStatusCode)
        );

        assert_eq!(parse_status_line(b"HTTP/1.1 040.0 Some other reason\r\nSOMEOTHERSHIT", Method::Get),
            Err(ParseError::BadStatusCode)
        );

        assert_eq!(parse_status_line(b"HTTP/1.1 04 Some other reason\r\nSOMEOTHERSHIT", Method::Get),
            Err(ParseError::BadStatusCode)
        );

        assert_eq!(parse_status_line(b"HTTP/1.1 0x64 Some other reason\r\nSOMEOTHERSHIT", Method::Get),
            Err(ParseError::BadStatusCode)
        );
    }

    #[test]
    fn test_parse_status_line_no_reason() {
        assert_eq!(parse_status_line(b"HTTP/1.1 100\r\nSOMEOTHERSHIT", Method::Get),
            Ok(Some(( StatusLine { version: HttpVersion::Http11, status: Status { code: 100 }, reason: b"".as_slice().to_owned() },
            14,
            ))));

        assert_eq!(parse_status_line(b"HTTP/1.1 100 \t \r\nSOMEOTHERSHIT", Method::Get),
            Ok(Some(( StatusLine { version: HttpVersion::Http11, status: Status { code: 100 }, reason: b"".as_slice().to_owned() },
            17,
            ))));
    }

    #[test]
    fn test_parse_status_line_empty() {
        assert_eq!(parse_status_line(b"\r\n", Method::Get), Err(ParseError::Malformed));
        assert_eq!(parse_status_line(b"", Method::Get), Ok(None));
    }

    #[test]
    fn test_parse_status_line_oversize() {
        let bytes = [b'1';65521];
        assert_eq!(parse_status_line(&bytes, Method::Get), Err(ParseError::TooLong));
    }

    #[test]
    fn test_parse_status_line_incomplete() {
        assert_eq!(parse_status_line(b"", Method::Get), Ok(None));
    }

    #[test]
    fn test_parse_headers() {
        assert_eq!(parse_headers(b"Field1: Field value is this mate\r\n\r\n"), Ok(Some((vec![("Field1".to_owned(),"Field value is this mate".to_owned())], 36))));
    }

    #[test]
    fn test_parse_headers_whitespace_in_name() {
        assert_eq!(parse_headers(b"Field1 : Field value is this mate\r\n\r\n"), Err(ParseError::Malformed));
    }

    #[test]
    fn test_parse_headers_incomplete() {
        assert_eq!(parse_headers(b"Field1: field value is this mate \r\n"), Ok(None));
    }

    #[test]
    fn test_parse_headers_too_long() {
        let mut bytes = [b'\0';8196];
        for i in 0..2049 {
            bytes[i*4] = b'f';
            bytes[i*4+1] = b':';
            bytes[i*4+2] = b'\r';
            bytes[i*4+3] = b'\n';
        }
        assert_eq!(parse_headers(&bytes), Err(ParseError::TooLong));

        let mut bytes = [b'\0';8192];
        for i in 0..2048 {
            bytes[i*4] = b'f';
            bytes[i*4+1] = b':';
            bytes[i*4+2] = b'\r';
            bytes[i*4+3] = b'\n';
        }
        assert_ne!(parse_headers(&bytes), Err(ParseError::TooLong));
    }
}
