use crate::{BadRequest, Cache, HttpCacheError};
use std::sync::Arc;

use crate::client_middleware::ClientBuilder;
use http_cache::*;
use reqwest::Client;
#[cfg(any(feature = "streaming", feature = "rate-limiting"))]
use wiremock::matchers::path;
use wiremock::{
    matchers::{header, method},
    Mock, MockServer, ResponseTemplate,
};

/// Helper function to create a temporary cache manager
fn create_cache_manager() -> CACacheManager {
    let cache_dir = tempfile::tempdir().expect("Failed to create temp dir");
    // Keep the temp dir alive by leaking it - it will be cleaned up when the process exits
    // This is acceptable for tests as they are short-lived
    let path = cache_dir.path().to_path_buf();
    std::mem::forget(cache_dir);
    CACacheManager::new(path, true)
}

pub(crate) fn build_mock(
    cache_control_val: &str,
    body: &[u8],
    status: u16,
    expect: u64,
) -> Mock {
    Mock::given(method(GET))
        .respond_with(
            ResponseTemplate::new(status)
                .insert_header("cache-control", cache_control_val)
                .set_body_bytes(body),
        )
        .expect(expect)
}

const GET: &str = "GET";

const TEST_BODY: &[u8] = b"test";

const CACHEABLE_PUBLIC: &str = "max-age=86400, public";

#[test]
#[allow(clippy::default_constructed_unit_structs)]
fn test_errors() -> Result<()> {
    // Testing the Debug, Default, and Clone traits for the error types
    let br = BadRequest::default();
    assert_eq!(format!("{:?}", br.clone()), "BadRequest",);
    assert_eq!(
        br.to_string(),
        "Request object is not cloneable. Are you passing a streaming body?"
            .to_string(),
    );

    // Test HttpCacheError
    let reqwest_err = HttpCacheError::cache("test cache error".to_string());
    assert!(format!("{:?}", reqwest_err).contains("Cache"));
    assert_eq!(
        reqwest_err.to_string(),
        "Cache error: test cache error".to_string(),
    );
    Ok(())
}

#[tokio::test]
async fn default_mode() -> Result<()> {
    let mock_server = MockServer::start().await;
    let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 1);
    let _mock_guard = mock_server.register_as_scoped(m).await;
    let url = format!("{}/", mock_server.uri());
    let manager = create_cache_manager();

    // Construct reqwest client with cache defaults
    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::Default,
            manager: manager.clone(),
            options: Default::default(),
        }))
        .build();

    // Cold pass to load cache
    client.get(url.clone()).send().await?;

    // Try to load cached object
    let data =
        CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
            .await?;
    assert!(data.is_some());

    // Hot pass to make sure the expect response was returned
    let res = client.get(url).send().await?;
    assert_eq!(res.bytes().await?, TEST_BODY);
    Ok(())
}

#[tokio::test]
async fn default_mode_with_options() -> Result<()> {
    let mock_server = MockServer::start().await;
    let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 1);
    let _mock_guard = mock_server.register_as_scoped(m).await;
    let url = format!("{}/", mock_server.uri());
    let manager = create_cache_manager();

    // Construct reqwest client with cache options override
    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::Default,
            manager: manager.clone(),
            options: HttpCacheOptions {
                cache_options: Some(CacheOptions {
                    shared: false,
                    ..Default::default()
                }),
                ..HttpCacheOptions::default()
            },
        }))
        .build();

    // Cold pass to load cache
    client.get(url.clone()).send().await?;

    // Try to load cached object
    let data =
        CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
            .await?;
    assert!(data.is_some());
    Ok(())
}

#[tokio::test]
async fn no_cache_mode() -> Result<()> {
    let mock_server = MockServer::start().await;
    let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 2);
    let _mock_guard = mock_server.register_as_scoped(m).await;
    let url = format!("{}/", mock_server.uri());
    let manager = create_cache_manager();

    // Construct reqwest client with cache defaults
    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::NoCache,
            manager: manager.clone(),
            options: Default::default(),
        }))
        .build();

    // Remote request and should cache
    client.get(url.clone()).send().await?;

    // Try to load cached object
    let data =
        CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
            .await?;
    assert!(data.is_some());

    // To verify our endpoint receives the request rather than a cache hit
    client.get(url).send().await?;
    Ok(())
}

#[tokio::test]
async fn reload_mode() -> Result<()> {
    let mock_server = MockServer::start().await;
    let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 2);
    let _mock_guard = mock_server.register_as_scoped(m).await;
    let url = format!("{}/", mock_server.uri());
    let manager = create_cache_manager();

    // Construct reqwest client with cache options override
    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::Reload,
            manager: manager.clone(),
            options: HttpCacheOptions {
                cache_options: Some(CacheOptions {
                    shared: false,
                    ..Default::default()
                }),
                ..HttpCacheOptions::default()
            },
        }))
        .build();

    // Cold pass to load cache
    client.get(url.clone()).send().await?;

    // Try to load cached object
    let data =
        CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
            .await?;
    assert!(data.is_some());

    // Another pass to make sure request is made to the endpoint
    client.get(url).send().await?;

    Ok(())
}

#[tokio::test]
async fn custom_cache_key() -> Result<()> {
    let mock_server = MockServer::start().await;
    let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 1);
    let _mock_guard = mock_server.register_as_scoped(m).await;
    let url = format!("{}/", mock_server.uri());
    let manager = create_cache_manager();

    // Construct reqwest client with cache defaults and custom cache key
    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::Default,
            manager: manager.clone(),
            options: HttpCacheOptions {
                cache_key: Some(Arc::new(|req: &http::request::Parts| {
                    format!("{}:{}:{:?}:test", req.method, req.uri, req.version)
                })),
                ..Default::default()
            },
        }))
        .build();

    // Remote request and should cache
    client.get(url.clone()).send().await?;

    // Try to load cached object
    let data = CacheManager::get(
        &manager,
        &format!("{}:{}:{:?}:test", GET, url, http::Version::HTTP_11),
    )
    .await?;

    assert!(data.is_some());
    Ok(())
}

#[tokio::test]
async fn custom_cache_mode_fn() -> Result<()> {
    let mock_server = MockServer::start().await;
    let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 2);
    let _mock_guard = mock_server.register_as_scoped(m).await;
    let url = format!("{}/test.css", mock_server.uri());
    let manager = create_cache_manager();

    // Construct reqwest client with cache defaults and custom cache mode
    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::NoStore,
            manager: manager.clone(),
            options: HttpCacheOptions {
                cache_key: None,
                cache_options: None,
                cache_mode_fn: Some(Arc::new(|req: &http::request::Parts| {
                    if req.uri.path().ends_with(".css") {
                        CacheMode::Default
                    } else {
                        CacheMode::NoStore
                    }
                })),
                cache_bust: None,
                cache_status_headers: true,
                max_ttl: None,
                ..Default::default()
            },
        }))
        .build();

    // Remote request and should cache
    client.get(url.clone()).send().await?;

    // Try to load cached object
    let data =
        CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
            .await?;
    assert!(data.is_some());

    let url = format!("{}/", mock_server.uri());
    // To verify our endpoint receives the request rather than a cache hit
    client.get(url.clone()).send().await?;

    // Check no cache object was created
    let data =
        CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
            .await?;
    assert!(data.is_none());

    Ok(())
}

#[tokio::test]
async fn override_cache_mode() -> Result<()> {
    let mock_server = MockServer::start().await;
    let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 2);
    let _mock_guard = mock_server.register_as_scoped(m).await;
    let url = format!("{}/test.css", mock_server.uri());
    let manager = create_cache_manager();

    // Construct reqwest client with cache defaults and custom cache mode
    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::Default,
            manager: manager.clone(),
            options: Default::default(),
        }))
        .build();

    // Remote request and should cache
    client.get(url.clone()).send().await?;

    // Try to load cached object
    let data =
        CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
            .await?;
    assert!(data.is_some());

    let url = format!("{}/", mock_server.uri());
    // To verify our endpoint receives the request rather than a cache hit
    client.get(url.clone()).with_extension(CacheMode::NoStore).send().await?;

    // Check no cache object was created
    let data =
        CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
            .await?;
    assert!(data.is_none());

    Ok(())
}

#[tokio::test]
async fn no_status_headers() -> Result<()> {
    let mock_server = MockServer::start().await;
    let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 1);
    let _mock_guard = mock_server.register_as_scoped(m).await;
    let url = format!("{}/test.css", mock_server.uri());
    let manager = create_cache_manager();

    // Construct reqwest client with cache defaults and custom cache mode
    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::Default,
            manager: manager.clone(),
            options: HttpCacheOptions {
                cache_key: None,
                cache_options: None,
                cache_mode_fn: None,
                cache_bust: None,
                cache_status_headers: false,
                max_ttl: None,
                ..Default::default()
            },
        }))
        .build();

    // Remote request and should cache
    let res = client.get(url.clone()).send().await?;

    // Try to load cached object
    let data =
        CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
            .await?;
    assert!(data.is_some());

    // Make sure the cache status headers aren't present in the response
    assert!(res.headers().get(XCACHELOOKUP).is_none());
    assert!(res.headers().get(XCACHE).is_none());

    Ok(())
}

#[tokio::test]
async fn cache_bust() -> Result<()> {
    let mock_server = MockServer::start().await;
    let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 2);
    let _mock_guard = mock_server.register_as_scoped(m).await;
    let url = format!("{}/", mock_server.uri());
    let manager = create_cache_manager();

    // Construct reqwest client with cache defaults and custom cache mode
    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::Default,
            manager: manager.clone(),
            options: HttpCacheOptions {
                cache_key: None,
                cache_options: None,
                cache_mode_fn: None,
                cache_bust: Some(Arc::new(
                    |req: &http::request::Parts, _, _| {
                        if req.uri.path().ends_with("/bust-cache") {
                            vec![format!(
                                "{}:{}://{}:{}/",
                                GET,
                                req.uri.scheme_str().unwrap(),
                                req.uri.host().unwrap(),
                                req.uri.port_u16().unwrap_or(80)
                            )]
                        } else {
                            Vec::new()
                        }
                    },
                )),
                cache_status_headers: true,
                max_ttl: None,
                ..Default::default()
            },
        }))
        .build();

    // Remote request and should cache
    client.get(url.clone()).send().await?;

    // Try to load cached object
    let data =
        CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
            .await?;
    assert!(data.is_some());

    // To verify our endpoint receives the request rather than a cache hit
    client.get(format!("{}/bust-cache", mock_server.uri())).send().await?;

    // Check cache object was busted
    let data =
        CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
            .await?;
    assert!(data.is_none());

    Ok(())
}

