pub mod invitation;
pub mod member;
pub mod org;

pub use invitation::*;
pub use member::*;
pub use org::*;

use athena_auth_core::adapters::DatabaseAdapter;
use athena_auth_core::entity::{AuthMember, AuthSession, AuthUser};
use athena_auth_core::error::{AuthError, AuthResult};
use athena_auth_core::plugin::AuthContext;
use athena_auth_core::types::{AuthRequest, AuthResponse};
use moka::sync::Cache;
use std::collections::HashSet;
use std::sync::{Arc, LazyLock};
use std::time::Duration;

use super::OrganizationConfig;
use super::rbac::has_permission_any;
use super::types::{HasPermissionRequest, HasPermissionResponse};
use crate::plugins::grants::{
    merge_permissions, normalize_grant_inputs, permissions_map_from_grant_keys,
};

const ORG_LIST_CACHE_MAX_ENTRIES: u64 = 20_000;
const ORG_LIST_CACHE_TTL_SECONDS: u64 = 5;

static ORGANIZATION_LIST_CACHE: LazyLock<Cache<String, Arc<serde_json::Value>>> =
    LazyLock::new(|| {
        Cache::builder()
            .max_capacity(ORG_LIST_CACHE_MAX_ENTRIES)
            .time_to_live(Duration::from_secs(ORG_LIST_CACHE_TTL_SECONDS))
            .build()
    });

/// Helper function to require authenticated session
pub(crate) async fn require_session<DB: DatabaseAdapter>(
    req: &AuthRequest,
    ctx: &AuthContext<DB>,
) -> AuthResult<(DB::User, DB::Session)> {
    let session_manager = ctx.session_manager();

    if let Some(token) = session_manager.extract_session_token(req)
        && let Some(session) = session_manager.get_session(&token).await?
        && let Some(user) = ctx.database.get_user_by_id(session.user_id()).await?
    {
        return Ok((user, session));
    }

    Err(AuthError::Unauthenticated)
}

/// Helper function to get organization ID from request or session
pub(crate) async fn resolve_organization_id<DB: DatabaseAdapter>(
    org_id: Option<&str>,
    org_slug: Option<&str>,
    session: &DB::Session,
    ctx: &AuthContext<DB>,
) -> AuthResult<String> {
    if let Some(id) = org_id {
        return Ok(id.to_string());
    }

    if let Some(slug) = org_slug {
        if let Some(org) = ctx.database.get_organization_by_slug(slug).await? {
            use athena_auth_core::entity::AuthOrganization;
            return Ok(org.id().to_string());
        }
        return Err(AuthError::not_found("Organization not found"));
    }

    session
        .active_organization_id()
        .map(|s| s.to_string())
        .ok_or_else(|| AuthError::bad_request("No active organization"))
}

pub(crate) fn get_cached_organization_list_json(user_id: &str) -> Option<serde_json::Value> {
    ORGANIZATION_LIST_CACHE
        .get(user_id)
        .map(|cached| (*cached).clone())
}

pub(crate) fn cache_organization_list_json(user_id: &str, value: serde_json::Value) {
    ORGANIZATION_LIST_CACHE.insert(user_id.to_string(), Arc::new(value));
}

pub(crate) async fn refresh_organization_list_cache_for_user<DB: DatabaseAdapter>(
    user_id: &str,
    ctx: &AuthContext<DB>,
) -> AuthResult<()> {
    let organizations = ctx.database.list_user_organizations(user_id).await?;
    let json = serde_json::to_value(&organizations).map_err(|error| {
        AuthError::internal(format!(
            "Failed to serialize organization list cache payload: {error}"
        ))
    })?;
    cache_organization_list_json(user_id, json);
    Ok(())
}

pub(crate) async fn refresh_organization_list_cache_for_users<DB: DatabaseAdapter>(
    user_ids: &[String],
    ctx: &AuthContext<DB>,
) -> AuthResult<()> {
    let unique_user_ids: HashSet<String> = user_ids.iter().cloned().collect();
    for user_id in unique_user_ids {
        refresh_organization_list_cache_for_user(&user_id, ctx).await?;
    }
    Ok(())
}

pub(crate) async fn collect_member_user_ids_for_organization<DB: DatabaseAdapter>(
    organization_id: &str,
    ctx: &AuthContext<DB>,
) -> AuthResult<Vec<String>> {
    let members = ctx
        .database
        .list_organization_members(organization_id)
        .await?;
    Ok(members
        .into_iter()
        .map(|member| member.user_id().to_string())
        .collect())
}

pub(crate) async fn has_permission_core<DB: DatabaseAdapter>(
    body: &HasPermissionRequest,
    user: &DB::User,
    session: &DB::Session,
    config: &OrganizationConfig,
    ctx: &AuthContext<DB>,
) -> AuthResult<HasPermissionResponse> {
    let org_id =
        resolve_organization_id(body.organization_id.as_deref(), None, session, ctx).await?;
    let effective_grant_catalog = config.effective_grant_catalog();

    let member = ctx
        .database
        .get_member(&org_id, user.id())
        .await?
        .ok_or_else(|| AuthError::forbidden("Not a member of this organization"))?;

    let effective_grant_keys = normalize_grant_inputs(
        body.grant_keys.iter().map(String::as_str),
        body.right_keys.iter().map(String::as_str),
        body.right_modules.iter(),
    );
    let mut effective_permissions = body.permissions.clone();
    merge_permissions(
        &mut effective_permissions,
        permissions_map_from_grant_keys(
            effective_grant_keys.iter().map(String::as_str),
            &effective_grant_catalog,
        ),
    );

    if effective_permissions.is_empty()
        && (!body.permissions.is_empty() || !effective_grant_keys.is_empty())
    {
        return Ok(HasPermissionResponse {
            success: false,
            error: Some("Permission denied".to_string()),
        });
    }

    let mut has_all_permissions = true;

    for (resource_str, actions) in &effective_permissions {
        for action_str in actions {
            if !has_permission_any(
                member.role(),
                resource_str,
                action_str,
                &config.roles,
                &effective_grant_catalog,
            ) {
                has_all_permissions = false;
                break;
            }
        }

        if !has_all_permissions {
            break;
        }
    }

    Ok(HasPermissionResponse {
        success: has_all_permissions,
        error: if has_all_permissions {
            None
        } else {
            Some("Permission denied".to_string())
        },
    })
}

/// Handle has-permission request
pub async fn handle_has_permission<DB: DatabaseAdapter>(
    req: &AuthRequest,
    ctx: &AuthContext<DB>,
    config: &OrganizationConfig,
) -> AuthResult<AuthResponse> {
    let (user, session) = require_session(req, ctx).await?;
    let body: HasPermissionRequest = match athena_auth_core::validate_request_body(req) {
        Ok(v) => v,
        Err(resp) => return Ok(resp),
    };
    let response = has_permission_core(&body, &user, &session, config, ctx).await?;
    Ok(AuthResponse::json(200, &response)?)
}
