use std::collections::HashMap;

use crate::plugins::grants::{grant_by_key, normalize_grant_inputs};

pub const ORGANIZATION_RESOURCE: &str = "organization";
pub const MEMBER_RESOURCE: &str = "member";
pub const INVITATION_RESOURCE: &str = "invitation";

pub const CREATE_ACTION: &str = "create";
pub const READ_ACTION: &str = "read";
pub const UPDATE_ACTION: &str = "update";
pub const DELETE_ACTION: &str = "delete";
pub const CANCEL_ACTION: &str = "cancel";

/// Permission definition
pub type Permissions = HashMap<String, Vec<String>>;

/// Role with associated permissions
#[derive(Debug, Clone)]
pub struct Role {
    pub name: String,
    pub permissions: Permissions,
}

/// Get default role definitions matching TypeScript implementation.
pub fn default_roles() -> HashMap<String, Role> {
    let mut roles = HashMap::new();

    roles.insert(
        "owner".to_string(),
        Role {
            name: "owner".to_string(),
            permissions: HashMap::from([
                (
                    ORGANIZATION_RESOURCE.to_string(),
                    vec![UPDATE_ACTION.to_string(), DELETE_ACTION.to_string()],
                ),
                (
                    MEMBER_RESOURCE.to_string(),
                    vec![
                        CREATE_ACTION.to_string(),
                        UPDATE_ACTION.to_string(),
                        DELETE_ACTION.to_string(),
                    ],
                ),
                (
                    INVITATION_RESOURCE.to_string(),
                    vec![CREATE_ACTION.to_string(), CANCEL_ACTION.to_string()],
                ),
            ]),
        },
    );

    roles.insert(
        "admin".to_string(),
        Role {
            name: "admin".to_string(),
            permissions: HashMap::from([
                (
                    ORGANIZATION_RESOURCE.to_string(),
                    vec![UPDATE_ACTION.to_string()],
                ),
                (
                    MEMBER_RESOURCE.to_string(),
                    vec![
                        CREATE_ACTION.to_string(),
                        UPDATE_ACTION.to_string(),
                        DELETE_ACTION.to_string(),
                    ],
                ),
                (
                    INVITATION_RESOURCE.to_string(),
                    vec![CREATE_ACTION.to_string(), CANCEL_ACTION.to_string()],
                ),
            ]),
        },
    );

    roles.insert(
        "member".to_string(),
        Role {
            name: "member".to_string(),
            permissions: HashMap::new(),
        },
    );

    roles
}

/// Check if a role has permission for an action on a resource.
pub fn has_permission(
    role: &str,
    resource: &str,
    action: &str,
    custom_roles: &HashMap<String, crate::plugins::organization::RolePermissions>,
    grant_catalog: &[crate::plugins::grants::GrantDescriptor],
) -> bool {
    let normalized_resource = normalize_token(resource);
    let normalized_action = normalize_token(action);
    if normalized_resource.is_empty() || normalized_action.is_empty() {
        return false;
    }

    let default = default_roles();

    if let Some(custom_role) = custom_roles.get(role) {
        if normalize_role_grant_keys(custom_role)
            .iter()
            .any(|grant_key| {
                grant_by_key(grant_catalog, grant_key).is_some_and(|descriptor| {
                    normalize_token(&descriptor.resource) == normalized_resource
                        && normalize_token(&descriptor.action) == normalized_action
                })
            })
        {
            return true;
        }

        if permission_list_allows(
            custom_role.grants.get(&normalized_resource),
            &normalized_action,
        ) {
            return true;
        }

        if permission_list_allows(
            legacy_custom_actions(custom_role, &normalized_resource),
            &normalized_action,
        ) {
            return true;
        }
    }

    if let Some(role_def) = default.get(role)
        && let Some(actions) = role_def.permissions.get(&normalized_resource)
    {
        return permission_list_allows(Some(actions), &normalized_action);
    }

    false
}

/// Handle composite roles (comma-separated).
pub fn has_permission_any(
    roles_str: &str,
    resource: &str,
    action: &str,
    custom_roles: &HashMap<String, crate::plugins::organization::RolePermissions>,
    grant_catalog: &[crate::plugins::grants::GrantDescriptor],
) -> bool {
    for role in roles_str.split(',').map(|s| s.trim()) {
        if has_permission(role, resource, action, custom_roles, grant_catalog) {
            return true;
        }
    }
    false
}

fn normalize_token(value: &str) -> String {
    value.trim().to_ascii_lowercase()
}

fn permission_list_allows(actions: Option<&Vec<String>>, required_action: &str) -> bool {
    actions.is_some_and(|values| {
        values.iter().any(|action| {
            let normalized = normalize_token(action);
            normalized == "*" || normalized == required_action
        })
    })
}

fn legacy_custom_actions<'a>(
    custom_role: &'a crate::plugins::organization::RolePermissions,
    resource: &str,
) -> Option<&'a Vec<String>> {
    match resource {
        ORGANIZATION_RESOURCE => Some(&custom_role.organization),
        MEMBER_RESOURCE => Some(&custom_role.member),
        INVITATION_RESOURCE => Some(&custom_role.invitation),
        _ => None,
    }
}

fn normalize_role_grant_keys(
    custom_role: &crate::plugins::organization::RolePermissions,
) -> Vec<String> {
    normalize_grant_inputs(
        custom_role.grant_keys.iter().map(String::as_str),
        custom_role.right_keys.iter().map(String::as_str),
        custom_role.right_modules.iter(),
    )
}

#[cfg(test)]
#[path = "__tests__/rbac.rs"]
mod tests;
