//! HTTP Request parsing and representation

use crate::errors::{FalcornError, Result};
use bytes::Bytes;
use std::net::SocketAddr;
use std::sync::Arc;

/// HTTP method representation
#[derive(Debug, Clone, PartialEq)]
pub enum HttpMethod {
    Get,
    Post,
    Put,
    Delete,
    Patch,
    Head,
    Options,
    Trace,
    Connect,
}

impl HttpMethod {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Get => "GET",
            Self::Post => "POST",
            Self::Put => "PUT",
            Self::Delete => "DELETE",
            Self::Patch => "PATCH",
            Self::Head => "HEAD",
            Self::Options => "OPTIONS",
            Self::Trace => "TRACE",
            Self::Connect => "CONNECT",
        }
    }

    pub fn from_str(s: &str) -> Result<Self> {
        match s.to_uppercase().as_str() {
            "GET" => Ok(Self::Get),
            "POST" => Ok(Self::Post),
            "PUT" => Ok(Self::Put),
            "DELETE" => Ok(Self::Delete),
            "PATCH" => Ok(Self::Patch),
            "HEAD" => Ok(Self::Head),
            "OPTIONS" => Ok(Self::Options),
            "TRACE" => Ok(Self::Trace),
            "CONNECT" => Ok(Self::Connect),
            other => Err(FalcornError::InvalidHttpMethod {
                method: other.to_string(),
            }),
        }
    }

    pub fn from_bytes(b: &[u8]) -> Result<Self> {
        match b {
            b"GET" => Ok(Self::Get),
            b"POST" => Ok(Self::Post),
            b"PUT" => Ok(Self::Put),
            b"DELETE" => Ok(Self::Delete),
            b"PATCH" => Ok(Self::Patch),
            b"HEAD" => Ok(Self::Head),
            b"OPTIONS" => Ok(Self::Options),
            b"TRACE" => Ok(Self::Trace),
            b"CONNECT" => Ok(Self::Connect),
            _ => Err(FalcornError::InvalidHttpMethod {
                method: unsafe { std::str::from_utf8_unchecked(b) }.to_string(),
            }),
        }
    }
}

/// HTTP request representation
#[derive(Debug, Clone)]
pub struct HttpRequest {
    /// Raw request buffer (shared for zero-copy slices)
    pub raw: Bytes,
    pub method: HttpMethod,
    pub path: Bytes,
    pub query_string: Option<Bytes>,
    pub version: http::Version,
    /// lowercased header name -> value (values are zero-copy slices)
    pub headers: Vec<(Bytes, Bytes)>,
    pub body: Option<Arc<Bytes>>,
    pub peer_addr: SocketAddr,
}

impl HttpRequest {
    /// Get the full URI (path + query string)
    pub fn uri(&self) -> String {
        match &self.query_string {
            Some(qs) => format!(
                "{}?{}",
                String::from_utf8_lossy(&self.path),
                String::from_utf8_lossy(qs)
            ),
            None => String::from_utf8_lossy(&self.path).to_string(),
        }
    }

    /// Get a header value by name (case-insensitive)
    pub fn get_header(&self, name: &str) -> Option<&[u8]> {
        let lower = name.to_ascii_lowercase();
        for (n, v) in &self.headers {
            if n.as_ref() == lower.as_bytes() {
                return Some(v.as_ref());
            }
        }
        None
    }

    pub fn get_header_str(&self, name: &str) -> Option<&str> {
        self.get_header(name)
            .and_then(|v| std::str::from_utf8(v).ok())
    }

    /// Get the content length
    pub fn content_length(&self) -> Option<usize> {
        self.get_header_str("content-length")
            .and_then(|s| s.parse().ok())
    }

