//! Integration tests for SDK initialization and credential request flow.
//!
//! These tests verify the complete flow of initializing the SDK and
//! requesting credentials using mock IDP responses.

use lastid_sdk::{
    BaseCredentialPolicy, ClientBuilder, HttpError, LastIDError, RequestId, RequestStatus,
    SDKConfig,
};
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};

/// Helper to create a test configuration pointing to mock server
fn test_config(mock_url: &str) -> SDKConfig {
    SDKConfig::builder()
        .client_id("test-client-id")
        .idp_endpoint(mock_url)
        .trust_registry_endpoint(format!("{}/v1/trust-registry", mock_url))
        .build()
        .expect("Valid test config")
}

/// Test successful credential request flow
#[tokio::test]
async fn test_credential_request_success() {
    // Start mock server
    let mock_server = MockServer::start().await;
    let config = test_config(&mock_server.uri());

    // Mock OAuth token endpoint (required for DPoP-bound token acquisition)
    Mock::given(method("POST"))
        .and(path("/oauth/token"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "access_token": "test_access_token",
            "token_type": "DPoP",
            "expires_in": 3600
        })))
        .mount(&mock_server)
        .await;

    // Mock OpenID4VP verification request endpoint
    Mock::given(method("POST"))
        .and(path("/v1/verify/request"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "request_id": "req_test_123",
            "request_uri": "openid4vp://authorize?request_uri=https://test.lastid.co/requests/req_test_123",
            "expires_in": 300,
            "nonce": "nonce_abc123"
        })))
        .mount(&mock_server)
        .await;

    // Create client
    let client = ClientBuilder::new()
        .with_config(config)
        .build()
        .expect("Client should build");

    // Create policy
    let policy = BaseCredentialPolicy::new().with_callback("https://example.com/callback");

    // Request credential - returns full response
    let response = client.request_credential(policy).await;

    assert!(
        response.is_ok(),
        "request_credential failed: {:?}",
        response.err()
    );
    let response = response.unwrap();
    assert_eq!(response.request_id.as_str(), "req_test_123");
    assert!(response.request_uri.starts_with("openid4vp://"));
    assert_eq!(response.expires_in, 300);
    assert_eq!(response.nonce, "nonce_abc123");
}

/// Test credential request with rate limiting
#[tokio::test]
async fn test_credential_request_rate_limited() {
    let mock_server = MockServer::start().await;
    let config = SDKConfig::builder()
        .client_id("test-client-id")
        .idp_endpoint(&mock_server.uri())
        .trust_registry_endpoint(format!("{}/v1/trust-registry", mock_server.uri()))
        .retry_max_attempts(1) // Only try once
        .build()
        .expect("Valid test config");

    // Mock OAuth token endpoint (required for DPoP-bound token acquisition)
    Mock::given(method("POST"))
        .and(path("/oauth/token"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "access_token": "test_access_token",
            "token_type": "DPoP",
            "expires_in": 3600
        })))
        .mount(&mock_server)
        .await;

    // Mock rate limited response
    Mock::given(method("POST"))
        .and(path("/v1/verify/request"))
        .respond_with(ResponseTemplate::new(429).insert_header("Retry-After", "60"))
        .mount(&mock_server)
        .await;

    let client = ClientBuilder::new()
        .with_config(config)
        .build()
        .expect("Client should build");

    let policy = BaseCredentialPolicy::new().with_callback("https://example.com/callback");

    let result = client.request_credential(policy).await;

    assert!(result.is_err());
    match result.unwrap_err() {
        LastIDError::Http(HttpError::RateLimited {
            retry_after_seconds,
        }) => {
            assert_eq!(retry_after_seconds, 60);
        }
        e => panic!("Expected RateLimited error, got: {:?}", e),
    }
}

/// Test credential request with invalid policy (400 error)
#[tokio::test]
async fn test_credential_request_invalid_policy() {
    let mock_server = MockServer::start().await;
    let config = test_config(&mock_server.uri());

    // Mock OAuth token endpoint (required for DPoP-bound token acquisition)
    Mock::given(method("POST"))
        .and(path("/oauth/token"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "access_token": "test_access_token",
            "token_type": "DPoP",
            "expires_in": 3600
        })))
        .mount(&mock_server)
        .await;

    // Mock validation error response
    Mock::given(method("POST"))
        .and(path("/v1/verify/request"))
        .respond_with(ResponseTemplate::new(400).set_body_json(serde_json::json!({
            "error": "Invalid callback URL"
        })))
        .mount(&mock_server)
        .await;

    let client = ClientBuilder::new()
        .with_config(config)
        .build()
        .expect("Client should build");

    let policy = BaseCredentialPolicy::new().with_callback("https://example.com/callback");

    let result = client.request_credential(policy).await;

    assert!(result.is_err());
    match result.unwrap_err() {
        LastIDError::Http(HttpError::Status { status, .. }) => {
            assert_eq!(status, 400);
        }
        e => panic!("Expected Status 400 error, got: {:?}", e),
    }
}

