use std::time::SystemTime;

use crate::middleware::FWOptions;
use crate::FWClientBuilder;
use mockito::{Matcher, Server};
use reqwest_retry::{RetryDecision, RetryPolicy, RetryTransientMiddleware};

#[rstest::rstest]
#[case::auth(false, |url: String| -> Matcher {Matcher::Exact(format!("scitran-user {}:test-key", url))})]
#[case::no_auth(true, |_| -> Matcher {Matcher::Missing})]
#[tokio::test]
async fn test_auth_extension<T>(#[case] no_auth: bool, #[case] matcher: T)
where
    T: Fn(String) -> Matcher + Send + 'static,
{
    let mut server = Server::new_async().await;
    let host = server.host_with_port();
    let host_only = host.split(':').next().unwrap().to_string();
    println!("host: {}", &host_only);
    let mock_server = server
        .mock("GET", "/api/v1/test")
        .match_header("Authorization", matcher(host_only))
        .with_status(200)
        .create_async()
        .await;

    // Create client with MockServer URL
    let api_key = format!("scitran-user http://{}:test-key", host)
        .parse()
        .unwrap();
    let client = FWClientBuilder::new(api_key)
        .build()
        .expect("Failed to create FWClient");

    // Create request with FWOptions extension
    let opts = FWOptions {
        skip_retry: false,
        no_auth,
    };

    // Send request with extensions
    let response = client
        .get("/api/v1/test")
        .with_extension(opts)
        .send()
        .await
        .expect("Request failed");

    assert_eq!(response.status().as_u16(), 200);
    // Verify the mock was called
    println!("{:?}", mock_server);
    mock_server.assert();
}

struct SimpleRetryPolicy(u32);

impl RetryPolicy for SimpleRetryPolicy {
    fn should_retry(&self, _: SystemTime, attempt: u32) -> RetryDecision {
        if attempt >= self.0 {
            RetryDecision::DoNotRetry
        } else {
            RetryDecision::Retry {
                execute_after: SystemTime::now(),
            }
        }
    }
}

#[rstest::rstest]
#[case::skip_retry(true, 1)]
#[case::no_skip_retry(false, 4)]
#[tokio::test]
async fn test_skip_retry(#[case] skip_retry: bool, #[case] expected_calls: usize) {
    // Setup mock server that will fail once then succeed
    let mut server = Server::new_async().await;
    let host = server.host_with_port();
    let mock_request = server
        .mock("GET", "/api/v1/test")
        .with_status(503) // Service unavailable
        .with_body("Service temporarily unavailable")
        .expect(expected_calls) // Expect to be called exactly once
        .create_async()
        .await;

    // Create client with MockServer URL and only 1 retry
    let api_key = format!("scitran-user http://{}:test_api_key", host)
        .parse()
        .unwrap();
    let client = FWClientBuilder::new(api_key)
        .with_retry(Some(RetryTransientMiddleware::new_with_policy(
            SimpleRetryPolicy(3),
        )))
        .build()
        .expect("Failed to create FWClient");

    // Create request with skip_retry option
    let options = FWOptions {
        skip_retry,
        no_auth: false,
    };

    // Send request with extensions - should fail without retry
    let response = client
        .get("/api/v1/test")
        .with_extension(options)
        .send()
        .await
        .unwrap();
    assert!(
        response.status().as_u16() == 503,
        "Expected 503 Service Unavailable, got {}",
        response.status()
    );

    // Verify the mock was called exactly once (no retries)
    mock_request.assert_async().await;
}

#[ignore = "Requires a valid API key"]
#[tokio::test]
async fn test_auth_info() {
    let api_key = std::env::var("FW_API_KEY")
        .unwrap_or("scitran-user http://localhost:8000:test-key".to_string());
    let client = FWClientBuilder::new(api_key.parse().unwrap())
        .build()
        .expect("Failed to create FWClient");
    let response = client
        .get("/api/users/self")
        .send()
        .await
        .expect("Request failed")
        .json::<serde_json::Value>()
        .await
        .expect("Failed to parse response as JSON");
    println!("{:?}", response);
}
