//! HTTP engine entrypoint (version detection + routing)

pub mod detector;
pub mod handler;
pub mod http1;
pub mod http2;
pub mod tls;
pub mod types;

use crate::config::ServerConfig;
use crate::errors::{FalcornError, Result};
use crate::logging::Logger;
use crate::runtime::BufferPool;
use crate::server::request_handler::RequestHandler;
use base64::Engine as _;
use std::net::{SocketAddr, TcpStream};
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
use tokio::io::AsyncReadExt;
use tokio::io::AsyncWriteExt;

use handler::HttpConnectionHandler;
use types::HttpVersion;

use http1::Http1Handler;
use http2::Http2Handler;
use std::io::Read;
use std::io::Write;

/// Structured HTTP engine with versioned handlers.
pub struct HttpEngine {
    http1: Http1Handler,
    http2: Http2Handler,
}

pub trait SendfileSupport {
    fn try_sendfile(
        &mut self,
        file: &mut std::fs::File,
        offset: u64,
        remaining: &mut u64,
    ) -> Result<bool>;
}

impl SendfileSupport for std::net::TcpStream {
    fn try_sendfile(
        &mut self,
        file: &mut std::fs::File,
        offset: u64,
        remaining: &mut u64,
    ) -> Result<bool> {
        #[cfg(target_os = "linux")]
        {
            use std::os::unix::io::AsRawFd;
            let out_fd = self.as_raw_fd();
            let in_fd = file.as_raw_fd();
            let mut off = offset as libc::off_t;

            while *remaining > 0 {
                let to_send = (*remaining).min(usize::MAX as u64) as usize;
                let sent =
                    unsafe { libc::sendfile(out_fd, in_fd, &mut off as *mut libc::off_t, to_send) };
                if sent < 0 {
                    let err = std::io::Error::last_os_error();
                    if err.kind() == std::io::ErrorKind::WouldBlock {
                        continue;
                    }
                    return Err(FalcornError::IoError(err));
                }
                if sent == 0 {
                    break;
                }
                *remaining = remaining.saturating_sub(sent as u64);
            }

            return Ok(true);
        }

        #[cfg(not(target_os = "linux"))]
        {
            let _ = file;
            let _ = offset;
            let _ = remaining;
            Ok(false)
        }
    }
}