/// Test poll status success (pending then fulfilled)
#[tokio::test]
async fn test_poll_status_fulfilled() {
    let mock_server = MockServer::start().await;
    let config = test_config(&mock_server.uri());

    let request_id = "req_poll_test";

    // Mock fulfilled status response (IDP format: state, vp_token)
    Mock::given(method("GET"))
        .and(path(format!("/v1/verify/status/{}", request_id)))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "state": "verified",
            "request_id": request_id,
            "vp_token": ["eyJhbGciOiJFUzI1NiIsInR5cCI6InZjK3NkLWp3dCJ9.test"],
            "expires_at": "2026-01-16T12:00:00Z"
        })))
        .mount(&mock_server)
        .await;

    let client = ClientBuilder::new()
        .with_config(config)
        .build()
        .expect("Client should build");

    let status = client
        .poll_request(&RequestId::new(request_id.to_string()))
        .await;

    assert!(status.is_ok());
    match status.unwrap() {
        RequestStatus::Fulfilled {
            request_id: rid,
            presentation,
            ..
        } => {
            assert_eq!(rid.as_str(), request_id);
            assert!(presentation.contains("eyJ"));
        }
        s => panic!("Expected Fulfilled status, got: {:?}", s),
    }
}

/// Test poll status returns pending
#[tokio::test]
async fn test_poll_status_pending() {
    let mock_server = MockServer::start().await;
    let config = test_config(&mock_server.uri());

    let request_id = "req_pending_test";

    // Mock pending status response (IDP format: state)
    Mock::given(method("GET"))
        .and(path(format!("/v1/verify/status/{}", request_id)))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "state": "pending",
            "request_id": request_id,
            "expires_at": "2026-01-16T11:00:00Z"
        })))
        .mount(&mock_server)
        .await;

    let client = ClientBuilder::new()
        .with_config(config)
        .build()
        .expect("Client should build");

    let status = client
        .poll_request(&RequestId::new(request_id.to_string()))
        .await;

    assert!(status.is_ok());
    match status.unwrap() {
        RequestStatus::Pending {
            request_id: rid, ..
        } => {
            assert_eq!(rid.as_str(), request_id);
        }
        s => panic!("Expected Pending status, got: {:?}", s),
    }
}

/// Test poll status with network retry
#[tokio::test]
async fn test_poll_status_retry_on_network_error() {
    let mock_server = MockServer::start().await;

    // Configure retry policy with 2 attempts
    let config = SDKConfig::builder()
        .client_id("test-client-id")
        .idp_endpoint(&mock_server.uri())
        .trust_registry_endpoint(format!("{}/v1/trust-registry", mock_server.uri()))
        .retry_max_attempts(3)
        .retry_initial_delay_ms(100)
        .build()
        .expect("Valid test config");

    let request_id = "req_retry_test";

    // First call returns 500 (retryable), second returns 200
    Mock::given(method("GET"))
        .and(path(format!("/v1/verify/status/{}", request_id)))
        .respond_with(
            ResponseTemplate::new(500)
                .set_body_string("Server error")
        )
        .up_to_n_times(1) // Only fail once
        .mount(&mock_server)
        .await;

    Mock::given(method("GET"))
        .and(path(format!("/v1/verify/status/{}", request_id)))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "state": "pending",
            "request_id": request_id,
            "expires_at": "2026-01-16T11:00:00Z"
        })))
        .mount(&mock_server)
        .await;

    let client = ClientBuilder::new()
        .with_config(config)
        .build()
        .expect("Client should build");

    let status = client
        .poll_request(&RequestId::new(request_id.to_string()))
        .await;

    // Should succeed after retry
    assert!(status.is_ok());
}

/// Test DPoP header is sent with requests
#[tokio::test]
async fn test_dpop_header_sent() {
    let mock_server = MockServer::start().await;
    let config = test_config(&mock_server.uri());

    // Mock OAuth token endpoint (required for DPoP-bound token acquisition)
    Mock::given(method("POST"))
        .and(path("/oauth/token"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "access_token": "test_access_token",
            "token_type": "DPoP",
            "expires_in": 3600
        })))
        .mount(&mock_server)
        .await;

    // Mock that accepts any POST request (DPoP header presence verified by checking
    // request details)
    Mock::given(method("POST"))
        .and(path("/v1/verify/request"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "request_id": "req_dpop_test",
            "request_uri": "openid4vp://authorize?request_uri=https://test.lastid.co/requests/req_dpop_test",
            "expires_in": 300,
            "nonce": "nonce_dpop"
        })))
        .expect(1)
        .mount(&mock_server)
        .await;

    let client = ClientBuilder::new()
        .with_config(config)
        .build()
        .expect("Client should build");

    let policy = BaseCredentialPolicy::new().with_callback("https://example.com/callback");

    let result = client.request_credential(policy).await;

    // Request should succeed
    assert!(result.is_ok());

    // Verify mock was called (DPoP header was included)
    // Note: The HttpIdpClient adds the DPoP header - we verify the request was
    // made
}