#[tokio::test]
async fn delete_after_non_get_head_method_request() -> Result<()> {
    let mock_server = MockServer::start().await;
    let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 1);
    let _mock_guard = mock_server.register_as_scoped(m).await;

    // Register a POST mock that returns 200 so invalidation triggers (RFC 7234 s4.4)
    let post_mock = Mock::given(method("POST"))
        .respond_with(ResponseTemplate::new(200).set_body_bytes(TEST_BODY))
        .expect(1);
    let _post_guard = mock_server.register_as_scoped(post_mock).await;

    let url = format!("{}/", mock_server.uri());
    let manager = create_cache_manager();

    // Construct reqwest client with cache defaults
    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::Default,
            manager: manager.clone(),
            options: Default::default(),
        }))
        .build();

    // Cold pass to load cache
    client.get(url.clone()).send().await?;

    // Try to load cached object
    let data =
        CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
            .await?;
    assert!(data.is_some());

    // Post request to make sure the cache object at the same resource was deleted
    // Invalidation only happens on successful response (RFC 7234 s4.4)
    client.post(url.clone()).send().await?;

    let data =
        CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
            .await?;
    assert!(data.is_none());

    Ok(())
}

#[tokio::test]
async fn default_mode_no_cache_response() -> Result<()> {
    let mock_server = MockServer::start().await;
    let m = build_mock("no-cache", TEST_BODY, 200, 2);
    let _mock_guard = mock_server.register_as_scoped(m).await;
    let url = format!("{}/", mock_server.uri());
    let manager = create_cache_manager();

    // Construct reqwest client with cache defaults
    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::Default,
            manager: manager.clone(),
            options: Default::default(),
        }))
        .build();

    // Cold pass to load cache
    client.get(url.clone()).send().await?;

    // Try to load cached object
    let data =
        CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
            .await?;
    assert!(data.is_some());

    // Hot pass to make sure the cached response was served but revalidated
    let res = client.get(url).send().await?;
    assert_eq!(res.bytes().await?, TEST_BODY);
    Ok(())
}

#[tokio::test]
async fn removes_warning() -> Result<()> {
    let mock_server = MockServer::start().await;
    let m = Mock::given(method(GET))
        .respond_with(
            ResponseTemplate::new(200)
                .insert_header("cache-control", CACHEABLE_PUBLIC)
                .insert_header("warning", "101 Test")
                .set_body_bytes(TEST_BODY),
        )
        .expect(1);
    let _mock_guard = mock_server.register_as_scoped(m).await;
    let url = format!("{}/", mock_server.uri());
    let manager = create_cache_manager();

    // Construct reqwest client with cache defaults
    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::Default,
            manager: manager.clone(),
            options: Default::default(),
        }))
        .build();

    // Cold pass to load cache
    client.get(url.clone()).send().await?;

    // Try to load cached object
    let data =
        CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
            .await?;
    assert!(data.is_some());

    // Hot pass to make sure the cached response was served without warning
    let res = client.get(url).send().await?;
    assert!(res.headers().get("warning").is_none());
    assert_eq!(res.bytes().await?, TEST_BODY);
    Ok(())
}

#[tokio::test]
async fn no_store_mode() -> Result<()> {
    let mock_server = MockServer::start().await;
    let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 2);
    let _mock_guard = mock_server.register_as_scoped(m).await;
    let url = format!("{}/", mock_server.uri());
    let manager = create_cache_manager();

    // Construct reqwest client with NoStore mode
    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::NoStore,
            manager: manager.clone(),
            options: Default::default(),
        }))
        .build();

    // Remote request but should not cache
    client.get(url.clone()).send().await?;

    // Try to load cached object
    let data =
        CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
            .await?;
    assert!(data.is_none());

    // Second request should go to remote again
    let res = client.get(url).send().await?;
    assert_eq!(res.bytes().await?, TEST_BODY);
    Ok(())
}

#[tokio::test]
async fn force_cache_mode() -> Result<()> {
    let mock_server = MockServer::start().await;
    let m = build_mock("max-age=0, public", TEST_BODY, 200, 1);
    let _mock_guard = mock_server.register_as_scoped(m).await;
    let url = format!("{}/", mock_server.uri());
    let manager = create_cache_manager();

    // Construct reqwest client with ForceCache mode
    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::ForceCache,
            manager: manager.clone(),
            options: Default::default(),
        }))
        .build();

    // Should result in a cache miss and a remote request
    client.get(url.clone()).send().await?;

    // Try to load cached object
    let data =
        CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
            .await?;
    assert!(data.is_some());

    // Should result in a cache hit and no remote request
    let res = client.get(url).send().await?;
    assert_eq!(res.bytes().await?, TEST_BODY);
    Ok(())
}

#[tokio::test]
async fn ignore_rules_mode() -> Result<()> {
    let mock_server = MockServer::start().await;
    let m = build_mock("no-store, max-age=0, public", TEST_BODY, 200, 1);
    let _mock_guard = mock_server.register_as_scoped(m).await;
    let url = format!("{}/", mock_server.uri());
    let manager = create_cache_manager();

    // Construct reqwest client with IgnoreRules mode
    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::IgnoreRules,
            manager: manager.clone(),
            options: Default::default(),
        }))
        .build();

    // Should result in a cache miss and a remote request
    client.get(url.clone()).send().await?;

    // Try to load cached object
    let data =
        CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
            .await?;
    assert!(data.is_some());

    // Should result in a cache hit and no remote request
    let res = client.get(url).send().await?;
    assert_eq!(res.bytes().await?, TEST_BODY);
    Ok(())
}

#[tokio::test]
async fn revalidation_304() -> Result<()> {
    let mock_server = MockServer::start().await;
    let m = build_mock("public, must-revalidate", TEST_BODY, 200, 1);
    let m_304 = Mock::given(method(GET))
        .respond_with(ResponseTemplate::new(304))
        .expect(1);
    let mock_guard = mock_server.register_as_scoped(m).await;
    let url = format!("{}/", mock_server.uri());
    let manager = create_cache_manager();

    // Construct reqwest client with cache defaults
    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::Default,
            manager: manager.clone(),
            options: Default::default(),
        }))
        .build();

    // Cold pass to load cache
    client.get(url.clone()).send().await?;

    drop(mock_guard);
    let _mock_guard = mock_server.register_as_scoped(m_304).await;

    // Try to load cached object
    let data =
        CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
            .await?;
    assert!(data.is_some());

    // Hot pass to make sure revalidation request was sent
    let res = client.get(url).send().await?;
    assert_eq!(res.bytes().await?, TEST_BODY);
    Ok(())
}

#[tokio::test]
async fn revalidation_200() -> Result<()> {
    let mock_server = MockServer::start().await;
    let m = build_mock("max-age=0, must-revalidate", TEST_BODY, 200, 1);
    let m_200 = build_mock("max-age=0, must-revalidate", b"updated", 200, 1);
    let mock_guard = mock_server.register_as_scoped(m).await;
    let url = format!("{}/", mock_server.uri());
    let manager = create_cache_manager();

    // Construct reqwest client with cache defaults
    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::Default,
            manager: manager.clone(),
            options: Default::default(),
        }))
        .build();

    // Cold pass to load cache
    client.get(url.clone()).send().await?;

    drop(mock_guard);
    let _mock_guard = mock_server.register_as_scoped(m_200).await;

    // Try to load cached object
    let data =
        CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
            .await?;
    assert!(data.is_some());

    // Hot pass to make sure revalidation request was sent
    let res = client.get(url).send().await?;
    assert_eq!(res.bytes().await?, &b"updated"[..]);
    Ok(())
}

#[tokio::test]
async fn no_duplicate_headers_on_revalidation() -> Result<()> {
    let mock_server = MockServer::start().await;

    // Both mocks require x-test: test exactly — wiremock will reject "test,test"
    let m = Mock::given(method(GET))
        .and(header("x-test", "test"))
        .respond_with(
            ResponseTemplate::new(200)
                .insert_header("cache-control", "max-age=0, must-revalidate")
                .set_body_bytes(TEST_BODY),
        )
        .expect(1);
    let m_revalidation = Mock::given(method(GET))
        .and(header("x-test", "test"))
        .respond_with(ResponseTemplate::new(304))
        .expect(1);

    let mock_guard = mock_server.register_as_scoped(m).await;
    let url = format!("{}/", mock_server.uri());
    let manager = create_cache_manager();

    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::Default,
            manager: manager.clone(),
            options: Default::default(),
        }))
        .build();

    // Cold pass to load cache
    client.get(url.clone()).header("x-test", "test").send().await?;

    drop(mock_guard);
    let _mock_guard = mock_server.register_as_scoped(m_revalidation).await;

    // Second request triggers revalidation — headers must not be duplicated
    let res = client.get(url).header("x-test", "test").send().await?;
    assert_eq!(res.bytes().await?, TEST_BODY);
    Ok(())
}

#[tokio::test]
async fn revalidation_500() -> Result<()> {
    let mock_server = MockServer::start().await;
    let m = build_mock("public, must-revalidate", TEST_BODY, 200, 1);
    let m_500 = Mock::given(method(GET))
        .respond_with(ResponseTemplate::new(500))
        .expect(1);
    let mock_guard = mock_server.register_as_scoped(m).await;
    let url = format!("{}/", mock_server.uri());
    let manager = create_cache_manager();

    // Construct reqwest client with cache defaults
    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::Default,
            manager: manager.clone(),
            options: Default::default(),
        }))
        .build();

    // Cold pass to load cache
    client.get(url.clone()).send().await?;

    drop(mock_guard);
    let _mock_guard = mock_server.register_as_scoped(m_500).await;

    // Try to load cached object
    let data =
        CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
            .await?;
    assert!(data.is_some());

    // Hot pass to make sure revalidation request was sent
    let res = client.get(url).send().await?;
    assert!(res.headers().get("warning").is_some());
    assert_eq!(res.bytes().await?, TEST_BODY);
    Ok(())
}