#[cfg(feature = "ssl")]
impl<C, T> SendfileSupport for rustls::StreamOwned<C, T>
where
    T: http1::ReadTimeout + Read + Write,
{
    fn try_sendfile(
        &mut self,
        _file: &mut std::fs::File,
        _offset: u64,
        _remaining: &mut u64,
    ) -> Result<bool> {
        Ok(false)
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
enum H2cUpgradeCheck {
    None,
    Valid { header_end: usize },
    Invalid(String),
}

impl Default for HttpEngine {
    fn default() -> Self {
        Self {
            http1: Http1Handler::new(),
            http2: Http2Handler::new(),
        }
    }
}

impl HttpEngine {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn with_pool(pool: BufferPool) -> Self {
        Self {
            http1: Http1Handler::with_pool(pool),
            http2: Http2Handler::new(),
        }
    }

    pub fn handle_connection_sync(
        &self,
        mut stream: TcpStream,
        peer_addr: SocketAddr,
        handler: &RequestHandler,
        logger: &Logger,
        config: &ServerConfig,
        request_count: Option<Arc<AtomicUsize>>,
    ) -> Result<()> {
        if config.ssl_enabled {
            return self.handle_tls_sync(stream, peer_addr, handler, logger, config, request_count);
        }

        let initial = self.read_h2c_preface_sync(&mut stream)?;
        match detector::classify_http2_preface(&initial) {
            detector::PrefaceMatch::Full => {
                if !config.h2c_enabled {
                    return Err(FalcornError::runtime(
                        "H2C preface detected but H2C is disabled",
                    ));
                }
                return self.handle_http2_sync(
                    stream,
                    peer_addr,
                    handler,
                    logger,
                    config,
                    initial,
                    request_count,
                );
            }
            _ => {
                if looks_like_h2c_preface_attempt(&initial) {
                    return Err(FalcornError::http_parse(
                        "Malformed HTTP/2 connection preface",
                    ));
                }

                let initial = if config.h2c_enabled {
                    self.read_http1_head_sync(&mut stream, initial)?
                } else {
                    initial
                };

                match check_h2c_upgrade_request(&initial) {
                    H2cUpgradeCheck::Valid { header_end } => {
                        logger.info(|| {
                            format!(
                                "h2c upgrade requested; peer={} header_bytes={}",
                                peer_addr, header_end
                            )
                        });
                        logger.warn(|| {
                            "h2c upgrade accepted without RFC7540 stream#1 request mapping; client should open a new HTTP/2 stream".to_string()
                        });

                        self.write_h2c_switching_protocols_sync(&mut stream)?;
                        let http2_initial = initial[header_end..].to_vec();
                        self.handle_http2_sync(
                            stream,
                            peer_addr,
                            handler,
                            logger,
                            config,
                            http2_initial,
                            request_count,
                        )
                    }
                    H2cUpgradeCheck::Invalid(reason) => {
                        self.write_simple_response_sync(
                            &mut stream,
                            400,
                            "Bad Request",
                            &format!("Invalid h2c upgrade request: {}", reason),
                        )?;
                        Ok(())
                    }
                    H2cUpgradeCheck::None => self.http1.handle_sync(
                        stream,
                        peer_addr,
                        handler,
                        logger,
                        config,
                        initial,
                        request_count.as_deref(),
                    ),
                }
            }
        }
    }

    #[cfg(feature = "ssl")]
    fn handle_tls_sync(
        &self,
        stream: TcpStream,
        peer_addr: SocketAddr,
        handler: &RequestHandler,
        logger: &Logger,
        config: &ServerConfig,
        request_count: Option<Arc<AtomicUsize>>,
    ) -> Result<()> {
        use rustls::{ServerConnection, StreamOwned};

        let cert = config
            .ssl_certfile
            .as_ref()
            .ok_or_else(|| FalcornError::config("ssl_certfile is required"))?;
        let key = config
            .ssl_keyfile
            .as_ref()
            .ok_or_else(|| FalcornError::config("ssl_keyfile is required"))?;

        let tls_config = tls::load_rustls_config(cert, key)?;
        let conn = ServerConnection::new(tls_config)
            .map_err(|e| FalcornError::runtime(format!("TLS error: {}", e)))?;

        let mut tls_stream = StreamOwned::new(conn, stream);
        tls_stream
            .conn
            .complete_io(&mut tls_stream.sock)
            .map_err(|e| FalcornError::runtime(format!("TLS handshake failed: {}", e)))?;

        let alpn = tls_stream.conn.alpn_protocol();
        let version = detector::detect_from_alpn(alpn);

        match version {
            HttpVersion::Http2 => Err(FalcornError::runtime(
                "HTTP/2 requires async worker (tokio). Use worker_class=async",
            )),
            _ => self.http1.handle_sync(
                tls_stream,
                peer_addr,
                handler,
                logger,
                config,
                Vec::new(),
                request_count.as_deref(),
            ),
        }
    }

    #[cfg(not(feature = "ssl"))]
    fn handle_tls_sync(
        &self,
        _stream: TcpStream,
        _peer_addr: SocketAddr,
        _handler: &RequestHandler,
        _logger: &Logger,
        _config: &ServerConfig,
        _request_count: Option<Arc<AtomicUsize>>,
    ) -> Result<()> {
        Err(FalcornError::runtime(
            "TLS support disabled (compile with feature 'ssl')",
        ))
    }

    pub async fn handle_connection_async<
        S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send,
    >(
        &self,
        mut stream: S,
        peer_addr: SocketAddr,
        handler: RequestHandler,
        logger: Logger,
        config: Arc<ServerConfig>,
        request_count: Option<std::sync::Arc<AtomicUsize>>,
    ) -> Result<()> {
        if config.ssl_enabled {
            return self
                .handle_tls_async(stream, peer_addr, handler, logger, config, request_count)
                .await;
        }

        let initial = self.read_h2c_preface_async(&mut stream).await?;
        match detector::classify_http2_preface(&initial) {
            detector::PrefaceMatch::Full => {
                if !config.h2c_enabled {
                    return Err(FalcornError::runtime(
                        "H2C preface detected but H2C is disabled",
                    ));
                }
                self.http2
                    .handle_async(
                        stream,
                        peer_addr,
                        handler,
                        logger,
                        config,
                        initial,
                        request_count,
                    )
                    .await
            }
            _ => {
                if looks_like_h2c_preface_attempt(&initial) {
                    return Err(FalcornError::http_parse(
                        "Malformed HTTP/2 connection preface",
                    ));
                }

                let initial = if config.h2c_enabled {
                    self.read_http1_head_async(&mut stream, initial).await?
                } else {
                    initial
                };

                match check_h2c_upgrade_request(&initial) {
                    H2cUpgradeCheck::Valid { header_end } => {
                        logger.info(|| {
                            format!(
                                "h2c upgrade requested; peer={} header_bytes={}",
                                peer_addr, header_end
                            )
                        });
                        logger.warn(|| {
                            "h2c upgrade accepted without RFC7540 stream#1 request mapping; client should open a new HTTP/2 stream".to_string()
                        });

                        self.write_h2c_switching_protocols_async(&mut stream)
                            .await?;

                        let http2_initial = initial[header_end..].to_vec();
                        self.http2
                            .handle_async(
                                stream,
                                peer_addr,
                                handler,
                                logger,
                                config,
                                http2_initial,
                                request_count,
                            )
                            .await?;
                        Ok(())
                    }
                    H2cUpgradeCheck::Invalid(reason) => {
                        self.write_simple_response_async(
                            &mut stream,
                            400,
                            "Bad Request",
                            &format!("Invalid h2c upgrade request: {}", reason),
                        )
                        .await?;
                        Ok(())
                    }
                    H2cUpgradeCheck::None => {
                        self.http1
                            .handle_async(
                                stream,
                                peer_addr,
                                handler,
                                logger,
                                config,
                                initial,
                                request_count,
                            )
                            .await
                    }
                }
            }
        }
    }

    fn read_h2c_preface_sync<S: std::io::Read>(&self, stream: &mut S) -> Result<Vec<u8>> {
        const PREFACE_LEN: usize = 24;
        let mut initial = vec![0u8; PREFACE_LEN];
        let n = std::io::Read::read(stream, &mut initial).map_err(FalcornError::IoError)?;
        initial.truncate(n);

        while initial.len() < PREFACE_LEN {
            match detector::classify_http2_preface(&initial) {
                detector::PrefaceMatch::Partial => {
                    let mut one = [0u8; 1];
                    let n = std::io::Read::read(stream, &mut one).map_err(FalcornError::IoError)?;
                    if n == 0 {
                        return Ok(initial);
                    }
                    initial.push(one[0]);
                }
                _ => break,
            }
        }

        Ok(initial)
    }

    fn read_http1_head_sync<S: std::io::Read>(
        &self,
        stream: &mut S,
        mut initial: Vec<u8>,
    ) -> Result<Vec<u8>> {
        const MAX_HEADER_BYTES: usize = 64 * 1024;
        const READ_CHUNK: usize = 1024;

        while find_headers_end(&initial).is_none() && initial.len() < MAX_HEADER_BYTES {
            let mut chunk = [0u8; READ_CHUNK];
            let n = std::io::Read::read(stream, &mut chunk).map_err(FalcornError::IoError)?;
            if n == 0 {
                break;
            }
            initial.extend_from_slice(&chunk[..n]);
        }

        if initial.len() >= MAX_HEADER_BYTES && find_headers_end(&initial).is_none() {
            return Err(FalcornError::http_parse(
                "HTTP request headers too large while checking h2c upgrade",
            ));
        }

        Ok(initial)
    }

    async fn read_h2c_preface_async<S: tokio::io::AsyncRead + Unpin>(
        &self,
        stream: &mut S,
    ) -> Result<Vec<u8>> {
        const PREFACE_LEN: usize = 24;
        let mut initial = vec![0u8; PREFACE_LEN];
        let n = stream
            .read(&mut initial)
            .await
            .map_err(FalcornError::IoError)?;
        initial.truncate(n);

        while initial.len() < PREFACE_LEN {
            match detector::classify_http2_preface(&initial) {
                detector::PrefaceMatch::Partial => {
                    let mut one = [0u8; 1];
                    let n = stream.read(&mut one).await.map_err(FalcornError::IoError)?;
                    if n == 0 {
                        return Ok(initial);
                    }
                    initial.push(one[0]);
                }
                _ => break,
            }
        }

        Ok(initial)
    }

    async fn read_http1_head_async<S: tokio::io::AsyncRead + Unpin>(
        &self,
        stream: &mut S,
        mut initial: Vec<u8>,
    ) -> Result<Vec<u8>> {
        const MAX_HEADER_BYTES: usize = 64 * 1024;
        const READ_CHUNK: usize = 1024;

        while find_headers_end(&initial).is_none() && initial.len() < MAX_HEADER_BYTES {
            let mut chunk = [0u8; READ_CHUNK];
            let n = stream
                .read(&mut chunk)
                .await
                .map_err(FalcornError::IoError)?;
            if n == 0 {
                break;
            }
            initial.extend_from_slice(&chunk[..n]);
        }

        if initial.len() >= MAX_HEADER_BYTES && find_headers_end(&initial).is_none() {
            return Err(FalcornError::http_parse(
                "HTTP request headers too large while checking h2c upgrade",
            ));
        }

        Ok(initial)
    }

    fn write_simple_response_sync<S: std::io::Write>(
        &self,
        stream: &mut S,
        status: u16,
        reason: &str,
        body: &str,
    ) -> Result<()> {
        let resp = format!(
            "HTTP/1.1 {} {}\r\ncontent-type: text/plain; charset=utf-8\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{}",
            status,
            reason,
            body.len(),
            body
        );
        std::io::Write::write_all(stream, resp.as_bytes()).map_err(FalcornError::IoError)?;
        std::io::Write::flush(stream).map_err(FalcornError::IoError)?;
        Ok(())
    }

    async fn write_simple_response_async<S: tokio::io::AsyncWrite + Unpin>(
        &self,
        stream: &mut S,
        status: u16,
        reason: &str,
        body: &str,
    ) -> Result<()> {
        let resp = format!(
            "HTTP/1.1 {} {}\r\ncontent-type: text/plain; charset=utf-8\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{}",
            status,
            reason,
            body.len(),
            body
        );
        stream
            .write_all(resp.as_bytes())
            .await
            .map_err(FalcornError::IoError)?;
        stream.flush().await.map_err(FalcornError::IoError)?;
        Ok(())
    }

    fn write_h2c_switching_protocols_sync<S: std::io::Write>(&self, stream: &mut S) -> Result<()> {
        const RESP: &[u8] =
            b"HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: h2c\r\n\r\n";
        std::io::Write::write_all(stream, RESP).map_err(FalcornError::IoError)?;
        std::io::Write::flush(stream).map_err(FalcornError::IoError)?;
        Ok(())
    }

    fn handle_http2_sync(
        &self,
        mut stream: TcpStream,
        peer_addr: SocketAddr,
        handler: &RequestHandler,
        logger: &Logger,
        config: &ServerConfig,
        initial: Vec<u8>,
        request_count: Option<Arc<AtomicUsize>>,
    ) -> Result<()> {
        stream
            .set_nonblocking(true)
            .map_err(FalcornError::IoError)?;

        let tokio_stream =
            tokio::net::TcpStream::from_std(stream).map_err(FalcornError::IoError)?;

        let rt = tokio::runtime::Builder::new_current_thread()
            .enable_all()
            .build()
            .map_err(|e| FalcornError::runtime(format!("tokio runtime error: {}", e)))?;

        let handler = handler.clone();
        let logger = logger.clone();
        let config = Arc::new(config.clone());

        rt.block_on(self.http2.handle_async(
            tokio_stream,
            peer_addr,
            handler,
            logger,
            config,
            initial,
            request_count,
        ))
    }

    async fn write_h2c_switching_protocols_async<S: tokio::io::AsyncWrite + Unpin>(
        &self,
        stream: &mut S,
    ) -> Result<()> {
        const RESP: &[u8] =
            b"HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: h2c\r\n\r\n";
        stream
            .write_all(RESP)
            .await
            .map_err(FalcornError::IoError)?;
        stream.flush().await.map_err(FalcornError::IoError)?;
        Ok(())
    }

    #[cfg(feature = "ssl")]
    async fn handle_tls_async<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send>(
        &self,
        stream: S,
        peer_addr: SocketAddr,
        handler: RequestHandler,
        logger: Logger,
        config: Arc<ServerConfig>,
        request_count: Option<std::sync::Arc<AtomicUsize>>,
    ) -> Result<()> {
        use tokio_rustls::TlsAcceptor;

        let cert = config
            .ssl_certfile
            .as_ref()
            .ok_or_else(|| FalcornError::config("ssl_certfile is required"))?;
        let key = config
            .ssl_keyfile
            .as_ref()
            .ok_or_else(|| FalcornError::config("ssl_keyfile is required"))?;

        let tls_config = tls::load_rustls_config(cert, key)?;
        let acceptor = TlsAcceptor::from(tls_config);

        let tls_stream = acceptor
            .accept(stream)
            .await
            .map_err(|e| FalcornError::runtime(format!("TLS accept failed: {}", e)))?;

        let alpn = tls_stream.get_ref().1.alpn_protocol();
        let version = detector::detect_from_alpn(alpn);

        match version {
            HttpVersion::Http2 => {
                self.http2
                    .handle_async(
                        tls_stream,
                        peer_addr,
                        handler,
                        logger,
                        config,
                        Vec::new(),
                        request_count,
                    )
                    .await
            }
            _ => {
                self.http1
                    .handle_async(
                        tls_stream,
                        peer_addr,
                        handler,
                        logger,
                        config,
                        Vec::new(),
                        request_count,
                    )
                    .await
            }
        }
    }

    #[cfg(not(feature = "ssl"))]
    async fn handle_tls_async<S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send>(
        &self,
        _stream: S,
        _peer_addr: SocketAddr,
        _handler: RequestHandler,
        _logger: Logger,
        _config: Arc<ServerConfig>,
        _request_count: Option<std::sync::Arc<AtomicUsize>>,
    ) -> Result<()> {
        Err(FalcornError::runtime(
            "TLS support disabled (compile with feature 'ssl')",
        ))
    }
}

fn check_h2c_upgrade_request(head: &[u8]) -> H2cUpgradeCheck {
    let header_end = match find_headers_end(head) {
        Some(idx) => idx + 4,
        None => return H2cUpgradeCheck::None,
    };

    let mut headers = [httparse::EMPTY_HEADER; 64];
    let mut req = httparse::Request::new(&mut headers);
    let status = match req.parse(&head[..header_end]) {
        Ok(s) => s,
        Err(e) => return H2cUpgradeCheck::Invalid(format!("invalid HTTP request: {}", e)),
    };
    if status.is_partial() {
        return H2cUpgradeCheck::None;
    }

    let mut upgrade_h2c = false;
    let mut connection_value: Option<&[u8]> = None;
    let mut h2_settings: Option<&[u8]> = None;

    for h in req.headers.iter() {
        let name = h.name.to_ascii_lowercase();
        match name.as_str() {
            "upgrade" => {
                if let Ok(v) = std::str::from_utf8(h.value) {
                    if v.trim().eq_ignore_ascii_case("h2c") {
                        upgrade_h2c = true;
                    }
                }
            }
            "connection" => connection_value = Some(h.value),
            "http2-settings" => h2_settings = Some(h.value),
            _ => {}
        }
    }

    if !upgrade_h2c {
        return H2cUpgradeCheck::None;
    }

    let connection = match connection_value.and_then(|v| std::str::from_utf8(v).ok()) {
        Some(v) => v,
        None => {
            return H2cUpgradeCheck::Invalid("missing Connection header".to_string());
        }
    };

    if !connection_has_token(connection, "upgrade")
        || !connection_has_token(connection, "http2-settings")
    {
        return H2cUpgradeCheck::Invalid(
            "Connection must include 'Upgrade' and 'HTTP2-Settings'".to_string(),
        );
    }

    let settings = match h2_settings.and_then(|v| std::str::from_utf8(v).ok()) {
        Some(v) => v.trim(),
        None => {
            return H2cUpgradeCheck::Invalid("missing HTTP2-Settings header".to_string());
        }
    };

    let decoded = match base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(settings) {
        Ok(v) => v,
        Err(_) => return H2cUpgradeCheck::Invalid("invalid HTTP2-Settings base64".to_string()),
    };
    if decoded.is_empty() || decoded.len() % 6 != 0 {
        return H2cUpgradeCheck::Invalid("invalid HTTP2-Settings payload length".to_string());
    }

    H2cUpgradeCheck::Valid { header_end }
}

fn connection_has_token(value: &str, token: &str) -> bool {
    value
        .split(',')
        .map(|s| s.trim())
        .any(|t| t.eq_ignore_ascii_case(token))
}

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

fn looks_like_h2c_preface_attempt(buf: &[u8]) -> bool {
    !buf.is_empty() && buf.starts_with(b"PRI *")
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::config::{ProtocolType, ServerConfig, WorkerClass};
    use crate::logging::Logger;
    use crate::logging::ipc::LogEvent;
    use crate::python::app::ApplicationInstance;
    use crate::server::request_handler::RequestHandler;
    use std::net::SocketAddr;
    use std::path::PathBuf;
    use tokio::io::{AsyncReadExt, AsyncWriteExt, duplex};

    fn sample_http2_settings() -> String {
        // SETTINGS_HEADER_TABLE_SIZE = 4096
        let payload = [0x00, 0x01, 0x00, 0x00, 0x10, 0x00];
        base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(payload)
    }

    #[test]
    fn test_check_h2c_upgrade_request_valid() {
        let settings = sample_http2_settings();
        let req = format!(
            "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: Upgrade, HTTP2-Settings\r\nUpgrade: h2c\r\nHTTP2-Settings: {}\r\n\r\n",
            settings
        );
        assert!(matches!(
            check_h2c_upgrade_request(req.as_bytes()),
            H2cUpgradeCheck::Valid { .. }
        ));
    }

    #[test]
    fn test_check_h2c_upgrade_request_missing_connection_tokens() {
        let settings = sample_http2_settings();
        let req = format!(
            "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\nUpgrade: h2c\r\nHTTP2-Settings: {}\r\n\r\n",
            settings
        );
        assert!(matches!(
            check_h2c_upgrade_request(req.as_bytes()),
            H2cUpgradeCheck::Invalid(_)
        ));
    }

    #[test]
    fn test_check_h2c_upgrade_request_invalid_settings() {
        let req = "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: Upgrade, HTTP2-Settings\r\nUpgrade: h2c\r\nHTTP2-Settings: !!!\r\n\r\n";
        assert!(matches!(
            check_h2c_upgrade_request(req.as_bytes()),
            H2cUpgradeCheck::Invalid(_)
        ));
    }

    #[test]
    fn test_check_h2c_upgrade_request_none_for_regular_http1() {
        let req = b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
        assert_eq!(check_h2c_upgrade_request(req), H2cUpgradeCheck::None);
    }

    #[test]
    fn test_check_h2c_upgrade_request_missing_http2_settings() {
        let req =
            b"GET / HTTP/1.1\r\nHost: localhost\r\nConnection: Upgrade\r\nUpgrade: h2c\r\n\r\n";
        assert!(matches!(
            check_h2c_upgrade_request(req),
            H2cUpgradeCheck::Invalid(_)
        ));
    }

    #[test]
    fn test_check_h2c_upgrade_request_invalid_request_line() {
        let req = b"BOGUS\r\nUpgrade: h2c\r\nConnection: Upgrade, HTTP2-Settings\r\nHTTP2-Settings: AAEAAQA\r\n\r\n";
        assert!(matches!(
            check_h2c_upgrade_request(req),
            H2cUpgradeCheck::Invalid(_)
        ));
    }

    #[test]
    fn test_connection_has_token_case_and_spaces() {
        assert!(connection_has_token(
            " keep-alive, Upgrade , HTTP2-Settings ",
            "upgrade"
        ));
        assert!(connection_has_token(
            "keep-alive, UPGRADE , http2-settings",
            "HTTP2-Settings"
        ));
        assert!(!connection_has_token("keep-alive, close", "upgrade"));
    }

    #[test]
    fn test_check_h2c_upgrade_request_valid_with_extra_bytes() {
        let settings = sample_http2_settings();
        let mut req = format!(
            "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: Upgrade, HTTP2-Settings\r\nUpgrade: h2c\r\nHTTP2-Settings: {}\r\n\r\n",
            settings
        )
        .into_bytes();
        req.extend_from_slice(b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
        match check_h2c_upgrade_request(&req) {
            H2cUpgradeCheck::Valid { header_end } => {
                assert!(header_end < req.len());
                assert_eq!(&req[header_end..header_end + 3], b"PRI");
            }
            other => panic!("expected valid upgrade, got {:?}", other),
        }
    }

    #[test]
    fn test_looks_like_h2c_preface_attempt() {
        assert!(looks_like_h2c_preface_attempt(b"PRI * H"));
        assert!(!looks_like_h2c_preface_attempt(b"GET / HTTP/1.1"));
    }

    fn test_logger() -> Logger {
        let (tx, _rx) = crossbeam_channel::unbounded::<LogEvent>();
        Logger::new(crate::config::LogLevel::Info, tx, false)
    }

    fn test_handler(protocol: ProtocolType) -> RequestHandler {
        let app_root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
            .join("..")
            .join("test_apps");
        let app = ApplicationInstance::load(
            "test",
            "application",
            &[app_root],
            &protocol,
            "asyncio",
            true,
            30_000,
            2048,
            4096,
            "task",
        )
        .expect("load test python app");
        RequestHandler::new(app, protocol, false)
    }

    fn async_config(h2c_enabled: bool) -> ServerConfig {
        let mut cfg = ServerConfig::new("test".to_string(), "application".to_string());
        cfg.worker_class = WorkerClass::Async;
        cfg.protocol = ProtocolType::Wsgi;
        cfg.h2c_enabled = h2c_enabled;
        cfg
    }

    async fn read_http1_response_head<S: tokio::io::AsyncRead + Unpin>(io: &mut S) -> Vec<u8> {
        let mut out = Vec::new();
        let mut one = [0u8; 1];
        loop {
            let n = io.read(&mut one).await.expect("read response");
            if n == 0 {
                break;
            }
            out.push(one[0]);
            if out.windows(4).any(|w| w == b"\r\n\r\n") {
                break;
            }
            assert!(out.len() < 16 * 1024, "response head too large");
        }
        out
    }

    fn parse_content_length(head: &str) -> Option<usize> {
        for line in head.lines() {
            if let Some((name, value)) = line.split_once(':')
                && name.trim().eq_ignore_ascii_case("content-length")
            {
                return value.trim().parse::<usize>().ok();
            }
        }
        None
    }

    // #[test]
    fn test_h2c_prior_knowledge_e2e() {
        let rt = tokio::runtime::Runtime::new().expect("tokio runtime");
        rt.block_on(async {
            let engine = HttpEngine::new();
            let handler = test_handler(ProtocolType::Wsgi);
            let logger = test_logger();
            let config = async_config(true);
            let peer: SocketAddr = "127.0.0.1:8080".parse().expect("peer addr");

            let (client_io, server_io) = duplex(64 * 1024);

            let server_task = tokio::spawn(async move {
                engine
                    .handle_connection_async(
                        server_io,
                        peer,
                        handler,
                        logger,
                        Arc::new(config),
                        None,
                    )
                    .await
            });

            let (send, connection) = h2::client::handshake(client_io)
                .await
                .expect("h2 client handshake");
            let conn_task = tokio::spawn(async move { connection.await });

            drop(send);
            conn_task.abort();
            server_task.abort();
        });
    }

    // #[test]
    fn test_h2c_upgrade_e2e_then_new_h2_stream() {
        let rt = tokio::runtime::Runtime::new().expect("tokio runtime");
        rt.block_on(async {
            let engine = HttpEngine::new();
            let handler = test_handler(ProtocolType::Wsgi);
            let logger = test_logger();
            let config = async_config(true);
            let peer: SocketAddr = "127.0.0.1:8081".parse().expect("peer addr");

            let (mut client_io, server_io) = duplex(64 * 1024);

            let server_task = tokio::spawn(async move {
                engine
                    .handle_connection_async(
                        server_io,
                        peer,
                        handler,
                        logger,
                        Arc::new(config),
                        None,
                    )
                    .await
            });

            let settings = sample_http2_settings();
            let upgrade_req = format!(
                "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: Upgrade, HTTP2-Settings\r\nUpgrade: h2c\r\nHTTP2-Settings: {}\r\n\r\n",
                settings
            );
            client_io
                .write_all(upgrade_req.as_bytes())
                .await
                .expect("write upgrade request");

            let head = read_http1_response_head(&mut client_io).await;
            let head_str = String::from_utf8(head).expect("ascii response head");
            assert!(head_str.starts_with("HTTP/1.1 101 Switching Protocols"));

            let (send, connection) = h2::client::handshake(client_io)
                .await
                .expect("h2 client handshake after 101");
            let conn_task = tokio::spawn(async move { connection.await });

            drop(send);
            conn_task.abort();
            server_task.abort();
        });
    }

    // #[test]
    fn test_upgrade_request_falls_back_to_http1_when_h2c_disabled() {
        let rt = tokio::runtime::Runtime::new().expect("tokio runtime");
        rt.block_on(async {
            let engine = HttpEngine::new();
            let handler = test_handler(ProtocolType::Wsgi);
            let logger = test_logger();
            let config = async_config(false);
            let peer: SocketAddr = "127.0.0.1:8082".parse().expect("peer addr");

            let (mut client_io, server_io) = duplex(64 * 1024);
            let server_task = tokio::spawn(async move {
                engine
                    .handle_connection_async(
                        server_io,
                        peer,
                        handler,
                        logger,
                        Arc::new(config),
                        None,
                    )
                    .await
            });

            let settings = sample_http2_settings();
            let req = format!(
                "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: Upgrade, HTTP2-Settings\r\nUpgrade: h2c\r\nHTTP2-Settings: {}\r\n\r\n",
                settings
            );
            client_io.write_all(req.as_bytes()).await.expect("write request");

            let head = read_http1_response_head(&mut client_io).await;
            let head_str = String::from_utf8(head).expect("ascii response head");
            assert!(
                head_str.starts_with("HTTP/1.1 200 OK"),
                "response was: {}",
                head_str
            );

            let len = parse_content_length(&head_str).expect("content-length header");
            let mut body = vec![0u8; len];
            client_io
                .read_exact(&mut body)
                .await
                .expect("read response body");
            assert_eq!(body, b"Hello world");

            drop(client_io);
            server_task
                .await
                .expect("server task join")
                .expect("server result");
        });
    }
}
