use super::{
    GraphScopeAdmissionDecision, GraphScopeAdmissionRequest,
    scope::{scope_allows, unique_strings},
};

#[must_use]
pub fn admit_graph_step_scopes(
    request: &GraphScopeAdmissionRequest,
) -> GraphScopeAdmissionDecision {
    let requested_scopes = unique_strings(&request.requested_scopes);
    let granted_scopes = unique_strings(&request.grant.scopes);
    let denied_scopes = denied_scopes(&requested_scopes, &granted_scopes);

    if denied_scopes.is_empty() {
        return GraphScopeAdmissionDecision::Allow {
            reasons: allow_reasons(&requested_scopes),
            step_id: request.step_id.clone(),
            requested_scopes,
            granted_scopes,
            grant_id: request.grant.grant_id.clone(),
        };
    }

    GraphScopeAdmissionDecision::Deny {
        reasons: vec![format!(
            "step '{}' requested scope(s) outside graph grant: {}",
            request.step_id,
            denied_scopes.join(", ")
        )],
        step_id: request.step_id.clone(),
        requested_scopes,
        granted_scopes,
        grant_id: request.grant.grant_id.clone(),
    }
}

fn denied_scopes(requested_scopes: &[String], granted_scopes: &[String]) -> Vec<String> {
    requested_scopes
        .iter()
        .filter(|scope| {
            !granted_scopes
                .iter()
                .any(|granted_scope| scope_allows(granted_scope, scope, true))
        })
        .cloned()
        .collect()
}

fn allow_reasons(requested_scopes: &[String]) -> Vec<String> {
    if requested_scopes.is_empty() {
        vec!["graph step requested no scopes".to_owned()]
    } else {
        vec!["graph step scopes allowed".to_owned()]
    }
}