/// http-cache#117: with middlewest's `redirect` feature the cache runs on each
/// hop, so the uncacheable 307 is not stored and the target is keyed on its URL.
#[cfg(all(feature = "middlewest", feature = "redirect"))]
#[tokio::test]
async fn redirects_are_cached_per_hop() -> Result<()> {
    use crate::client_middleware::redirect::RedirectPolicy;
    use wiremock::matchers::path;

    let mock_server = MockServer::start().await;
    Mock::given(path("/"))
        .respond_with(
            ResponseTemplate::new(307)
                .insert_header("cache-control", "no-store")
                .insert_header("location", "/final"),
        )
        .mount(&mock_server)
        .await;
    Mock::given(path("/final"))
        .respond_with(
            ResponseTemplate::new(200)
                .insert_header("cache-control", CACHEABLE_PUBLIC)
                .set_body_bytes(TEST_BODY),
        )
        .mount(&mock_server)
        .await;

    let manager = create_cache_manager();
    let client = ClientBuilder::from_reqwest_builder(Client::builder())?
        .with(Cache(HttpCache {
            mode: CacheMode::Default,
            manager: manager.clone(),
            options: Default::default(),
        }))
        .redirect(RedirectPolicy::limited(10))
        .build();

    let root = format!("{}/", mock_server.uri());
    let target = format!("{}/final", mock_server.uri());
    let res = client.get(root.clone()).send().await?;
    assert_eq!(res.bytes().await?, TEST_BODY);

    assert!(
        CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&root)?))
            .await?
            .is_none(),
        "uncacheable 307 must not be cached under the original URL"
    );
    assert!(
        CacheManager::get(
            &manager,
            &format!("{}:{}", GET, url_parse(&target)?)
        )
        .await?
        .is_some(),
        "cacheable redirect target must be cached under its own URL"
    );
    Ok(())
}

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

    #[tokio::test]
    async fn miss() -> Result<()> {
        let mock_server = MockServer::start().await;
        let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 0);
        let _mock_guard = mock_server.register_as_scoped(m).await;
        let url = format!("{}/", mock_server.uri());
        let manager = create_cache_manager();

        // Construct reqwest client with OnlyIfCached mode
        let client = ClientBuilder::new(Client::new())
            .with(Cache(HttpCache {
                mode: CacheMode::OnlyIfCached,
                manager: manager.clone(),
                options: Default::default(),
            }))
            .build();

        // OnlyIfCached mode with no cached entry should return 504 Gateway Timeout
        let res = client.get(url.clone()).send().await?;
        assert_eq!(res.status(), 504);

        // Verify no entry was cached
        let data = CacheManager::get(
            &manager,
            &format!("{}:{}", GET, url_parse(&url)?),
        )
        .await?;
        assert!(data.is_none());
        Ok(())
    }

    #[tokio::test]
    async fn hit() -> Result<()> {
        let mock_server = MockServer::start().await;
        let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 1);
        let _mock_guard = mock_server.register_as_scoped(m).await;
        let url = format!("{}/", mock_server.uri());
        let manager = create_cache_manager();

        // First, load cache with Default mode
        let client = ClientBuilder::new(Client::new())
            .with(Cache(HttpCache {
                mode: CacheMode::Default,
                manager: manager.clone(),
                options: Default::default(),
            }))
            .build();

        // Cold pass to load the cache
        client.get(url.clone()).send().await?;

        // Try to load cached object
        let data = CacheManager::get(
            &manager,
            &format!("{}:{}", GET, url_parse(&url)?),
        )
        .await?;
        assert!(data.is_some());

        // Now construct client with OnlyIfCached mode
        let client = ClientBuilder::new(Client::new())
            .with(Cache(HttpCache {
                mode: CacheMode::OnlyIfCached,
                manager: manager.clone(),
                options: Default::default(),
            }))
            .build();

        // Should result in a cache hit and no remote request
        let res = client.get(url).send().await?;
        assert_eq!(res.bytes().await?, TEST_BODY);

        // Temporary directories are automatically cleaned up

        Ok(())
    }
}

#[tokio::test]
async fn head_request_caching() -> Result<()> {
    let mock_server = MockServer::start().await;
    let m = Mock::given(method("HEAD"))
        .respond_with(
            ResponseTemplate::new(200)
                .insert_header("cache-control", CACHEABLE_PUBLIC)
                .insert_header("content-type", "text/plain")
                .insert_header("content-length", "4"), // HEAD responses should not have a body
        )
        .expect(1);
    let _mock_guard = mock_server.register_as_scoped(m).await;
    let url = format!("{}/", mock_server.uri());
    let manager = create_cache_manager();

    // Construct reqwest client with cache defaults
    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::Default,
            manager: manager.clone(),
            options: Default::default(),
        }))
        .build();

    // HEAD request should be cached
    let res = client.head(url.clone()).send().await?;
    assert_eq!(res.status(), 200);
    assert_eq!(res.headers().get("content-type").unwrap(), "text/plain");
    // HEAD response should have no body but may have content-length header
    let body = res.bytes().await?;
    assert_eq!(body.len(), 0);

    // Try to load cached object - should use HEAD method in cache key
    let data =
        CacheManager::get(&manager, &format!("HEAD:{}", url_parse(&url)?))
            .await?;
    assert!(data.is_some());

    // Cached HEAD response should also have no body
    let cached_response = data.unwrap().0;
    assert_eq!(cached_response.status, 200);
    assert_eq!(cached_response.body.len(), 0);

    Ok(())
}

#[tokio::test]
async fn head_request_cached_like_get() -> Result<()> {
    let mock_server = MockServer::start().await;

    // Mock GET request
    let m_get = Mock::given(method("GET"))
        .respond_with(
            ResponseTemplate::new(200)
                .insert_header("cache-control", CACHEABLE_PUBLIC)
                .insert_header("content-type", "text/plain")
                .insert_header("etag", "\"12345\"")
                .set_body_bytes(TEST_BODY),
        )
        .expect(1);

    // Mock HEAD request - should return same headers but no body
    let m_head = Mock::given(method("HEAD"))
        .respond_with(
            ResponseTemplate::new(200)
                .insert_header("cache-control", CACHEABLE_PUBLIC)
                .insert_header("content-type", "text/plain")
                .insert_header("etag", "\"12345\"")
                .insert_header("content-length", "4"),
        )
        .expect(1);

    let mock_guard_get = mock_server.register_as_scoped(m_get).await;
    let url = format!("{}/", mock_server.uri());
    let manager = create_cache_manager();

    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::Default,
            manager: manager.clone(),
            options: Default::default(),
        }))
        .build();

    // First, cache a GET response
    let get_res = client.get(url.clone()).send().await?;
    assert_eq!(get_res.status(), 200);
    assert_eq!(get_res.bytes().await?, TEST_BODY);

    drop(mock_guard_get);
    let _mock_guard_head = mock_server.register_as_scoped(m_head).await;

    // HEAD request should be able to use cached GET response metadata
    // but still make a HEAD request to verify headers
    let head_res = client.head(url.clone()).send().await?;
    assert_eq!(head_res.status(), 200);
    assert_eq!(head_res.headers().get("etag").unwrap(), "\"12345\"");

    // Verify both GET and HEAD cache entries exist
    let get_data =
        CacheManager::get(&manager, &format!("GET:{}", url_parse(&url)?))
            .await?;
    assert!(get_data.is_some());

    let head_data =
        CacheManager::get(&manager, &format!("HEAD:{}", url_parse(&url)?))
            .await?;
    assert!(head_data.is_some());

    Ok(())
}

#[tokio::test]
async fn put_request_invalidates_cache() -> Result<()> {
    let mock_server = MockServer::start().await;

    // Mock GET request for caching
    let m_get = Mock::given(method("GET"))
        .respond_with(
            ResponseTemplate::new(200)
                .insert_header("cache-control", CACHEABLE_PUBLIC)
                .set_body_bytes(TEST_BODY),
        )
        .expect(1);

    // Mock PUT request that should invalidate cache
    let m_put = Mock::given(method("PUT"))
        .respond_with(ResponseTemplate::new(204))
        .expect(1);

    let mock_guard_get = mock_server.register_as_scoped(m_get).await;
    let url = format!("{}/", mock_server.uri());
    let manager = create_cache_manager();

    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::Default,
            manager: manager.clone(),
            options: Default::default(),
        }))
        .build();

    // First, cache a GET response
    client.get(url.clone()).send().await?;

    // Verify it's cached
    let data =
        CacheManager::get(&manager, &format!("GET:{}", url_parse(&url)?))
            .await?;
    assert!(data.is_some());

    drop(mock_guard_get);
    let _mock_guard_put = mock_server.register_as_scoped(m_put).await;

    // PUT request should invalidate the cached GET response
    let put_res = client.put(url.clone()).send().await?;
    assert_eq!(put_res.status(), 204);

    // Verify cache was invalidated
    let data =
        CacheManager::get(&manager, &format!("GET:{}", url_parse(&url)?))
            .await?;
    assert!(data.is_none());

    Ok(())
}

#[tokio::test]
async fn patch_request_invalidates_cache() -> Result<()> {
    let mock_server = MockServer::start().await;

    let m_get = Mock::given(method("GET"))
        .respond_with(
            ResponseTemplate::new(200)
                .insert_header("cache-control", CACHEABLE_PUBLIC)
                .set_body_bytes(TEST_BODY),
        )
        .expect(1);

    let m_patch = Mock::given(method("PATCH"))
        .respond_with(ResponseTemplate::new(200))
        .expect(1);

    let mock_guard_get = mock_server.register_as_scoped(m_get).await;
    let url = format!("{}/", mock_server.uri());
    let manager = create_cache_manager();

    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::Default,
            manager: manager.clone(),
            options: Default::default(),
        }))
        .build();

    // Cache a GET response
    client.get(url.clone()).send().await?;

    // Verify it's cached
    let data =
        CacheManager::get(&manager, &format!("GET:{}", url_parse(&url)?))
            .await?;
    assert!(data.is_some());

    drop(mock_guard_get);
    let _mock_guard_patch = mock_server.register_as_scoped(m_patch).await;

    // PATCH request should invalidate cache
    let patch_res = client.patch(url.clone()).send().await?;
    assert_eq!(patch_res.status(), 200);

    // Verify cache was invalidated
    let data =
        CacheManager::get(&manager, &format!("GET:{}", url_parse(&url)?))
            .await?;
    assert!(data.is_none());

    Ok(())
}

