use bucketwarden_auth::{
    AssumeRoleRequest, AssumeRoleWithCustomIdentityRequest, AssumeRoleWithWebIdentityRequest,
    AuthError, AuthStore, CredentialScope, DirectoryProvider, IdentityProvider, OperatorRole,
};

const NOW: u64 = 1_700_000_000;

#[test]
fn direct_assume_role_requires_role_authority_and_issues_scoped_session() {
    let mut store = AuthStore::new();
    store.create_service_account("ops");
    store
        .assign_operator_role("ops", OperatorRole::SecurityOfficer, "tenant-a")
        .expect("role");

    let denied = store.assume_role(
        AssumeRoleRequest::new(
            "ops",
            "tenant-b-writer",
            "tenant-b",
            900,
            scope(),
            "ASIA-DENIED",
            "denied-secret",
            "denied-token",
        ),
        NOW,
    );
    assert!(matches!(
        denied,
        Err(AuthError::RoleAssumptionDenied { .. })
    ));

    let session = store
        .assume_role(
            AssumeRoleRequest::new(
                "ops",
                "tenant-a-reader",
                "tenant-a",
                900,
                scope(),
                "ASIA-ASSUMED",
                "assumed-secret",
                "assumed-token",
            ),
            NOW,
        )
        .expect("assume role");

    assert_eq!(session.principal_id, "ops");
    assert_eq!(session.expires_at_epoch_seconds, NOW + 900);
    let resolved = store
        .resolve_credential("ASIA-ASSUMED", NOW + 899)
        .expect("resolve");
    assert!(resolved
        .scope
        .as_ref()
        .is_some_and(|scope| scope.permits("s3:GetObject", "tenant-a/photos/cat.jpg")));
}

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

    let session = store
        .assume_role_with_ldap(
            AssumeRoleWithWebIdentityRequest::new(
                "ldap-main",
                "alice:secret",
                300,
                scope(),
                "ASIA-LDAP",
                "ldap-session-secret",
                "ldap-session-token",
            ),
            NOW,
        )
        .expect("ldap assume role");

    assert_eq!(session.principal_id, "ldap:alice");
    assert!(matches!(
        store.assume_role_with_ldap(
            AssumeRoleWithWebIdentityRequest::new(
                "ldap-main",
                "disabled:secret",
                300,
                scope(),
                "ASIA-LDAP-DISABLED",
                "secret",
                "token",
            ),
            NOW,
        ),
        Err(AuthError::DisabledDirectorySubject(_))
    ));
}

#[test]
fn custom_identity_assertion_creates_scoped_session_and_rejects_bad_secret() {
    let mut store = AuthStore::new();
    store
        .create_custom_identity("custom:build-bot", "identity-secret")
        .expect("custom identity");

    assert!(matches!(
        store.assume_role_with_custom_identity(
            AssumeRoleWithCustomIdentityRequest::new(
                "custom:build-bot",
                "wrong-secret",
                120,
                scope(),
                "ASIA-CUSTOM-BAD",
                "secret",
                "token",
            ),
            NOW,
        ),
        Err(AuthError::InvalidCustomIdentitySecret(_))
    ));

    let session = store
        .assume_role_with_custom_identity(
            AssumeRoleWithCustomIdentityRequest::new(
                "custom:build-bot",
                "identity-secret",
                120,
                scope(),
                "ASIA-CUSTOM",
                "custom-session-secret",
                "custom-session-token",
            ),
            NOW,
        )
        .expect("custom assume role");

    assert_eq!(session.principal_id, "custom:build-bot");
    assert_eq!(session.expires_at_epoch_seconds, NOW + 120);
}

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

    assert!(report.native_support_state.contains(&"AssumeRole"));
    assert!(report.native_support_state.contains(&"AssumeRoleWithLDAP"));
    assert!(report.native_support_state.contains(&"Custom identity"));
    assert!(report
        .security_governance_impact
        .contains(&"ManageCredentials role gate for direct AssumeRole"));
    assert!(report.failure_modes.contains(&"RoleAssumptionDenied"));
    assert!(report
        .failure_modes
        .contains(&"InvalidCustomIdentitySecret"));
    assert!(report
        .validation_test_coverage
        .contains(&"crates/bucketwarden-auth/tests/role_assumption_support_contract.rs"));
    assert!(report
        .product_specific_caveats
        .iter()
        .any(|caveat| caveat.contains("trust-policy document")));
}

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