use super::execute::RequestCoordinator;
use super::*;
use futures::StreamExt;
use secir::Transport;
use secir::transport::{RequestPlan, RequestSpec};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;

#[path = "../../../tests/unit/http_transport_basic.rs"]
mod http_transport_basic;
#[path = "../../../tests/unit/http_transport_transport.rs"]
mod http_transport_transport;

fn single_request_plan(url: String) -> RequestPlan {
    RequestPlan {
        requests: vec![RequestSpec {
            target: url.clone(),
            url,
            method: "GET".to_string(),
            headers: HashMap::new(),
            body: None,
            template_refs: vec![("test-template".to_string(), 0)],
            follow_redirects: true,
            max_redirects: 10,
            cookie_reuse: true,
        }],
        target_count: 1,
        template_count: 1,
        dedup_savings: 0,
    }
}

fn chunked_http_response(content_length: usize, body: &[u8]) -> Vec<u8> {
    let mut response = format!(
        "HTTP/1.1 200 OK\r\nContent-Length: {content_length}\r\nTransfer-Encoding: chunked\r\n\r\n"
    )
    .into_bytes();
    for chunk in body.chunks(8192) {
        response.extend_from_slice(format!("{:X}\r\n", chunk.len()).as_bytes());
        response.extend_from_slice(chunk);
        response.extend_from_slice(b"\r\n");
    }
    response.extend_from_slice(b"0\r\n\r\n");
    response
}

// ============================================================================
// Audit verification tests  -  designed to fail on current code
// ============================================================================

#[tokio::test]
#[ignore = "slow: redirect SSRF loop - run: cargo test -p scanclient http_transport::tests -- --ignored"]
async fn verify_httptransport_redirect_ssrf() {
    let redirect_listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
    let redirect_addr = redirect_listener.local_addr().unwrap();

    let victim_listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
    let victim_addr = victim_listener.local_addr().unwrap();

    let redirect_server = tokio::spawn(async move {
        let (mut socket, _) = redirect_listener.accept().await.unwrap();
        let mut buf = [0u8; 2048];
        let _ = socket.read(&mut buf).await;
        let response = format!(
            "HTTP/1.1 302 Found\r\nLocation: http://127.0.0.1:{}/\r\nContent-Length: 0\r\n\r\n",
            victim_addr.port()
        );
        let _ = socket.write_all(response.as_bytes()).await;
    });

    let victim_server = tokio::spawn(async move {
        let result =
            tokio::time::timeout(std::time::Duration::from_secs(2), victim_listener.accept()).await;
        result
    });

    let transport = super::HttpTransport::with_transport_options(
        1,
        std::time::Duration::from_secs(5),
        super::HttpTransportOptions {
            allow_internal: true,
            ..super::HttpTransportOptions::default()
        },
    )
    .unwrap();

    let plan = RequestPlan {
        requests: vec![RequestSpec {
            target: format!("http://{}/", redirect_addr),
            url: format!("http://{}/", redirect_addr),
            method: "GET".to_string(),
            headers: HashMap::new(),
            body: None,
            template_refs: vec![("test-template".to_string(), 0)],
            follow_redirects: true,
            max_redirects: 10,
            cookie_reuse: true,
        }],
        target_count: 1,
        template_count: 1,
        dedup_savings: 0,
    };

    let _ = transport.execute(&plan).collect::<Vec<_>>().await;
    let _ = redirect_server.await;

    let victim_result = victim_server.await.unwrap();
    assert!(
        victim_result.is_err(),
        "HttpTransport must NOT follow redirects to internal IPs (SSRF)"
    );
}

#[test]
fn verify_httptransport_tls_defaults_to_secure() {
    let opts = super::HttpTransportOptions::default();
    assert!(
        !opts.accept_invalid_certs,
        "HttpTransport must verify TLS certificates by default"
    );
}

#[tokio::test]
async fn verify_httptransport_body_read_error_not_swallowed() {
    let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
    let addr = listener.local_addr().unwrap();

    let server = tokio::spawn(async move {
        let (mut socket, _) = listener.accept().await.unwrap();
        let mut buf = [0u8; 2048];
        let _ = socket.read(&mut buf).await;
        let _ = socket
            .write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 100\r\n\r\nhello")
            .await;
        drop(socket);
    });

    let transport = super::HttpTransport::with_transport_options(
        1,
        std::time::Duration::from_secs(5),
        super::HttpTransportOptions {
            allow_internal: true,
            ..super::HttpTransportOptions::default()
        },
    )
    .unwrap();

    let plan = RequestPlan {
        requests: vec![RequestSpec {
            target: format!("http://{}/", addr),
            url: format!("http://{}/", addr),
            method: "GET".to_string(),
            headers: HashMap::new(),
            body: None,
            template_refs: vec![("test-template".to_string(), 0)],
            follow_redirects: false,
            max_redirects: 10,
            cookie_reuse: true,
        }],
        target_count: 1,
        template_count: 1,
        dedup_savings: 0,
    };

    let results = transport.execute(&plan).collect::<Vec<_>>().await;
    let _ = server.await;

    assert_eq!(results.len(), 1);
    assert!(
        results[0].is_err(),
        "Incomplete body read must be surfaced as an error, not swallowed as success"
    );
}

#[tokio::test]
#[ignore = "slow: proxy redirect loop >60s - run: cargo test -p scanclient http_transport::tests -- --ignored"]
async fn verify_httptransport_proxy_disables_redirects() {
    let proxy_listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
    let proxy_addr = proxy_listener.local_addr().unwrap();

    let victim_listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
    let victim_addr = victim_listener.local_addr().unwrap();

    let proxy_server = tokio::spawn(async move {
        // Accept up to a few connections so the test can observe whether
        // a redirect is followed through the proxy.
        for _ in 0..3 {
            if let Ok((mut socket, _)) = proxy_listener.accept().await {
                let mut buf = [0u8; 4096];
                if let Ok(n) = socket.read(&mut buf).await {
                    let request = String::from_utf8_lossy(&buf[..n]);
                    if request.contains("redirect") {
                        let response = format!(
                            "HTTP/1.1 302 Found\r\nLocation: http://127.0.0.1:{}/\r\nContent-Length: 0\r\n\r\n",
                            victim_addr.port()
                        );
                        let _ = socket.write_all(response.as_bytes()).await;
                    }
                }
            }
        }
    });

    let victim_server = tokio::spawn(async move {
        let result =
            tokio::time::timeout(std::time::Duration::from_secs(2), victim_listener.accept()).await;
        result
    });

    let transport = super::HttpTransport::with_transport_options(
        1,
        std::time::Duration::from_secs(5),
        super::HttpTransportOptions {
            proxy_url: Some(format!("http://{}", proxy_addr)),
            allow_internal: true,
            ..super::HttpTransportOptions::default()
        },
    )
    .unwrap();

    let plan = RequestPlan {
        requests: vec![RequestSpec {
            target: "http://example.com/redirect".to_string(),
            url: "http://example.com/redirect".to_string(),
            method: "GET".to_string(),
            headers: HashMap::new(),
            body: None,
            template_refs: vec![("test-template".to_string(), 0)],
            follow_redirects: true,
            max_redirects: 10,
            cookie_reuse: true,
        }],
        target_count: 1,
        template_count: 1,
        dedup_savings: 0,
    };

    let _ = transport.execute(&plan).collect::<Vec<_>>().await;
    let _ = proxy_server.await;

    let victim_result = victim_server.await.unwrap();
    assert!(
        victim_result.is_err(),
        "Redirects must be blocked when a proxy is configured"
    );
}