#[tokio::test]
async fn delete_request_invalidates_cache() -> Result<()> {
    let mock_server = MockServer::start().await;

    let m_get = Mock::given(method("GET"))
        .respond_with(
            ResponseTemplate::new(200)
                .insert_header("cache-control", CACHEABLE_PUBLIC)
                .set_body_bytes(TEST_BODY),
        )
        .expect(1);

    let m_delete = Mock::given(method("DELETE"))
        .respond_with(ResponseTemplate::new(204))
        .expect(1);

    let mock_guard_get = mock_server.register_as_scoped(m_get).await;
    let url = format!("{}/", mock_server.uri());
    let manager = create_cache_manager();

    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::Default,
            manager: manager.clone(),
            options: Default::default(),
        }))
        .build();

    // Cache a GET response
    client.get(url.clone()).send().await?;

    // Verify it's cached
    let data =
        CacheManager::get(&manager, &format!("GET:{}", url_parse(&url)?))
            .await?;
    assert!(data.is_some());

    drop(mock_guard_get);
    let _mock_guard_delete = mock_server.register_as_scoped(m_delete).await;

    // DELETE request should invalidate cache
    let delete_res = client.delete(url.clone()).send().await?;
    assert_eq!(delete_res.status(), 204);

    // Verify cache was invalidated
    let data =
        CacheManager::get(&manager, &format!("GET:{}", url_parse(&url)?))
            .await?;
    assert!(data.is_none());

    Ok(())
}

#[tokio::test]
async fn options_request_not_cached() -> Result<()> {
    let mock_server = MockServer::start().await;
    let m = Mock::given(method("OPTIONS"))
        .respond_with(
            ResponseTemplate::new(200)
                .insert_header("allow", "GET, POST, PUT, DELETE")
                .insert_header("cache-control", CACHEABLE_PUBLIC), // Even with cache headers
        )
        .expect(2); // Should be called twice since not cached
    let _mock_guard = mock_server.register_as_scoped(m).await;
    let url = format!("{}/", mock_server.uri());
    let manager = create_cache_manager();

    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::Default,
            manager: manager.clone(),
            options: Default::default(),
        }))
        .build();

    // First OPTIONS request
    let res1 =
        client.request(reqwest::Method::OPTIONS, url.clone()).send().await?;
    assert_eq!(res1.status(), 200);

    // Verify it's not cached
    let data =
        CacheManager::get(&manager, &format!("OPTIONS:{}", url_parse(&url)?))
            .await?;
    assert!(data.is_none());

    // Second OPTIONS request should hit the server again
    let res2 =
        client.request(reqwest::Method::OPTIONS, url.clone()).send().await?;
    assert_eq!(res2.status(), 200);

    Ok(())
}