    /// Check if connection is keep-alive
    pub fn is_keep_alive(&self) -> bool {
        match self.version {
            http::Version::HTTP_11 => {
                let connection = self
                    .get_header_str("connection")
                    .unwrap_or("")
                    .to_lowercase();
                !connection.contains("close")
            }
            http::Version::HTTP_10 => {
                let connection = self
                    .get_header_str("connection")
                    .unwrap_or("")
                    .to_lowercase();
                connection.contains("keep-alive")
            }
            _ => false,
        }
    }

    /// Check if request expects 100-continue
    pub fn expects_continue(&self) -> bool {
        self.get_header_str("expect")
            .map(|v| v.to_lowercase().contains("100-continue"))
            .unwrap_or(false)
    }

    /// Get user agent
    pub fn user_agent(&self) -> Option<&str> {
        self.get_header_str("user-agent")
    }

    /// Get host header
    pub fn host(&self) -> Option<&str> {
        self.get_header_str("host")
    }

    /// Get version str
    pub fn version_str(&self) -> &'static str {
        match self.version {
            http::Version::HTTP_10 => "HTTP/1.0",
            http::Version::HTTP_11 => "HTTP/1.1",
            http::Version::HTTP_2 => "HTTP/2.0",
            http::Version::HTTP_3 => "HTTP/3.0",
            _ => "HTTP/1.1",
        }
    }
}

/// Parse HTTP request from raw bytes
pub fn parse(bytes: &[u8], peer_addr: SocketAddr) -> Result<HttpRequest> {
    let raw = Bytes::copy_from_slice(bytes);
    let data = raw.as_ref();

    // Find headers end
    let headers_end = data
        .windows(4)
        .position(|w| w == b"\r\n\r\n")
        .ok_or_else(|| FalcornError::http_parse("Incomplete HTTP headers"))?;

    let (head, _body_bytes) = data.split_at(headers_end + 4);

    // Parse request line
    let line_end = head
        .windows(2)
        .position(|w| w == b"\r\n")
        .ok_or_else(|| FalcornError::http_parse("Invalid request line"))?;

    let request_line = &head[..line_end];

    // METHOD SP URI SP VERSION (manual split to keep indices)
    let method_end = request_line
        .iter()
        .position(|&b| b == b' ')
        .ok_or_else(|| FalcornError::http_parse("Missing method"))?;
    let uri_start = method_end + 1;
    let uri_end = request_line[uri_start..]
        .iter()
        .position(|&b| b == b' ')
        .ok_or_else(|| FalcornError::http_parse("Missing URI"))?
        + uri_start;
    let version_start = uri_end + 1;

    let method_bytes = &request_line[..method_end];
    let uri_bytes = &request_line[uri_start..uri_end];
    let version_bytes = &request_line[version_start..];

    let method = HttpMethod::from_bytes(method_bytes)?;
    let version = parse_version(version_bytes)?;

    // Parse URI (zero-copy slices into raw)
    let (path, query_string) = match uri_bytes.iter().position(|&b| b == b'?') {
        Some(idx) => {
            let path_range = uri_start..(uri_start + idx);
            let query_range = (uri_start + idx + 1)..uri_end;
            (raw.slice(path_range), Some(raw.slice(query_range)))
        }
        None => (raw.slice(uri_start..uri_end), None),
    };

    // Headers
    let mut headers: Vec<(Bytes, Bytes)> = Vec::with_capacity(16);
    let mut pos = line_end + 2;

    while pos < head.len() - 2 {
        if &head[pos..pos + 2] == b"\r\n" {
            break;
        }

        let line_end = head[pos..]
            .windows(2)
            .position(|w| w == b"\r\n")
            .ok_or_else(|| FalcornError::http_parse("Invalid header line"))?
            + pos;

        let line = &head[pos..line_end];
        pos = line_end + 2;

        let colon = line
            .iter()
            .position(|&b| b == b':')
            .ok_or_else(|| FalcornError::http_parse("Invalid header"))?;

        let name = &line[..colon];
        let line_start = line.as_ptr() as usize - data.as_ptr() as usize;
        let value_start = line_start + colon + 1;

        let mut name_lc = Vec::with_capacity(name.len());
        for b in name {
            name_lc.push(b.to_ascii_lowercase());
        }

        let value_range = trim_ascii_range(data, value_start, line_end);
        headers.push((Bytes::from(name_lc), raw.slice(value_range)));
    }

    // Body (zero-copy slice of raw buffer)
    let body_start = headers_end + 4;
    let body = if data.len() > body_start {
        Some(Arc::new(raw.slice(body_start..data.len())))
    } else {
        None
    };

    Ok(HttpRequest {
        raw,
        method,
        path,
        query_string,
        version,
        headers,
        body,
        peer_addr,
    })
}

