//! Integration tests for polling and verification flow.
//!
//! These tests verify the poll_for_completion and verify_presentation
//! functionality using mock IDP and trust registry responses.

use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};

use lastid_sdk::{ClientBuilder, IssuerStatus, LastIDError, RequestId, RequestStatus, SDKConfig};
use p256::ecdsa::SigningKey;
use p256::ecdsa::signature::Signer;
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))
        .polling_initial_interval_ms(50) // Fast polling for tests
        .polling_max_interval_ms(100)
        .polling_max_duration_seconds(2) // Short timeout for tests
        .build()
        .expect("Valid test config")
}

/// Create a valid mock SD-JWT for testing
fn create_mock_sdjwt() -> String {
    // Create a minimal SD-JWT structure:
    // header.payload.signature~disclosure~
    let header = base64_url_encode(r#"{"alg":"ES256","typ":"vc+sd-jwt"}"#);

    let now = chrono::Utc::now().timestamp();
    let exp = now + 3600; // 1 hour from now

    // SD-JWT uses "vct" (verifiable credential type) for the credential type
    let payload = base64_url_encode(&format!(
        r#"{{
            "iss": "did:lastid:test-issuer",
            "sub": "did:lastid:test-subject",
            "iat": {},
            "exp": {},
            "vct": "LastID.Base",
            "_sd": ["abc123"]
        }}"#,
        now, exp
    ));

    let signature = base64_url_encode("mock-signature");

    // Create a disclosure for email claim
    // Format: base64url([salt, claim_name, claim_value])
    let disclosure = base64_url_encode(r#"["salt123","email","test@example.com"]"#);

    format!("{}.{}.{}~{}~", header, payload, signature, disclosure)
}

fn base64_url_encode(data: &str) -> String {
    use base64::Engine;
    use base64::engine::general_purpose::URL_SAFE_NO_PAD;
    URL_SAFE_NO_PAD.encode(data.as_bytes())
}

fn base64_url_encode_bytes(data: &[u8]) -> String {
    use base64::Engine;
    use base64::engine::general_purpose::URL_SAFE_NO_PAD;
    URL_SAFE_NO_PAD.encode(data)
}