#[cfg(feature = "streaming")]
#[tokio::test]
async fn test_multipart_form_cloning_issue() -> Result<()> {
    // This test reproduces the exact issue reported by the user
    // where multipart forms cause "Request object is not cloneable" errors

    let manager = CACacheManager::new(".cache".into(), true);
    let mock_server = MockServer::start().await;

    // Mock an API endpoint that accepts multipart forms
    let mock = Mock::given(method("POST"))
        .and(path("/api/upload"))
        .respond_with(
            ResponseTemplate::new(200)
                .insert_header("content-type", "application/json")
                .insert_header("cache-control", "no-cache") // Should not be cached anyway
                .set_body_bytes(r#"{"status": "uploaded"}"#),
        )
        .expect(1); // Should be called once since cache is bypassed

    let _mock_guard = mock_server.register_as_scoped(mock).await;

    // Create cached client
    let client = ClientBuilder::new(
        Client::builder()
            .build()
            .expect("should be able to construct reqwest client"),
    )
    .with(Cache(HttpCache {
        mode: CacheMode::Default,
        manager,
        options: Default::default(),
    }))
    .build();

    // Create a streaming body that should cause cloning issues
    // We need to create a body that can't be cloned - like a stream
    use bytes::Bytes;
    use futures_util::stream;
    use reqwest::Body;

    let file_content = b"fake file content for testing";
    // Create a stream that can't be cloned
    let stream = stream::iter(vec![Ok::<_, reqwest::Error>(Bytes::from(
        file_content.to_vec(),
    ))]);
    let body = Body::wrap_stream(stream);

    let url = format!("{}/api/upload", mock_server.uri());

    // This should reproduce the cloning error when the cache middleware
    // tries to clone the request for cache analysis
    let result = client
        .post(&url)
        .header("Accept", "application/json")
        .header("api-key", "test-key")
        .header("content-type", "application/octet-stream")
        .body(body)
        .send()
        .await;

    // With the graceful fallback fix, the request should now succeed
    // by bypassing the cache entirely
    match result {
        Ok(response) => {
            // This is what we expect - graceful fallback working
            assert_eq!(response.status(), 200);
        }
        Err(e) => {
            panic!("Expected graceful fallback, but got error: {}", e);
        }
    }

    Ok(())
}

#[cfg(all(test, feature = "streaming"))]
mod streaming_tests {
    use super::*;
    use crate::{HttpCacheStreamInterface, HttpStreamingCache, StreamingBody};
    use bytes::Bytes;
    use http::{Request, Response};
    use http_body::Body;
    use http_body_util::{BodyExt, Full};
    use http_cache::StreamingManager;
    /// Helper function to create a streaming cache manager
    async fn create_streaming_cache_manager() -> StreamingManager {
        StreamingManager::with_temp_dir(1000)
            .await
            .expect("Failed to create streaming cache manager")
    }

    #[tokio::test]
    async fn test_streaming_cache_basic_operations() -> Result<()> {
        let manager = create_streaming_cache_manager().await;
        let cache = HttpStreamingCache {
            mode: CacheMode::Default,
            manager,
            options: Default::default(),
        };

        // Create a test request
        let request = Request::builder()
            .uri("https://example.com/test")
            .header("user-agent", "test-agent")
            .body(())
            .unwrap();

        // Analyze the request
        let (parts, _) = request.into_parts();
        let analysis = cache.analyze_request(&parts, None)?;
        assert!(!analysis.cache_key.is_empty());
        assert!(analysis.should_cache);

        // Test cache miss
        let cached_response =
            cache.lookup_cached_response(&analysis.cache_key).await?;
        assert!(cached_response.is_none());

        // Create a response to cache
        let response = Response::builder()
            .status(200)
            .header("content-type", "application/json")
            .header("cache-control", "max-age=3600")
            .body(Full::new(Bytes::from("streaming test data")))
            .unwrap();

        // Process and cache the response
        let cached_response =
            cache.process_response(analysis.clone(), response, None).await?;
        assert_eq!(cached_response.status(), 200);

        // Verify the response body
        let body_bytes =
            cached_response.into_body().collect().await?.to_bytes();
        assert_eq!(body_bytes, "streaming test data");

        // Test cache hit
        let cached_response =
            cache.lookup_cached_response(&analysis.cache_key).await?;
        assert!(cached_response.is_some());

        if let Some((response, _policy)) = cached_response {
            let body_bytes = response.into_body().collect().await?.to_bytes();
            assert_eq!(body_bytes, "streaming test data");
        }

        Ok(())
    }

    #[tokio::test]
    async fn test_streaming_cache_large_response() -> Result<()> {
        let manager = create_streaming_cache_manager().await;
        let cache = HttpStreamingCache {
            mode: CacheMode::Default,
            manager,
            options: Default::default(),
        };

        // Create a large test response (1MB)
        let large_data = "x".repeat(1024 * 1024);
        let request = Request::builder()
            .uri("https://example.com/large")
            .body(())
            .unwrap();

        let (parts, _) = request.into_parts();
        let analysis = cache.analyze_request(&parts, None)?;

        let response = Response::builder()
            .status(200)
            .header("content-type", "text/plain")
            .header("cache-control", "max-age=3600")
            .body(Full::new(Bytes::from(large_data.clone())))
            .unwrap();

        // Process the large response
        let cached_response =
            cache.process_response(analysis.clone(), response, None).await?;
        assert_eq!(cached_response.status(), 200);

        // Verify the large response body
        let body_bytes =
            cached_response.into_body().collect().await?.to_bytes();
        assert_eq!(body_bytes.len(), 1024 * 1024);
        assert_eq!(body_bytes, large_data.as_bytes());

        // Verify it's cached properly
        let cached_response =
            cache.lookup_cached_response(&analysis.cache_key).await?;
        assert!(cached_response.is_some());

        Ok(())
    }

    #[tokio::test]
    async fn test_streaming_cache_empty_response() -> Result<()> {
        let manager = create_streaming_cache_manager().await;
        let cache = HttpStreamingCache {
            mode: CacheMode::Default,
            manager,
            options: Default::default(),
        };

        let request = Request::builder()
            .uri("https://example.com/empty")
            .body(())
            .unwrap();

        let (parts, _) = request.into_parts();
        let analysis = cache.analyze_request(&parts, None)?;

        let response = Response::builder()
            .status(204)
            .header("cache-control", "max-age=3600")
            .body(Full::new(Bytes::new()))
            .unwrap();

        // Process the empty response
        let cached_response =
            cache.process_response(analysis.clone(), response, None).await?;
        assert_eq!(cached_response.status(), 204);

        // Verify empty body
        let body_bytes =
            cached_response.into_body().collect().await?.to_bytes();
        assert!(body_bytes.is_empty());

        // Verify it's cached
        let cached_response =
            cache.lookup_cached_response(&analysis.cache_key).await?;
        assert!(cached_response.is_some());

        Ok(())
    }

    /// A HEAD response carries the entity's Content-Length with an empty
    /// body; the orchestrator must tag it with `CachedRequestMethod` or the
    /// manager's completeness check declines it and HEAD is never cached.
    #[tokio::test]
    async fn test_streaming_cache_head_response_is_cached() -> Result<()> {
        let manager = create_streaming_cache_manager().await;
        let cache = HttpStreamingCache {
            mode: CacheMode::Default,
            manager,
            options: Default::default(),
        };

        let request = Request::builder()
            .method("HEAD")
            .uri("https://example.com/head")
            .body(())
            .unwrap();
        let (parts, _) = request.into_parts();
        let analysis = cache.analyze_request(&parts, None)?;

        let response = Response::builder()
            .status(200)
            .header("cache-control", "max-age=3600")
            .header("content-length", "13")
            .body(Full::new(Bytes::new()))
            .unwrap();
        cache.process_response(analysis.clone(), response, None).await?;

        assert!(
            cache.lookup_cached_response(&analysis.cache_key).await?.is_some(),
            "HEAD response must be cached"
        );
        Ok(())
    }

    #[tokio::test]
    async fn test_streaming_cache_no_cache_mode() -> Result<()> {
        let manager = create_streaming_cache_manager().await;
        let cache = HttpStreamingCache {
            mode: CacheMode::NoStore,
            manager,
            options: Default::default(),
        };

        let request = Request::builder()
            .uri("https://example.com/no-cache")
            .body(())
            .unwrap();

        let (parts, _) = request.into_parts();
        let analysis = cache.analyze_request(&parts, None)?;

        // Should not cache when mode is NoStore
        assert!(!analysis.should_cache);

        Ok(())
    }

    #[tokio::test]
    async fn test_streaming_body_operations() -> Result<()> {
        // Test buffered streaming body
        let data = Bytes::from("test streaming body data");
        let buffered_body: StreamingBody<Full<Bytes>> =
            StreamingBody::buffered(data.clone());

        assert!(!buffered_body.is_end_stream());

        // Test size hint
        let size_hint = buffered_body.size_hint();
        assert_eq!(size_hint.exact(), Some(data.len() as u64));

        // Test body collection
        let collected = buffered_body.collect().await?.to_bytes();
        assert_eq!(collected, data);

        Ok(())
    }

    #[tokio::test]
    async fn custom_response_cache_mode_fn() -> Result<()> {
        let mock_server = MockServer::start().await;

        // Mock endpoint that returns 200 with no-cache headers
        let no_cache_mock = Mock::given(method(GET))
            .and(path("/api/data"))
            .respond_with(
                ResponseTemplate::new(200)
                    .insert_header(
                        "cache-control",
                        "no-cache, no-store, must-revalidate",
                    )
                    .insert_header("pragma", "no-cache")
                    .set_body_bytes(TEST_BODY),
            )
            .expect(2);

        // Mock endpoint that returns 429 with cacheable headers
        let rate_limit_mock = Mock::given(method(GET))
            .and(path("/api/rate-limited"))
            .respond_with(
                ResponseTemplate::new(429)
                    .insert_header("cache-control", "public, max-age=300")
                    .insert_header("retry-after", "60")
                    .set_body_bytes(b"Rate limit exceeded"),
            )
            .expect(2);

        let _no_cache_guard =
            mock_server.register_as_scoped(no_cache_mock).await;
        let _rate_limit_guard =
            mock_server.register_as_scoped(rate_limit_mock).await;

        let manager = create_cache_manager();

        // Configure cache with response-based mode override
        let client = ClientBuilder::new(Client::new())
            .with(Cache(HttpCache {
                mode: CacheMode::Default,
                manager: manager.clone(),
                options: HttpCacheOptions {
                    response_cache_mode_fn: Some(Arc::new(
                        |_request_parts, response| {
                            match response.status {
                                // Force cache 2xx responses even if headers say not to cache
                                200..=299 => Some(CacheMode::ForceCache),
                                // Never cache rate-limited responses
                                429 => Some(CacheMode::NoStore),
                                _ => None, // Use default behavior
                            }
                        },
                    )),
                    ..Default::default()
                },
            }))
            .build();

        // Test 1: Force cache 200 response despite no-cache headers
        let success_url = format!("{}/api/data", mock_server.uri());
        let response = client.get(&success_url).send().await?;
        assert_eq!(response.status(), 200);

        // Verify it was cached despite no-cache headers
        let cache_key = format!("{}:{}", GET, url_parse(&success_url)?);
        let cached_data = CacheManager::get(&manager, &cache_key).await?;
        assert!(cached_data.is_some());
        let (cached_response, _) = cached_data.unwrap();
        assert_eq!(cached_response.body, TEST_BODY);

        // Test 2: Don't cache 429 response despite cacheable headers
        let rate_limit_url = format!("{}/api/rate-limited", mock_server.uri());
        let response = client.get(&rate_limit_url).send().await?;
        assert_eq!(response.status(), 429);

        // Verify it was NOT cached despite cacheable headers
        let cache_key = format!("{}:{}", GET, url_parse(&rate_limit_url)?);
        let cached_data = CacheManager::get(&manager, &cache_key).await?;
        assert!(cached_data.is_none());

        // Test hitting the same endpoints again to verify cache behavior
        let response = client.get(&success_url).send().await?;
        assert_eq!(response.status(), 200);

        let response = client.get(&rate_limit_url).send().await?;
        assert_eq!(response.status(), 429);

        Ok(())
    }

    #[tokio::test]
    async fn streaming_with_different_cache_modes() -> Result<()> {
        let manager = create_streaming_cache_manager().await;

        // Test with NoCache mode
        let cache_no_cache = HttpStreamingCache {
            mode: CacheMode::NoCache,
            manager: manager.clone(),
            options: Default::default(),
        };

        let request = Request::builder()
            .uri("https://example.com/streaming-no-cache")
            .header("user-agent", "test-agent")
            .body(())
            .unwrap();

        let (parts, _) = request.into_parts();
        let analysis = cache_no_cache.analyze_request(&parts, None)?;

        // Should analyze but mode affects caching behavior
        assert!(!analysis.cache_key.is_empty());

        // Test with ForceCache mode
        let cache_force = HttpStreamingCache {
            mode: CacheMode::ForceCache,
            manager: manager.clone(),
            options: Default::default(),
        };

        let request2 = Request::builder()
            .uri("https://example.com/streaming-force-cache")
            .header("user-agent", "test-agent")
            .body(())
            .unwrap();

        let (parts2, _) = request2.into_parts();
        let analysis2 = cache_force.analyze_request(&parts2, None)?;

        assert!(!analysis2.cache_key.is_empty());
        assert!(analysis2.should_cache);

        Ok(())
    }

    #[tokio::test]
    async fn streaming_with_custom_cache_options() -> Result<()> {
        let manager = create_streaming_cache_manager().await;

        let cache = HttpStreamingCache {
            mode: CacheMode::Default,
            manager,
            options: HttpCacheOptions {
                cache_key: Some(Arc::new(|req: &http::request::Parts| {
                    format!("stream:{}:{}", req.method, req.uri)
                })),
                cache_options: Some(CacheOptions {
                    shared: false,
                    ..Default::default()
                }),
                cache_mode_fn: Some(Arc::new(|req: &http::request::Parts| {
                    if req.uri.path().contains("stream") {
                        CacheMode::ForceCache
                    } else {
                        CacheMode::Default
                    }
                })),
                cache_bust: None,
                cache_status_headers: false,
                max_ttl: None,
                ..Default::default()
            },
        };

        // Test custom cache key generation
        let request = Request::builder()
            .uri("https://example.com/streaming-custom")
            .header("user-agent", "test-agent")
            .body(())
            .unwrap();

        let (parts, _) = request.into_parts();
        let analysis = cache.analyze_request(&parts, None)?;

        assert_eq!(
            analysis.cache_key,
            "stream:GET:https://example.com/streaming-custom"
        );
        assert!(analysis.should_cache); // ForceCache mode due to custom function

        Ok(())
    }

    #[tokio::test]
    async fn streaming_error_handling() -> Result<()> {
        let manager = create_streaming_cache_manager().await;
        let cache = HttpStreamingCache {
            mode: CacheMode::Default,
            manager,
            options: Default::default(),
        };

        // Test with malformed request
        let request =
            Request::builder().uri("not-a-valid-uri").body(()).unwrap();

        let (parts, _) = request.into_parts();

        // Should handle gracefully and not panic
        let result = cache.analyze_request(&parts, None);
        // The analyze_request should succeed even with unusual URIs
        assert!(result.is_ok());

        Ok(())
    }

    #[tokio::test]
    async fn streaming_concurrent_access() -> Result<()> {
        use tokio::task::JoinSet;

        let manager = create_streaming_cache_manager().await;
        let cache = Arc::new(HttpStreamingCache {
            mode: CacheMode::Default,
            manager,
            options: Default::default(),
        });

        let mut join_set = JoinSet::new();

        // Spawn multiple concurrent tasks
        for i in 0..10 {
            let cache_clone = cache.clone();
            join_set.spawn(async move {
                let request = Request::builder()
                    .uri(format!("https://example.com/concurrent-{i}"))
                    .header("user-agent", "test-agent")
                    .body(())
                    .unwrap();

                let (parts, _) = request.into_parts();
                cache_clone.analyze_request(&parts, None)
            });
        }

        // Collect all results
        let mut results = Vec::new();
        while let Some(result) = join_set.join_next().await {
            results.push(result.unwrap());
        }

        // All should succeed
        assert_eq!(results.len(), 10);
        for result in results {
            assert!(result.is_ok());
        }

        Ok(())
    }

    #[tokio::test]
    async fn streaming_with_request_extensions() -> Result<()> {
        let manager = create_streaming_cache_manager().await;
        let cache = HttpStreamingCache {
            mode: CacheMode::Default,
            manager,
            options: Default::default(),
        };

        // Test with request that has extensions (simulating middleware data)
        let mut request = Request::builder()
            .uri("https://example.com/with-extensions")
            .header("user-agent", "test-agent")
            .body(())
            .unwrap();

        // Add some extension data
        request.extensions_mut().insert("custom_data".to_string());

        let (parts, _) = request.into_parts();
        let analysis = cache.analyze_request(&parts, None)?;

        // Should handle requests with extensions normally
        assert!(!analysis.cache_key.is_empty());
        assert!(analysis.should_cache);

        Ok(())
    }

    #[tokio::test]
    async fn streaming_cache_with_vary_headers() -> Result<()> {
        let manager = create_streaming_cache_manager().await;
        let cache = HttpStreamingCache {
            mode: CacheMode::Default,
            manager,
            options: Default::default(),
        };

        // Create a request with headers that could affect caching via Vary
        let request = Request::builder()
            .uri("https://example.com/vary-test")
            .header("user-agent", "test-agent")
            .header("accept-encoding", "gzip, deflate")
            .header("accept-language", "en-US,en;q=0.9")
            .body(())
            .unwrap();

        let (parts, _) = request.into_parts();
        let analysis = cache.analyze_request(&parts, None)?;

        // Create a response with Vary headers
        let response = Response::builder()
            .status(200)
            .header("content-type", "application/json")
            .header("cache-control", "max-age=3600")
            .header("vary", "Accept-Encoding, Accept-Language")
            .body(Full::new(Bytes::from("vary test data")))
            .unwrap();

        // Process the response
        let cached_response =
            cache.process_response(analysis.clone(), response, None).await?;
        assert_eq!(cached_response.status(), 200);

        // Verify the body
        let body_bytes =
            cached_response.into_body().collect().await?.to_bytes();
        assert_eq!(body_bytes, "vary test data");

        // Test cache lookup
        let cached_response =
            cache.lookup_cached_response(&analysis.cache_key).await?;
        assert!(cached_response.is_some());

        Ok(())
    }

    #[tokio::test]
    async fn streaming_cache_with_multiple_vary_headers() -> Result<()> {
        let manager = create_streaming_cache_manager().await;
        let cache = HttpStreamingCache {
            mode: CacheMode::Default,
            manager,
            options: Default::default(),
        };

        // Create a request
        let request = Request::builder()
            .uri("https://example.com/multiple-vary-test")
            .header("user-agent", "test-agent")
            .header("accept-encoding", "gzip")
            .header("accept-language", "en-US")
            .body(())
            .unwrap();

        let (parts, _) = request.into_parts();
        let analysis = cache.analyze_request(&parts, None)?;

        // Create a response with MULTIPLE Vary headers (the issue from #119)
        let mut response = Response::builder()
            .status(200)
            .header("content-type", "application/json")
            .header("cache-control", "max-age=3600")
            .body(Full::new(Bytes::from("multiple vary test data")))
            .unwrap();

        // Add multiple Vary headers using headers_mut()
        let headers = response.headers_mut();
        headers.append("vary", "Prefer".parse().unwrap());
        headers.append("vary", "Accept".parse().unwrap());
        headers.append("vary", "Range".parse().unwrap());
        headers.append("vary", "Accept-Encoding".parse().unwrap());
        headers.append("vary", "Accept-Language".parse().unwrap());
        headers.append("vary", "Accept-Datetime".parse().unwrap());

        // Process the response
        let cached_response =
            cache.process_response(analysis.clone(), response, None).await?;
        assert_eq!(cached_response.status(), 200);

        // Verify the body
        let body_bytes =
            cached_response.into_body().collect().await?.to_bytes();
        assert_eq!(body_bytes, "multiple vary test data");

        // Test cache lookup - this is where the bug would show up
        let cached_result =
            cache.lookup_cached_response(&analysis.cache_key).await?;
        assert!(cached_result.is_some());

        if let Some((response, _policy)) = cached_result {
            // Get ALL Vary header values
            let vary_values: Vec<_> = response
                .headers()
                .get_all("vary")
                .iter()
                .map(|v| v.to_str().unwrap())
                .collect();

            // StreamingManager stores metadata directly in moka's in-memory cache
            // WITHOUT serialization, so http-headers-compat doesn't affect it.
            // Headers always remain in Modern format with separate values.
            // (The compat feature only affects disk-serialized caches like CACacheManager)
            assert_eq!(
                vary_values.len(),
                6,
                "Expected 6 Vary headers, got {}: {:?}",
                vary_values.len(),
                vary_values
            );

            // Verify all expected values are present
            assert!(vary_values.contains(&"Prefer"));
            assert!(vary_values.contains(&"Accept"));
            assert!(vary_values.contains(&"Range"));
            assert!(vary_values.contains(&"Accept-Encoding"));
            assert!(vary_values.contains(&"Accept-Language"));
            assert!(vary_values.contains(&"Accept-Datetime"));
        }

        Ok(())
    }

    #[cfg(feature = "rate-limiting")]
    #[tokio::test]
    async fn test_streaming_with_rate_limiting() -> Result<()> {
        use crate::{CacheAwareRateLimiter, StreamingCache};
        use std::sync::{Arc, Mutex};
        use std::time::{Duration, Instant};

        // Mock rate limiter for testing rate limiting behavior
        #[derive(Debug)]
        struct MockStreamingRateLimiter {
            calls: Arc<Mutex<Vec<String>>>,
            delay: Duration,
        }

        impl MockStreamingRateLimiter {
            fn new(delay: Duration) -> Self {
                Self { calls: Arc::new(Mutex::new(Vec::new())), delay }
            }
        }

        impl CacheAwareRateLimiter for MockStreamingRateLimiter {
            fn until_key_ready(
                &self,
                key: &str,
            ) -> std::pin::Pin<
                Box<dyn std::future::Future<Output = ()> + Send + '_>,
            > {
                let key = key.to_string();
                Box::pin(async move {
                    self.calls.lock().unwrap().push(key);
                    if self.delay > Duration::ZERO {
                        tokio::time::sleep(self.delay).await;
                    }
                })
            }

            fn check_key(&self, _key: &str) -> bool {
                true // Always allow for testing
            }
        }

        let manager = create_streaming_cache_manager().await;
        let rate_limiter =
            MockStreamingRateLimiter::new(Duration::from_millis(50));
        let call_counter = rate_limiter.calls.clone();

        let options = HttpCacheOptions {
            rate_limiter: Some(Arc::new(rate_limiter)),
            ..HttpCacheOptions::default()
        };

        let client = ClientBuilder::new(Client::new())
            .with(StreamingCache::with_options(
                manager,
                CacheMode::Default,
                options,
            ))
            .build();

        let mock_server = MockServer::start().await;
        let url = format!("{}/streaming-rate-limited", mock_server.uri());

        // Mock non-cacheable response to ensure network requests
        Mock::given(method("GET"))
            .and(path("/streaming-rate-limited"))
            .respond_with(
                ResponseTemplate::new(200)
                    .insert_header("cache-control", "no-cache")
                    .set_body_bytes(b"streaming response"),
            )
            .expect(2)
            .mount(&mock_server)
            .await;

        // First request - should apply rate limiting
        let start = Instant::now();
        let _response1 = client.get(&url).send().await?;
        let first_duration = start.elapsed();

        assert_eq!(call_counter.lock().unwrap().len(), 1);
        assert!(
            first_duration >= Duration::from_millis(50),
            "First request should be rate limited"
        );

        // Second request - should also apply rate limiting (not cached due to no-cache)
        let start = Instant::now();
        let _response2 = client.get(&url).send().await?;
        let second_duration = start.elapsed();

        assert_eq!(call_counter.lock().unwrap().len(), 2);
        assert!(
            second_duration >= Duration::from_millis(50),
            "Second request should also be rate limited"
        );

        Ok(())
    }

    #[cfg(feature = "rate-limiting")]
    #[tokio::test]
    async fn test_streaming_cache_hit_bypasses_rate_limiting() -> Result<()> {
        use crate::{CacheAwareRateLimiter, StreamingCache};
        use std::sync::{Arc, Mutex};
        use std::time::{Duration, Instant};

        // Mock rate limiter
        #[derive(Debug)]
        struct MockStreamingRateLimiter {
            calls: Arc<Mutex<Vec<String>>>,
            delay: Duration,
        }

        impl MockStreamingRateLimiter {
            fn new(delay: Duration) -> Self {
                Self { calls: Arc::new(Mutex::new(Vec::new())), delay }
            }
        }

        impl CacheAwareRateLimiter for MockStreamingRateLimiter {
            fn until_key_ready(
                &self,
                key: &str,
            ) -> std::pin::Pin<
                Box<dyn std::future::Future<Output = ()> + Send + '_>,
            > {
                let key = key.to_string();
                Box::pin(async move {
                    self.calls.lock().unwrap().push(key);
                    if self.delay > Duration::ZERO {
                        tokio::time::sleep(self.delay).await;
                    }
                })
            }

            fn check_key(&self, _key: &str) -> bool {
                true // Always allow for testing
            }
        }

        let manager = create_streaming_cache_manager().await;
        let rate_limiter =
            MockStreamingRateLimiter::new(Duration::from_millis(50));
        let call_counter = rate_limiter.calls.clone();

        let options = HttpCacheOptions {
            rate_limiter: Some(Arc::new(rate_limiter)),
            ..HttpCacheOptions::default()
        };

        let client = ClientBuilder::new(Client::new())
            .with(StreamingCache::with_options(
                manager,
                CacheMode::Default,
                options,
            ))
            .build();

        let mock_server = MockServer::start().await;
        let url = format!("{}/streaming-cacheable", mock_server.uri());

        // Mock cacheable response
        Mock::given(method("GET"))
            .and(path("/streaming-cacheable"))
            .respond_with(
                ResponseTemplate::new(200)
                    .insert_header("cache-control", "public, max-age=3600")
                    .set_body_bytes(b"cacheable streaming response"),
            )
            .expect(1) // Only expect one request due to caching
            .mount(&mock_server)
            .await;

        // First request - should apply rate limiting and cache the response
        let start = Instant::now();
        let response1 = client.get(&url).send().await?;
        let first_duration = start.elapsed();

        assert_eq!(response1.status(), 200);
        assert_eq!(call_counter.lock().unwrap().len(), 1);
        assert!(
            first_duration >= Duration::from_millis(50),
            "First request should be rate limited"
        );

        // Clear call counter
        call_counter.lock().unwrap().clear();

        // Second request - should be cache hit, NO rate limiting
        let response2 = client.get(&url).send().await?;

        assert_eq!(response2.status(), 200);
        assert_eq!(call_counter.lock().unwrap().len(), 0);

        Ok(())
    }
}

#[cfg(all(test, feature = "rate-limiting"))]
mod rate_limiting_tests {
    use super::*;
    use crate::{CacheAwareRateLimiter, DomainRateLimiter, Quota};
    use std::num::NonZero;
    use std::sync::{Arc, Mutex};
    use std::time::{Duration, Instant};

    // Mock rate limiter for testing rate limiting behavior
    #[derive(Debug)]
    struct MockRateLimiter {
        calls: Arc<Mutex<Vec<String>>>,
        delay: Duration,
    }

    impl MockRateLimiter {
        fn new(delay: Duration) -> Self {
            Self { calls: Arc::new(Mutex::new(Vec::new())), delay }
        }
    }

    impl CacheAwareRateLimiter for MockRateLimiter {
        fn until_key_ready(
            &self,
            key: &str,
        ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + '_>>
        {
            let key = key.to_string();
            Box::pin(async move {
                self.calls.lock().unwrap().push(key);
                if !self.delay.is_zero() {
                    tokio::time::sleep(self.delay).await;
                }
            })
        }

        fn check_key(&self, _key: &str) -> bool {
            true
        }
    }

    #[tokio::test]
    async fn test_cache_with_rate_limiting_cache_hit() -> Result<()> {
        let mock_server = MockServer::start().await;
        let url = format!("{}/test", mock_server.uri());

        // Set up mock to expect only one request (cache miss)
        build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 1)
            .mount(&mock_server)
            .await;

        let rate_limiter = MockRateLimiter::new(Duration::from_millis(10));
        let call_counter = rate_limiter.calls.clone();

        let options = HttpCacheOptions {
            rate_limiter: Some(Arc::new(rate_limiter)),
            ..HttpCacheOptions::default()
        };

        let client = ClientBuilder::new(Client::new())
            .with(Cache(HttpCache {
                mode: CacheMode::Default,
                manager: create_cache_manager(),
                options,
            }))
            .build();

        // First request - should trigger rate limiting and cache miss
        let start = Instant::now();
        let response1 = client.get(&url).send().await?;
        let first_duration = start.elapsed();

        assert_eq!(response1.status(), 200);
        assert_eq!(call_counter.lock().unwrap().len(), 1);
        assert!(first_duration >= Duration::from_millis(10)); // Rate limiting delay

        // Clear rate limiter calls for next test
        call_counter.lock().unwrap().clear();

        // Second request - should be cache hit, NO rate limiting
        let response2 = client.get(&url).send().await?;

        assert_eq!(response2.status(), 200);
        assert_eq!(call_counter.lock().unwrap().len(), 0);

        // Verify both responses have the same body
        let body1 = response1.bytes().await?;
        let body2 = response2.bytes().await?;
        assert_eq!(body1, body2);
        assert_eq!(body1, TEST_BODY);

        Ok(())
    }

    #[tokio::test]
    async fn test_cache_with_rate_limiting_domain_based() -> Result<()> {
        let mock_server1 = MockServer::start().await;
        let mock_server2 = MockServer::start().await;

        let url1 = format!("{}/test1", mock_server1.uri());
        let url2 = format!("{}/test2", mock_server2.uri());

        // Set up mocks for both servers
        build_mock("no-cache", b"server1", 200, 1).mount(&mock_server1).await;
        build_mock("no-cache", b"server2", 200, 1).mount(&mock_server2).await;

        let rate_limiter = MockRateLimiter::new(Duration::from_millis(1));
        let call_counter = rate_limiter.calls.clone();

        let options = HttpCacheOptions {
            rate_limiter: Some(Arc::new(rate_limiter)),
            ..HttpCacheOptions::default()
        };

        let client = ClientBuilder::new(Client::new())
            .with(Cache(HttpCache {
                mode: CacheMode::Default,
                manager: create_cache_manager(),
                options,
            }))
            .build();

        // Make requests to both domains
        let _response1 = client.get(&url1).send().await?;
        let _response2 = client.get(&url2).send().await?;

        // Both should trigger rate limiting (different domains)
        let calls = call_counter.lock().unwrap().clone();
        assert_eq!(calls.len(), 2);

        // Verify domains are correctly extracted
        assert!(
            calls[0].contains("127.0.0.1") || calls[0].contains("localhost")
        );
        assert!(
            calls[1].contains("127.0.0.1") || calls[1].contains("localhost")
        );

        Ok(())
    }

    #[tokio::test]
    async fn test_rate_limiting_with_governor() -> Result<()> {
        let mock_server = MockServer::start().await;
        let url = format!("{}/test", mock_server.uri());

        // Set up mock to allow multiple requests (no caching)
        build_mock("no-cache", TEST_BODY, 200, 2).mount(&mock_server).await;

        // Create rate limiter: 2 requests per second
        let quota = Quota::per_second(NonZero::new(2).unwrap());
        let rate_limiter = DomainRateLimiter::new(quota);

        let options = HttpCacheOptions {
            rate_limiter: Some(Arc::new(rate_limiter)),
            ..HttpCacheOptions::default()
        };

        let client = ClientBuilder::new(Client::new())
            .with(Cache(HttpCache {
                mode: CacheMode::Default,
                manager: create_cache_manager(),
                options,
            }))
            .build();

        let start = Instant::now();

        // First two requests should be fast (within burst limit)
        let _response1 = client.get(&url).send().await?;
        let first_duration = start.elapsed();

        let _response2 = client.get(&url).send().await?;
        let second_duration = start.elapsed();

        // Both should be relatively fast
        assert!(first_duration < Duration::from_millis(50));
        assert!(second_duration < Duration::from_millis(100));

        Ok(())
    }

    #[tokio::test]
    async fn test_direct_rate_limiter_behavior() -> Result<()> {
        let mock_server = MockServer::start().await;
        let url = format!("{}/test", mock_server.uri());

        // Set up mock
        build_mock("no-cache", TEST_BODY, 200, 2).mount(&mock_server).await;

        // Create direct rate limiter (not domain-based)
        let quota = Quota::per_second(NonZero::new(5).unwrap());
        let rate_limiter = DomainRateLimiter::new(quota);

        let options = HttpCacheOptions {
            rate_limiter: Some(Arc::new(rate_limiter)),
            ..HttpCacheOptions::default()
        };

        let client = ClientBuilder::new(Client::new())
            .with(Cache(HttpCache {
                mode: CacheMode::Default,
                manager: create_cache_manager(),
                options,
            }))
            .build();

        // Make multiple requests
        let _response1 = client.get(&url).send().await?;
        let _response2 = client.get(&url).send().await?;

        // Both should succeed (rate limiting applies globally, not per domain)
        // This test mainly verifies the integration works without panicking

        Ok(())
    }

    #[tokio::test]
    async fn test_no_rate_limiting_by_default() -> Result<()> {
        let mock_server = MockServer::start().await;
        let url = format!("{}/test", mock_server.uri());

        build_mock("no-cache", TEST_BODY, 200, 1).mount(&mock_server).await;

        // Default options should have no rate limiting
        let options = HttpCacheOptions::default();
        assert!(options.rate_limiter.is_none());

        let client = ClientBuilder::new(Client::new())
            .with(Cache(HttpCache {
                mode: CacheMode::Default,
                manager: create_cache_manager(),
                options,
            }))
            .build();

        let start = Instant::now();
        let _response = client.get(&url).send().await?;
        let duration = start.elapsed();

        // Should be very fast without rate limiting
        assert!(duration < Duration::from_millis(100));

        Ok(())
    }

    #[tokio::test]
    async fn test_rate_limiting_only_on_network_requests() -> Result<()> {
        let mock_server = MockServer::start().await;
        let url = format!("{}/test", mock_server.uri());

        // Set up mock to expect only one request
        build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 1)
            .mount(&mock_server)
            .await;

        let rate_limiter = MockRateLimiter::new(Duration::from_millis(20));
        let call_counter = rate_limiter.calls.clone();

        let options = HttpCacheOptions {
            rate_limiter: Some(Arc::new(rate_limiter)),
            ..HttpCacheOptions::default()
        };

        let client = ClientBuilder::new(Client::new())
            .with(Cache(HttpCache {
                mode: CacheMode::Default,
                manager: create_cache_manager(),
                options,
            }))
            .build();

        // First request: cache miss, should apply rate limiting
        let start = Instant::now();
        let _response1 = client.get(&url).send().await?;
        let first_duration = start.elapsed();

        assert_eq!(call_counter.lock().unwrap().len(), 1);
        assert!(first_duration >= Duration::from_millis(20));

        // Clear calls
        call_counter.lock().unwrap().clear();

        // Second request: cache hit, should NOT apply rate limiting
        let _response2 = client.get(&url).send().await?;

        assert_eq!(call_counter.lock().unwrap().len(), 0);

        // Third request: cache hit, should NOT apply rate limiting
        let _response3 = client.get(&url).send().await?;

        assert_eq!(call_counter.lock().unwrap().len(), 0);

        Ok(())
    }
}

