use bucketwarden_auth::{
    AuthError, AuthStore, CredentialScope, DirectoryProvider, IdentityProvider, PrincipalKind,
    SamlProvider,
};

const NOW: u64 = 1_700_000_000;

#[test]
fn ldap_provider_issues_scoped_session_for_enabled_directory_subject() {
    let mut store = AuthStore::new();
    store.upsert_identity_provider(IdentityProvider::Ldap(
        DirectoryProvider::ldap("ldap-main", "ldap://directory.example", "ldap:")
            .with_user("alice", "alice-password"),
    ));

    let session = store
        .assume_role_with_web_identity(
            request(
                "ldap-main",
                "alice:alice-password",
                "AKIALDAP",
                "ldap-secret",
            ),
            NOW,
        )
        .expect("ldap session");
    let principal = store.principal("ldap:alice").expect("ldap principal");

    assert_eq!(session.principal_id, "ldap:alice");
    assert_eq!(principal.kind, PrincipalKind::LdapUser);
    assert_eq!(
        store.resolve_credential("AKIALDAP", NOW).unwrap().scope,
        Some(scope())
    );
}

#[test]
fn active_directory_provider_fails_closed_for_bad_or_disabled_subjects() {
    let mut store = AuthStore::new();
    store.upsert_identity_provider(IdentityProvider::ActiveDirectory(
        DirectoryProvider::active_directory("ad-main", "ldaps://ad.example", "ad:")
            .with_user("alice", "correct-password")
            .with_disabled_user("disabled", "disabled-password"),
    ));

    assert!(matches!(
        store.assume_role_with_web_identity(
            request("ad-main", "alice:wrong-password", "AKIAADBAD", "ad-secret"),
            NOW,
        ),
        Err(AuthError::InvalidIdentityProviderToken(_))
    ));
    assert_eq!(
        store.assume_role_with_web_identity(
            request(
                "ad-main",
                "disabled:disabled-password",
                "AKIAADDISABLED",
                "ad-secret"
            ),
            NOW,
        ),
        Err(AuthError::DisabledDirectorySubject("disabled".to_string()))
    );
}

#[test]
fn saml_provider_accepts_signed_assertions_and_rejects_expired_assertions() {
    let mut store = AuthStore::new();
    let provider = SamlProvider::hs256(
        "saml-main",
        "https://idp.example/saml",
        "bucketwarden",
        "saml-key-1",
        "saml-shared-secret",
        "saml:",
    );
    let assertion = provider
        .sign_assertion("carol", NOW + 60, NOW)
        .expect("assertion");
    let expired = provider
        .sign_assertion("carol", NOW - 1, NOW - 60)
        .expect("expired assertion");
    store.upsert_identity_provider(IdentityProvider::Saml(provider));

    let session = store
        .assume_role_with_web_identity(
            request("saml-main", &assertion, "AKIASAML", "saml-secret"),
            NOW,
        )
        .expect("saml session");
    let principal = store.principal("saml:carol").expect("saml principal");

    assert_eq!(session.principal_id, "saml:carol");
    assert_eq!(principal.kind, PrincipalKind::SamlSubject);
    assert_eq!(
        store.assume_role_with_web_identity(
            request("saml-main", &expired, "AKIASAMLEXPIRED", "saml-secret"),
            NOW,
        ),
        Err(AuthError::WebIdentityTokenExpired)
    );
}

#[test]
fn identity_provider_support_report_exposes_contract_controls_and_caveats() {
    let report = AuthStore::new().identity_provider_support_report();

    assert!(report.native_support_state.contains(&"ldap"));
    assert!(report.native_support_state.contains(&"active-directory"));
    assert!(report.native_support_state.contains(&"saml"));
    assert!(report
        .semantic_parity
        .contains("typed principals and scoped session credentials"));
    assert!(report
        .configuration_admin_surface
        .contains(&"AuthStore::upsert_identity_provider"));
    assert!(report
        .security_governance_impact
        .contains(&"invalid secret/signature rejection"));
    assert!(report
        .observability_evidence_fields
        .contains(&"provider_id"));
    assert!(report.failure_modes.contains(&"DisabledDirectorySubject"));
    assert!(report
        .validation_test_coverage
        .contains(&"crates/bucketwarden-auth/tests/external_identity_providers.rs"));
    assert!(report
        .product_specific_caveats
        .iter()
        .any(|caveat| caveat.contains("production LDAP/AD network binding")));
}

fn request(
    provider_id: &str,
    token: &str,
    access_key_id: &str,
    secret_access_key: &str,
) -> bucketwarden_auth::AssumeRoleWithWebIdentityRequest {
    bucketwarden_auth::AssumeRoleWithWebIdentityRequest {
        provider_id: provider_id.to_string(),
        web_identity_token: token.to_string(),
        access_key_id: access_key_id.to_string(),
        secret_access_key: secret_access_key.to_string(),
        session_token: format!("{access_key_id}-session"),
        duration_seconds: 900,
        scope: scope(),
    }
}

fn scope() -> CredentialScope {
    CredentialScope::new(vec!["s3:*".to_string()], vec!["tenant-a/".to_string()])
}