/// Create a properly signed SD-JWT for testing with real P-256 ECDSA signature.
///
/// # Arguments
///
/// * `signing_key` - The P-256 signing key to sign the JWT with
/// * `issuer_did` - The issuer DID to include in the `iss` claim
/// * `subject_did` - The subject DID to include in the `sub` claim
fn create_signed_sdjwt(signing_key: &SigningKey, issuer_did: &str, subject_did: &str) -> String {
    // Create header with ES256 algorithm
    let header = base64_url_encode(r#"{"alg":"ES256","typ":"vc+sd-jwt"}"#);

    let now = chrono::Utc::now().timestamp();
    let exp = now + 3600; // 1 hour from now

    // SD-JWT uses "vct" (verifiable credential type) for the credential type
    let payload_json = format!(
        r#"{{"iss":"{}","sub":"{}","iat":{},"exp":{},"vct":"LastID.Base","_sd":["abc123"]}}"#,
        issuer_did, subject_did, now, exp
    );
    let payload = base64_url_encode(&payload_json);

    // Create the signing input (header.payload)
    let signing_input = format!("{}.{}", header, payload);

    // Sign with the P-256 key using ES256 (ECDSA with SHA-256)
    let signature: p256::ecdsa::Signature = signing_key.sign(signing_input.as_bytes());

    // The signature is 64 bytes (r || s) in raw format
    let signature_bytes = signature.to_bytes();
    let signature_b64 = base64_url_encode_bytes(&signature_bytes);

    // Create a disclosure for email claim
    // Format: base64url([salt, claim_name, claim_value])
    let disclosure = base64_url_encode(r#"["salt123","email","test@example.com"]"#);

    format!("{}.{}.{}~{}~", header, payload, signature_b64, disclosure)
}

/// Convert a P-256 signing key to JWK format for the trust registry response.
///
/// Returns a JSON object with the public key in JWK format:
/// ```json
/// {
///     "kty": "EC",
///     "crv": "P-256",
///     "x": "<base64url-encoded x coordinate>",
///     "y": "<base64url-encoded y coordinate>"
/// }
/// ```
fn signing_key_to_jwk(signing_key: &SigningKey) -> serde_json::Value {
    let verifying_key = signing_key.verifying_key();
    let public_point = verifying_key.to_encoded_point(false);

    // Extract x and y coordinates from uncompressed point (skip 0x04 prefix)
    let point_bytes = public_point.as_bytes();
    let x_bytes = &point_bytes[1..33];
    let y_bytes = &point_bytes[33..65];

    serde_json::json!({
        "kty": "EC",
        "crv": "P-256",
        "x": base64_url_encode_bytes(x_bytes),
        "y": base64_url_encode_bytes(y_bytes)
    })
}

/// Test poll_for_completion with immediate fulfillment
#[tokio::test]
async fn test_poll_for_completion_immediate_fulfill() {
    let mock_server = MockServer::start().await;
    let config = test_config(&mock_server.uri());
    let request_id = "req_immediate_test";

    // Return fulfilled on first poll
    // vp_token must be an array per OpenID4VP spec
    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": [create_mock_sdjwt()],
            "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_for_completion(&RequestId::new(request_id.to_string()))
        .await;

    assert!(
        status.is_ok(),
        "poll_for_completion failed: {:?}",
        status.err()
    );
    match status.unwrap() {
        RequestStatus::Fulfilled { .. } => {}
        s => panic!("Expected Fulfilled, got {:?}", s),
    }
}

/// Test poll_for_completion with multiple pending polls before fulfillment
#[tokio::test]
async fn test_poll_for_completion_eventual_fulfill() {
    let mock_server = MockServer::start().await;
    let config = test_config(&mock_server.uri());
    let request_id = "req_eventual_test";

    // Track how many times we've been polled
    let poll_count = Arc::new(AtomicUsize::new(0));
    let poll_count_clone = poll_count.clone();

    // Return pending twice, then fulfilled
    Mock::given(method("GET"))
        .and(path(format!("/v1/verify/status/{}", request_id)))
        .respond_with(move |_req: &wiremock::Request| {
            let count = poll_count_clone.fetch_add(1, Ordering::SeqCst);
            if count < 2 {
                ResponseTemplate::new(200).set_body_json(serde_json::json!({
                    "state": "pending",
                    "request_id": request_id,
                    "expires_at": "2026-01-16T11:00:00Z"
                }))
            } else {
                ResponseTemplate::new(200).set_body_json(serde_json::json!({
                    "state": "verified",
                    "request_id": request_id,
                    "vp_token": [create_mock_sdjwt()],
                    "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_for_completion(&RequestId::new(request_id.to_string()))
        .await;

    assert!(status.is_ok());
    match status.unwrap() {
        RequestStatus::Fulfilled { .. } => {}
        s => panic!("Expected Fulfilled, got {:?}", s),
    }

    // Verify we polled multiple times
    assert!(poll_count.load(Ordering::SeqCst) >= 3);
}

/// Test poll_for_completion timeout
#[tokio::test]
async fn test_poll_for_completion_timeout() {
    let mock_server = MockServer::start().await;

    // Use very short timeout for test
    let config = SDKConfig::builder()
        .client_id("test-client-id")
        .idp_endpoint(&mock_server.uri())
        .trust_registry_endpoint(format!("{}/v1/trust-registry", mock_server.uri()))
        .polling_initial_interval_ms(100)
        .polling_max_interval_ms(100)
        .polling_max_duration_seconds(1) // 1 second timeout
        .build()
        .expect("Valid test config");

    let request_id = "req_timeout_test";

    // Always return pending
    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 result = client
        .poll_for_completion(&RequestId::new(request_id.to_string()))
        .await;

    // FR-010: Timeout during polling MUST return Err(LastIDError::Timeout), not
    // Ok(RequestStatus::Timeout)
    assert!(result.is_err());
    match result.unwrap_err() {
        LastIDError::Timeout(elapsed_seconds) => {
            assert!(elapsed_seconds >= 1);
        }
        e => panic!("Expected LastIDError::Timeout, got {:?}", e),
    }
}

/// Test poll_for_completion with denial
#[tokio::test]
async fn test_poll_for_completion_denied() {
    let mock_server = MockServer::start().await;
    let config = test_config(&mock_server.uri());
    let request_id = "req_denied_test";

    // Mock IDP status endpoint (uses state field, not status)
    Mock::given(method("GET"))
        .and(path(format!("/v1/verify/status/{}", request_id)))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "state": "denied",
            "request_id": request_id,
            "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_for_completion(&RequestId::new(request_id.to_string()))
        .await;

    assert!(
        status.is_ok(),
        "poll_for_completion failed: {:?}",
        status.err()
    );
    match status.unwrap() {
        RequestStatus::Denied { reason, .. } => {
            // SDK maps denied state to a generic reason
            assert!(reason.contains("declined") || reason.contains("failed"));
        }
        s => panic!("Expected Denied, got {:?}", s),
    }
}

/// Test verify_presentation with valid SD-JWT and trust registry validation
///
/// This test creates a properly signed SD-JWT with a real P-256 key and
/// verifies it against the matching public key returned by the mock trust
/// registry.
#[tokio::test]
async fn test_verify_presentation_success() {
    use rand::rngs::OsRng;

    let mock_server = MockServer::start().await;
    let config = test_config(&mock_server.uri());

    // Generate a real P-256 signing key for the issuer
    let signing_key = SigningKey::random(&mut OsRng);
    let public_key_jwk = signing_key_to_jwk(&signing_key);

    let issuer_did = "did:lastid:test-issuer";
    let subject_did = "did:lastid:test-subject";

    // Mock trust registry response for the issuer (camelCase per IssuerInfo struct)
    Mock::given(method("GET"))
        .and(path(
            "/v1/trust-registry/issuers/did%3Alastid%3Atest-issuer",
        ))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "enterpriseDid": issuer_did,
            "organizationName": "Test Organization",
            "endpointUrl": "https://test.lastid.co",
            "status": "authorized",
            "permittedTypes": ["LastID.Base", "LastID.Persona"],
            "publicKey": public_key_jwk
        })))
        .mount(&mock_server)
        .await;

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

    // Create a properly signed SD-JWT
    let presentation = create_signed_sdjwt(&signing_key, issuer_did, subject_did);
    let result = client.verify_presentation(&presentation).await;

    assert!(
        result.is_ok(),
        "verify_presentation failed: {:?}",
        result.err()
    );
    let verified = result.unwrap();

    assert_eq!(verified.issuer_did, issuer_did);
    assert_eq!(verified.subject_did, subject_did);
    assert_eq!(verified.issuer_status, IssuerStatus::Authorized);
    assert!(!verified.is_expired());
}

/// Test verify_presentation with suspended issuer
#[tokio::test]
async fn test_verify_presentation_suspended_issuer() {
    let mock_server = MockServer::start().await;
    let config = test_config(&mock_server.uri());

    // Mock trust registry response with suspended status (camelCase)
    Mock::given(method("GET"))
        .and(path(
            "/v1/trust-registry/issuers/did%3Alastid%3Atest-issuer",
        ))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "enterpriseDid": "did:lastid:test-issuer",
            "organizationName": "Suspended Org",
            "endpointUrl": "https://test.lastid.co",
            "status": "suspended",
            "permittedTypes": ["LastID.Base"],
            "publicKey": {
                "kty": "EC",
                "crv": "P-256",
                "x": "WbbXWlY0Sdw6eVKiJGnFFAJnGDHVjdVRLWbZGIgn3Go",
                "y": "bfIuXkJHLZvVMZ96ekEqhyuSoVVbWBzWqcAzSvhp2gs"
            }
        })))
        .mount(&mock_server)
        .await;

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

    let presentation = create_mock_sdjwt();
    let result = client.verify_presentation(&presentation).await;

    // Should fail because issuer is suspended
    assert!(result.is_err());
    match result.unwrap_err() {
        LastIDError::TrustRegistry(_) => {}
        e => panic!("Expected TrustRegistry error, got: {:?}", e),
    }
}