#[tokio::test]
async fn test_metadata_retrieval_through_extensions() -> Result<()> {
    let mock_server = MockServer::start().await;

    Mock::given(method(GET))
        .respond_with(
            ResponseTemplate::new(200)
                .insert_header("cache-control", CACHEABLE_PUBLIC)
                .set_body_bytes(TEST_BODY),
        )
        .expect(1) // Only called once, second request is cached
        .mount(&mock_server)
        .await;

    let url = format!("{}/metadata-test", mock_server.uri());

    // Create cache options with a metadata provider
    let options = HttpCacheOptions {
        metadata_provider: Some(Arc::new(|_request_parts, _response_parts| {
            Some(b"test-metadata-value".to_vec())
        })),
        ..Default::default()
    };

    let client = ClientBuilder::new(Client::new())
        .with(Cache(HttpCache {
            mode: CacheMode::Default,
            manager: create_cache_manager(),
            options,
        }))
        .build();

    // First request - stores the response with metadata
    let response1 = client.get(&url).send().await?;
    assert_eq!(response1.status(), 200);

    // First response should have metadata (generated by metadata provider when caching)
    let metadata1 = response1.extensions().get::<HttpCacheMetadata>();
    assert!(
        metadata1.is_some(),
        "Metadata should be present when response is cached"
    );
    assert_eq!(
        metadata1.unwrap().as_slice(),
        b"test-metadata-value",
        "Metadata value should match what was stored"
    );

    // Second request - should retrieve from cache with metadata in extensions
    let response2 = client.get(&url).send().await?;
    assert_eq!(response2.status(), 200);

    // Check that metadata is also present on cache hit
    let metadata2 = response2.extensions().get::<HttpCacheMetadata>();
    assert!(
        metadata2.is_some(),
        "Metadata should be present in response extensions on cache hit"
    );

    let metadata_value = metadata2.unwrap();
    assert_eq!(
        metadata_value.as_slice(),
        b"test-metadata-value",
        "Metadata value should match what was stored"
    );

    Ok(())
}

