use runx_contracts::JsonValue;

use crate::policy::{
    LocalAdmissionGrant, LocalScopeAdmissionOptions, ScopeAdmission, ScopeAdmissionStatus,
    credential_grant::{credential_grant_requirement, find_matching_grant},
    scope::unique_strings,
};

use super::util::{non_empty_option, non_empty_vec};

#[must_use]
pub fn build_local_scope_admission(
    auth: Option<&JsonValue>,
    grants: &[LocalAdmissionGrant],
    options: &LocalScopeAdmissionOptions,
) -> ScopeAdmission {
    let Some(requirement) = credential_grant_requirement(auth) else {
        return scope_admission_allow(Vec::new(), Vec::new(), None, "no connected auth requested");
    };

    let requested_scopes = unique_strings(&requirement.scopes);
    if options.denied_before_grant_resolution.unwrap_or(false) {
        return scope_admission_deny(
            requested_scopes,
            Vec::new(),
            vec!["structural policy denied before connected auth grant resolution".to_owned()],
            "structural policy denied before grant resolution",
        );
    }

    match find_matching_grant(
        &requirement,
        grants,
        options.connected_auth_checked_at.as_deref(),
        options.wildcard_scopes_trusted,
    ) {
        Some(grant) => scope_admission_allow(
            requested_scopes,
            unique_strings(&grant.scopes),
            Some(grant.grant_id.clone()),
            "matching active grant admitted",
        ),
        None => scope_admission_deny(
            requested_scopes,
            Vec::new(),
            vec![format!(
                "connected auth grant required for provider '{}'",
                requirement.provider
            )],
            "no matching active grant resolved",
        ),
    }
}

fn scope_admission_allow(
    requested_scopes: Vec<String>,
    granted_scopes: Vec<String>,
    grant_id: Option<String>,
    summary: &str,
) -> ScopeAdmission {
    ScopeAdmission {
        status: ScopeAdmissionStatus::Allow,
        requested_scopes: non_empty_vec(requested_scopes),
        granted_scopes: non_empty_vec(granted_scopes),
        grant_id: non_empty_option(grant_id),
        reasons: None,
        decision_summary: Some(summary.to_owned()),
    }
}

fn scope_admission_deny(
    requested_scopes: Vec<String>,
    granted_scopes: Vec<String>,
    reasons: Vec<String>,
    summary: &str,
) -> ScopeAdmission {
    ScopeAdmission {
        status: ScopeAdmissionStatus::Deny,
        requested_scopes: non_empty_vec(requested_scopes),
        granted_scopes: non_empty_vec(granted_scopes),
        grant_id: None,
        reasons: Some(non_empty_vec(reasons)),
        decision_summary: Some(summary.to_owned()),
    }
}