fn parse_version(b: &[u8]) -> Result<http::Version> {
    match b {
        b"HTTP/1.1" => Ok(http::Version::HTTP_11),
        b"HTTP/1.0" => Ok(http::Version::HTTP_10),
        _ => Err(FalcornError::InvalidHttpVersion {
            version: unsafe { std::str::from_utf8_unchecked(b) }.to_string(),
        }),
    }
}

fn trim_ascii_range(data: &[u8], start: usize, end: usize) -> std::ops::Range<usize> {
    let mut s = start;
    let mut e = end;

    while s < e && data[s] == b' ' {
        s += 1;
    }
    while e > s && data[e - 1] == b' ' {
        e -= 1;
    }

    s..e
}

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

    #[test]
    fn test_http_method_from_str() {
        assert_eq!(HttpMethod::from_str("GET").unwrap(), HttpMethod::Get);
        assert_eq!(HttpMethod::from_str("post").unwrap(), HttpMethod::Post);
        assert!(HttpMethod::from_str("INVALID").is_err());
    }

    #[test]
    fn test_parse_simple_request() {
        let request_bytes = b"GET /test HTTP/1.1\r\nHost: localhost\r\n\r\n";
        let peer_addr = "127.0.0.1:8000".parse().unwrap();

        let request = parse(request_bytes, peer_addr).unwrap();
        assert_eq!(request.method, HttpMethod::Get);
        assert_eq!(request.path, Bytes::from_static(b"/test"));
        assert_eq!(request.version, http::Version::HTTP_11);
    }

    #[test]
    fn test_parse_request_with_query() {
        let request_bytes = b"GET /test?foo=bar&baz=qux HTTP/1.1\r\n\r\n";
        let peer_addr = "127.0.0.1:8000".parse().unwrap();

        let request = parse(request_bytes, peer_addr).unwrap();
        assert_eq!(request.path, Bytes::from_static(b"/test"));
        assert_eq!(
            request.query_string,
            Some(Bytes::from_static(b"foo=bar&baz=qux"))
        );
    }

    #[test]
    fn test_content_length_and_expect_continue() {
        let request_bytes = b"POST /submit HTTP/1.1\r\nHost: localhost\r\nContent-Length: 4\r\nExpect: 100-continue\r\n\r\ntest";
        let peer_addr = "127.0.0.1:8000".parse().unwrap();
        let request = parse(request_bytes, peer_addr).unwrap();

        assert_eq!(request.content_length(), Some(4));
        assert!(request.expects_continue());
    }

    #[test]
    fn test_keep_alive_http11_default_true() {
        let request_bytes = b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
        let peer_addr = "127.0.0.1:8000".parse().unwrap();
        let request = parse(request_bytes, peer_addr).unwrap();
        assert!(request.is_keep_alive());
    }

    #[test]
    fn test_keep_alive_http11_connection_close() {
        let request_bytes = b"GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";
        let peer_addr = "127.0.0.1:8000".parse().unwrap();
        let request = parse(request_bytes, peer_addr).unwrap();
        assert!(!request.is_keep_alive());
    }

    #[test]
    fn test_keep_alive_http10_requires_header() {
        let request_bytes = b"GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n";
        let peer_addr = "127.0.0.1:8000".parse().unwrap();
        let request = parse(request_bytes, peer_addr).unwrap();
        assert!(request.is_keep_alive());
    }
}