// Full middleware-level e2e test for the bincode->postcard migration path.
// Simulates a user who had http-cache-reqwest 0.16.0 and upgrades to 1.0-alpha.4.
#[cfg(all(
    feature = "manager-cacache",
    feature = "manager-cacache-bincode",
    feature = "http-headers-compat"
))]
mod bincode_migration {
    use super::*;

    use http_cache::{HttpVersion, Url};
    use serde::Serialize;
    use std::collections::HashMap;
    use std::str::FromStr;

    // Local copies matching the exact serde layout from http-cache 0.21.0.
    // This is intentional — it's a true black-box test mirroring what old
    // code actually wrote via bincode.
    #[derive(Serialize)]
    struct LegacyStore {
        response: LegacyTestResponse,
        policy: http_cache_semantics::CachePolicy,
    }

    #[derive(Serialize)]
    struct LegacyTestResponse {
        body: Vec<u8>,
        headers: HashMap<String, String>,
        status: u16,
        url: Url,
        version: HttpVersion,
    }

    #[tokio::test]
    async fn migration_from_bincode_cache() -> Result<()> {
        let mock_server = MockServer::start().await;

        // Set up a mock that we expect NOT to be called if the cache hit works.
        // Use ForceCache mode so the stale policy doesn't trigger revalidation.
        let m = build_mock(CACHEABLE_PUBLIC, b"fresh from server", 200, 0);
        let _mock_guard = mock_server.register_as_scoped(m).await;

        let url = format!("{}/legacy-endpoint", &mock_server.uri());
        let parsed_url = Url::from_str(&url)?;

        let cache_dir = tempfile::tempdir().unwrap();
        let cache_path = cache_dir.path().to_path_buf();
        let cache_key = format!("GET:{}", &parsed_url);

        // Construct a legacy bincode payload
        let mut headers = HashMap::new();
        headers
            .insert("content-type".to_string(), "application/json".to_string());
        headers
            .insert("cache-control".to_string(), CACHEABLE_PUBLIC.to_string());

        let legacy_body = b"legacy bincode response";

        let req = http::Request::get(url.as_str()).body(())?;
        let res = http::Response::builder()
            .status(200)
            .header("cache-control", CACHEABLE_PUBLIC)
            .body(legacy_body.to_vec())?;
        let policy = http_cache_semantics::CachePolicy::new(&req, &res);

        let legacy_store = LegacyStore {
            response: LegacyTestResponse {
                body: legacy_body.to_vec(),
                headers,
                status: 200,
                url: parsed_url.clone(),
                version: HttpVersion::Http11,
            },
            policy,
        };

        // Serialize with bincode and write to cacache
        let bytes = bincode::serialize(&legacy_store).unwrap();
        cacache::write(&cache_path, &cache_key, bytes).await.unwrap();

        // Create a reqwest client pointing at the same cache directory
        let manager = CACacheManager::new(cache_path.clone(), true);
        let client = ClientBuilder::new(Client::new())
            .with(Cache(HttpCache {
                mode: CacheMode::ForceCache,
                manager: manager.clone(),
                options: Default::default(),
            }))
            .build();

        // Request the same URL — should be served from the legacy cache
        let res = client.get(&url).send().await?;
        assert_eq!(res.status(), 200);
        let body = res.bytes().await?;
        assert_eq!(
            body.as_ref(),
            legacy_body,
            "Response should come from the legacy bincode cache entry"
        );

        // The mock expects 0 calls, so WireMock will assert if it was hit

        Ok(())
    }