/// Test verify_presentation with unknown issuer
#[tokio::test]
async fn test_verify_presentation_unknown_issuer() {
    let mock_server = MockServer::start().await;
    let config = test_config(&mock_server.uri());

    // Mock trust registry 404 response
    Mock::given(method("GET"))
        .and(path(
            "/v1/trust-registry/issuers/did%3Alastid%3Atest-issuer",
        ))
        .respond_with(ResponseTemplate::new(404))
        .mount(&mock_server)
        .await;

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

    let presentation = create_mock_sdjwt();
    let result = client.verify_presentation(&presentation).await;

    assert!(result.is_err());
    match result.unwrap_err() {
        LastIDError::TrustRegistry(_) => {}
        e => panic!("Expected TrustRegistry error, got: {:?}", e),
    }
}

/// Test verify_presentation with invalid SD-JWT format
#[tokio::test]
async fn test_verify_presentation_invalid_format() {
    let mock_server = MockServer::start().await;
    let config = test_config(&mock_server.uri());

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

    let invalid_presentation = "not.a.valid.sdjwt";
    let result = client.verify_presentation(invalid_presentation).await;

    assert!(result.is_err());
    match result.unwrap_err() {
        LastIDError::InvalidCredential(_) => {}
        e => panic!("Expected InvalidCredential error, got: {:?}", e),
    }
}