    #[tokio::test]
    async fn migration_new_entries_work_after_upgrade() -> Result<()> {
        let mock_server = MockServer::start().await;
        let m = build_mock(CACHEABLE_PUBLIC, b"new postcard data", 200, 1);
        let _mock_guard = mock_server.register_as_scoped(m).await;
        let url = format!("{}/new-endpoint", &mock_server.uri());

        let cache_dir = tempfile::tempdir().unwrap();
        let manager = CACacheManager::new(cache_dir.path().to_path_buf(), true);

        let client = ClientBuilder::new(Client::new())
            .with(Cache(HttpCache {
                mode: CacheMode::Default,
                manager: manager.clone(),
                options: Default::default(),
            }))
            .build();

        // First request: cache miss, goes to mock server
        let res = client.get(&url).send().await?;
        assert_eq!(res.status(), 200);
        assert_eq!(res.bytes().await?, &b"new postcard data"[..]);

        // Verify it was cached (written with postcard)
        let data =
            CacheManager::get(&manager, &format!("GET:{}", &url_parse(&url)?))
                .await?;
        assert!(data.is_some(), "New entry should be cached with postcard");

        // Second request: should come from cache (mock expects only 1 call)
        let res = client.get(&url).send().await?;
        assert_eq!(res.status(), 200);
        assert_eq!(res.bytes().await?, &b"new postcard data"[..]);

        Ok(())
    }
}

#[cfg(feature = "streaming")]
mod streaming_issue_164 {
    use super::*;
    use crate::StreamingCache;
    use http_cache::{CacheMode, StreamingManager};

    /// Over-cap cacheable response: must pass through successfully
    /// (previously errored the whole request) and must never be cached.
    #[tokio::test]
    async fn oversized_response_passes_through_uncached() -> Result<()> {
        let mock_server = MockServer::start().await;
        let body = vec![0xCD_u8; 8192]; // 8 KiB
        let m = Mock::given(method(GET)).respond_with(
            ResponseTemplate::new(200)
                .insert_header("cache-control", "public, max-age=3600")
                .set_body_bytes(body),
        );
        let _guard = mock_server.register_as_scoped(m).await;
        let url = format!("{}/", mock_server.uri());

        let tmp = tempfile::TempDir::new()?;
        let manager = StreamingManager::with_max_body_size(
            tmp.path().to_path_buf(),
            100,
            1024, // 1 KiB cap < 8 KiB body
        )
        .await?;
        let client = ClientBuilder::new(Client::new())
            .with(StreamingCache::new(manager, CacheMode::Default))
            .build();

        for _ in 0..2 {
            let res = client.get(&url).send().await?;
            assert_eq!(
                res.headers().get("x-cache").unwrap().to_str().unwrap(),
                "MISS",
                "over-cap response must never become a HIT"
            );
            let got = res.bytes().await?;
            assert_eq!(got.len(), 8192);
        }
        Ok(())
    }

    /// Anti-regression test for issue #164's adapter half: the middleware
    /// must not buffer the upstream body. A hand-rolled chunked server sends
    /// one chunk, then waits for the client to observe it before sending the
    /// rest, so a buffering adapter deadlocks and the 10s timeouts fire.
    ///
    /// The response is `no-store`, so this pins the pass-through path; the
    /// cacheable-miss path spools to EOF before the first byte.
    #[tokio::test]
    async fn first_byte_arrives_before_upstream_eof() -> Result<()> {
        use futures_util::StreamExt;
        use tokio::io::{AsyncReadExt, AsyncWriteExt};

        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
        let addr = listener.local_addr()?;
        let (ack_tx, ack_rx) = tokio::sync::oneshot::channel::<()>();

        tokio::spawn(async move {
            let (mut sock, _) = listener.accept().await.unwrap();
            let mut buf = [0u8; 4096];
            let _ = sock.read(&mut buf).await.unwrap(); // request head
            sock.write_all(
                b"HTTP/1.1 200 OK\r\n\
                  cache-control: no-store\r\n\
                  transfer-encoding: chunked\r\n\
                  \r\n\
                  10\r\nfirst-chunk-data\r\n",
            )
            .await
            .unwrap();
            sock.flush().await.unwrap();
            // Hold the body open until the client acks the first chunk.
            let _ = ack_rx.await;
            sock.write_all(b"E\r\nsecond-chunk-d\r\n0\r\n\r\n").await.unwrap();
        });

        let tmp = tempfile::TempDir::new()?;
        let manager =
            StreamingManager::new(tmp.path().to_path_buf(), 100).await?;
        let client = ClientBuilder::new(Client::new())
            .with(StreamingCache::new(manager, CacheMode::Default))
            .build();

        let res = tokio::time::timeout(
            std::time::Duration::from_secs(10),
            client.get(format!("http://{addr}/")).send(),
        )
        .await
        .expect(
            "send() must return headers before the upstream body finishes \
             — a timeout here means the middleware is buffering bodies \
             again (issue #164 regression)",
        )?;
        let mut stream = res.bytes_stream();
        // The 16-byte first chunk may arrive as several stream items.
        let first_chunk_len = b"first-chunk-data".len();
        let mut first = Vec::new();
        while first.len() < first_chunk_len {
            let chunk = tokio::time::timeout(
                std::time::Duration::from_secs(10),
                stream.next(),
            )
            .await
            .expect(
                "first chunk must arrive before upstream EOF — a timeout \
                 here means the middleware is buffering bodies again \
                 (issue #164 regression)",
            )
            .expect("stream ended before the first chunk")?;
            first.extend_from_slice(&chunk);
        }
        assert_eq!(&first[..], b"first-chunk-data");
        let _ = ack_tx.send(());
        let mut rest = Vec::new();
        while let Some(c) = stream.next().await {
            rest.extend_from_slice(&c?);
        }
        assert_eq!(&rest[..], b"second-chunk-d");
        Ok(())
    }

    /// End-to-end MISS -> HIT round trip. The final URL must survive the
    /// streaming middleware on both (regression: it used to come back as
    /// http://no.url.provided.local).
    #[tokio::test]
    async fn final_url_preserved() -> Result<()> {
        let mock_server = MockServer::start().await;
        let m = Mock::given(method(GET)).respond_with(
            ResponseTemplate::new(200)
                .insert_header("cache-control", "public, max-age=3600")
                .set_body_bytes(b"url-test".to_vec()),
        );
        let _guard = mock_server.register_as_scoped(m).await;
        let url = format!("{}/", mock_server.uri());

        let tmp = tempfile::TempDir::new()?;
        let manager =
            StreamingManager::new(tmp.path().to_path_buf(), 100).await?;
        let client = ClientBuilder::new(Client::new())
            .with(StreamingCache::new(manager, CacheMode::Default))
            .build();

        let res = client.get(&url).send().await?;
        assert_eq!(res.headers().get("x-cache").unwrap(), "MISS");
        assert_eq!(res.url().as_str(), url, "MISS must preserve final URL");
        assert_eq!(res.bytes().await?, &b"url-test"[..]);

        let res = client.get(&url).send().await?;
        assert_eq!(res.headers().get("x-cache").unwrap(), "HIT");
        assert_eq!(res.url().as_str(), url, "HIT must preserve final URL");
        assert_eq!(res.bytes().await?, &b"url-test"[..]);
        Ok(())
    }

    /// 304 revalidation on the streaming path is a metadata-only refresh via
    /// `update_metadata`; the stored entry must pick up the revalidated
    /// headers.
    #[tokio::test]
    async fn revalidation_304_updates_stored_metadata() -> Result<()> {
        let mock_server = MockServer::start().await;
        let m = build_mock("public, must-revalidate", TEST_BODY, 200, 1);
        let m304 = Mock::given(method(GET))
            .respond_with(
                ResponseTemplate::new(304)
                    .insert_header("cache-control", "public, max-age=999"),
            )
            .expect(1);
        let mock_guard = mock_server.register_as_scoped(m).await;
        let url = format!("{}/", mock_server.uri());

        let tmp = tempfile::TempDir::new()?;
        let manager =
            StreamingManager::new(tmp.path().to_path_buf(), 100).await?;
        let probe = manager.clone();
        let client = ClientBuilder::new(Client::new())
            .with(StreamingCache::new(manager, CacheMode::Default))
            .build();

        let res = client.get(url.clone()).send().await?;
        assert_eq!(res.bytes().await?, TEST_BODY);

        drop(mock_guard);
        let _mock_guard = mock_server.register_as_scoped(m304).await;

        let res = client.get(url.clone()).send().await?;
        assert_eq!(res.bytes().await?, TEST_BODY);

        let key = format!("{}:{}", GET, url_parse(&url)?);
        let (stored, _) =
            StreamingCacheManager::get(&probe, &key).await?.unwrap();
        assert_eq!(
            stored.headers().get("cache-control").unwrap(),
            "public, max-age=999",
            "304 must refresh the stored metadata"
        );
        Ok(())
    }
}
