pub mod handlers;
pub mod rbac;
pub mod types;

use std::collections::HashMap;
use std::sync::Arc;

use crate::plugins::grants::{merged_grant_catalog, merged_native_right_catalog};
use async_trait::async_trait;
use athena_auth_core::NativeRightModuleInput;
use athena_auth_core::adapters::DatabaseAdapter;
use athena_auth_core::error::AuthResult;
use athena_auth_core::plugin::{AuthContext, AuthPlugin, AuthRoute};
use athena_auth_core::types::{AuthRequest, AuthResponse, HttpMethod};

#[cfg(feature = "axum")]
use athena_auth_core::plugin::{AuthState, AxumPlugin};

/// Permission definitions for a role
#[derive(Debug, Clone, Default)]
pub struct RolePermissions {
    pub organization: Vec<String>,
    pub member: Vec<String>,
    pub invitation: Vec<String>,
    pub grants: HashMap<String, Vec<String>>,
    pub grant_keys: Vec<String>,
    pub right_keys: Vec<String>,
    pub right_modules: Vec<NativeRightModuleInput>,
}

#[cfg(test)]
mod route_tests {
    use super::*;
    use crate::plugins::test_helpers;
    use athena_auth_core::adapters::SessionOps;
    use athena_auth_core::entity::{AuthSession, AuthUser};
    use athena_auth_core::{AuthPlugin, CreateUser, GrantDescriptor, HttpMethod};
    use chrono::Duration;
    use std::collections::HashMap;

    fn json_body(resp: &AuthResponse) -> serde_json::Value {
        serde_json::from_slice(&resp.body).expect("response should be valid JSON")
    }

    #[tokio::test]
    async fn test_has_permission_route_accepts_native_right_inputs() {
        let ctx = test_helpers::create_test_context();
        let (user, session) = test_helpers::create_user_and_session(
            &ctx,
            CreateUser::new()
                .with_email("org-rights@example.com")
                .with_name("Org Rights User"),
            Duration::hours(24),
        )
        .await;
        let plugin = OrganizationPlugin {
            config: OrganizationConfig {
                creator_role: "billing-webhook".to_string(),
                roles: HashMap::from([(
                    "billing-webhook".to_string(),
                    RolePermissions {
                        right_modules: vec![NativeRightModuleInput {
                            key: "billing".to_string(),
                            right_keys: vec!["payment-link.paid".to_string()],
                        }],
                        ..Default::default()
                    },
                )]),
                grant_catalog: vec![
                    GrantDescriptor {
                        key: "payment-link.paid".to_string(),
                        resource: "payment-link".to_string(),
                        action: "paid".to_string(),
                        description: "Grant access when a payment-link payment has succeeded."
                            .to_string(),
                    },
                    GrantDescriptor {
                        key: "payment-link.failed".to_string(),
                        resource: "payment-link".to_string(),
                        action: "failed".to_string(),
                        description: "Grant access when a payment-link payment has failed."
                            .to_string(),
                    },
                ],
                ..Default::default()
            },
        };

        let create_req = test_helpers::create_auth_json_request_no_query(
            HttpMethod::Post,
            "/organization/create",
            Some(&session.token),
            Some(serde_json::json!({
                "name": "Billing Rights Org",
                "slug": "billing-rights-org"
            })),
        );
        let create_resp = plugin.on_request(&create_req, &ctx).await.unwrap().unwrap();
        assert_eq!(create_resp.status, 200);
        let create_json = json_body(&create_resp);
        let organization_id = create_json["id"]
            .as_str()
            .expect("organization/create response missing id");

        let allowed_req = test_helpers::create_auth_json_request_no_query(
            HttpMethod::Post,
            "/organization/has-permission",
            Some(&session.token),
            Some(serde_json::json!({
                "organizationId": organization_id,
                "grantKeys": ["payment-link.paid"]
            })),
        );
        let allowed_resp = plugin
            .on_request(&allowed_req, &ctx)
            .await
            .unwrap()
            .unwrap();
        assert_eq!(allowed_resp.status, 200);
        let allowed_json = json_body(&allowed_resp);
        assert_eq!(allowed_json["success"], true);
        assert!(allowed_json["error"].is_null());

        let denied_req = test_helpers::create_auth_json_request_no_query(
            HttpMethod::Post,
            "/organization/has-permission",
            Some(&session.token),
            Some(serde_json::json!({
                "organizationId": organization_id,
                "grantKeys": ["payment-link.failed"]
            })),
        );
        let denied_resp = plugin.on_request(&denied_req, &ctx).await.unwrap().unwrap();
        assert_eq!(denied_resp.status, 200);
        let denied_json = json_body(&denied_resp);
        assert_eq!(denied_json["success"], false);
        assert_eq!(denied_json["error"], "Permission denied");

        let alias_req = test_helpers::create_auth_json_request_no_query(
            HttpMethod::Post,
            "/organization/has-permission",
            Some(&session.token),
            Some(serde_json::json!({
                "organizationId": organization_id,
                "rightKeys": ["payment-link.paid"]
            })),
        );
        let alias_resp = plugin.on_request(&alias_req, &ctx).await.unwrap().unwrap();
        assert_eq!(alias_resp.status, 200);
        let alias_json = json_body(&alias_resp);
        assert_eq!(alias_json["success"], true);
        assert!(alias_json["error"].is_null());

        let module_req = test_helpers::create_auth_json_request_no_query(
            HttpMethod::Post,
            "/organization/has-permission",
            Some(&session.token),
            Some(serde_json::json!({
                "organizationId": organization_id,
                "rightModules": [{
                    "key": "billing",
                    "rightKeys": ["payment-link.paid"]
                }]
            })),
        );
        let module_resp = plugin.on_request(&module_req, &ctx).await.unwrap().unwrap();
        assert_eq!(module_resp.status, 200);
        let module_json = json_body(&module_resp);
        assert_eq!(module_json["success"], true);
        assert!(module_json["error"].is_null());

        let stored_session = ctx
            .database
            .get_session(&session.token)
            .await
            .expect("session lookup should succeed")
            .expect("session should still exist");
        assert_eq!(
            stored_session.active_organization_id(),
            Some(organization_id),
            "organization/create should still make the new org active for the same session"
        );
        assert_eq!(stored_session.user_id(), user.id());
    }
}

/// Payload passed to custom organization invitation email callbacks.
#[derive(Debug, Clone)]
pub struct InvitationEmailPayload {
    pub invitation_id: String,
    pub organization_id: String,
    pub organization_name: String,
    pub organization_slug: String,
    pub invited_email: String,
    pub role: String,
    pub inviter_user_id: String,
    pub inviter_name: Option<String>,
    pub inviter_email: Option<String>,
    pub invitation_url: String,
}

/// Custom callback for sending organization invitation emails.
#[async_trait]
pub trait SendInvitationEmail: Send + Sync + std::fmt::Debug {
    async fn send(&self, payload: InvitationEmailPayload) -> AuthResult<()>;
}

/// Configuration for the Organization plugin
#[derive(Debug, Clone, athena_auth_core::PluginConfig)]
#[plugin(name = "OrganizationPlugin")]
pub struct OrganizationConfig {
    /// Allow users to create organizations (default: true)
    #[config(default = true)]
    pub allow_user_to_create_organization: bool,
    /// Maximum organizations per user (None = unlimited)
    #[config(default = None)]
    pub organization_limit: Option<usize>,
    /// Maximum members per organization (None = unlimited)
    #[config(default = Some(100))]
    pub membership_limit: Option<usize>,
    /// Role assigned to organization creator (default: "owner")
    #[config(default = "owner".to_string())]
    pub creator_role: String,
    /// Invitation expiration in seconds (default: 48 hours)
    #[config(default = 60 * 60 * 48)]
    pub invitation_expires_in: u64,
    /// Whether to send invitation emails via configured transport/callback.
    #[config(default = true)]
    pub send_email_notifications: bool,
    /// Require email verification before accepting an invitation.
    #[config(default = true)]
    pub require_email_verification_on_invitation: bool,
    /// Maximum pending invitations per organization (None = unlimited)
    #[config(default = Some(100))]
    pub invitation_limit: Option<usize>,
    /// Optional custom invitation sender callback (overrides EmailProvider).
    #[config(default = None, skip)]
    pub send_invitation_email: Option<Arc<dyn SendInvitationEmail>>,
    /// Disable organization deletion (default: false)
    #[config(default = false)]
    pub disable_organization_deletion: bool,
    /// Custom role definitions (extending default roles)
    #[config(default = HashMap::new(), skip)]
    pub roles: HashMap<String, RolePermissions>,
    /// Optional grant catalog used to resolve native grant keys.
    #[config(default = Vec::new(), skip)]
    pub grant_catalog: Vec<crate::plugins::grants::GrantDescriptor>,
    /// Optional extra native-right descriptors layered onto the shared Athena catalog.
    #[config(default = Vec::new(), skip)]
    pub native_right_catalog: Vec<athena_rights::AthenaRightDescriptor>,
}

/// Organization plugin for multi-tenancy support
pub struct OrganizationPlugin {
    config: OrganizationConfig,
}

impl OrganizationPlugin {
    pub fn with_grant_catalog(
        mut self,
        grant_catalog: Vec<crate::plugins::grants::GrantDescriptor>,
    ) -> Self {
        self.config.grant_catalog = grant_catalog;
        self
    }

    pub fn with_native_right_catalog(
        mut self,
        native_right_catalog: Vec<athena_rights::AthenaRightDescriptor>,
    ) -> Self {
        self.config.native_right_catalog = native_right_catalog;
        self
    }
}

impl OrganizationConfig {
    pub fn effective_grant_catalog(&self) -> Vec<crate::plugins::grants::GrantDescriptor> {
        merged_grant_catalog(&self.grant_catalog, &self.native_right_catalog)
    }

    pub fn merged_native_right_catalog(&self) -> Vec<athena_rights::AthenaRightDescriptor> {
        merged_native_right_catalog(&self.native_right_catalog)
    }
}

#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<DB: DatabaseAdapter> AuthPlugin<DB> for OrganizationPlugin {
    fn name(&self) -> &'static str {
        "organization"
    }

    fn routes(&self) -> Vec<AuthRoute> {
        vec![
            AuthRoute::post("/organization/create", "create_organization"),
            AuthRoute::post("/organization/update", "update_organization"),
            AuthRoute::post("/organization/delete", "delete_organization"),
            AuthRoute::get("/organization/list", "list_organizations"),
            AuthRoute::get(
                "/organization/get-full-organization",
                "get_full_organization",
            ),
            AuthRoute::post("/organization/check-slug", "check_slug"),
            AuthRoute::post("/organization/set-active", "set_active_organization"),
            AuthRoute::post("/organization/leave", "leave_organization"),
            AuthRoute::get("/organization/get-active-member", "get_active_member"),
            AuthRoute::get("/organization/list-members", "list_members"),
            AuthRoute::post("/organization/remove-member", "remove_member"),
            AuthRoute::post("/organization/update-member-role", "update_member_role"),
            AuthRoute::post("/organization/invite-member", "invite_member"),
            AuthRoute::get("/organization/get-invitation", "get_invitation"),
            AuthRoute::get("/organization/list-invitations", "list_invitations"),
            AuthRoute::get(
                "/organization/list-user-invitations",
                "list_user_invitations",
            ),
            AuthRoute::post("/organization/accept-invitation", "accept_invitation"),
            AuthRoute::post("/organization/reject-invitation", "reject_invitation"),
            AuthRoute::post("/organization/cancel-invitation", "cancel_invitation"),
            AuthRoute::post("/organization/has-permission", "has_permission"),
        ]
    }

    async fn on_request(
        &self,
        req: &AuthRequest,
        ctx: &AuthContext<DB>,
    ) -> AuthResult<Option<AuthResponse>> {
        match (req.method(), req.path()) {
            (HttpMethod::Post, "/organization/create") => Ok(Some(
                handlers::org::handle_create_organization(req, ctx, &self.config).await?,
            )),
            (HttpMethod::Post, "/organization/update") => Ok(Some(
                handlers::org::handle_update_organization(req, ctx, &self.config).await?,
            )),
            (HttpMethod::Post, "/organization/delete") => Ok(Some(
                handlers::org::handle_delete_organization(req, ctx, &self.config).await?,
            )),
            (HttpMethod::Get, "/organization/list") => Ok(Some(
                handlers::org::handle_list_organizations(req, ctx).await?,
            )),
            (HttpMethod::Get, "/organization/get-full-organization") => Ok(Some(
                handlers::org::handle_get_full_organization(req, ctx).await?,
            )),
            (HttpMethod::Post, "/organization/check-slug") => {
                Ok(Some(handlers::org::handle_check_slug(req, ctx).await?))
            }
            (HttpMethod::Post, "/organization/set-active") => Ok(Some(
                handlers::org::handle_set_active_organization(req, ctx).await?,
            )),
            (HttpMethod::Post, "/organization/leave") => Ok(Some(
                handlers::org::handle_leave_organization(req, ctx).await?,
            )),
            (HttpMethod::Get, "/organization/get-active-member") => Ok(Some(
                handlers::member::handle_get_active_member(req, ctx).await?,
            )),
            (HttpMethod::Get, "/organization/list-members") => {
                Ok(Some(handlers::member::handle_list_members(req, ctx).await?))
            }
            (HttpMethod::Post, "/organization/remove-member") => Ok(Some(
                handlers::member::handle_remove_member(req, ctx, &self.config).await?,
            )),
            (HttpMethod::Post, "/organization/update-member-role") => Ok(Some(
                handlers::member::handle_update_member_role(req, ctx, &self.config).await?,
            )),
            (HttpMethod::Post, "/organization/invite-member") => Ok(Some(
                handlers::invitation::handle_invite_member(req, ctx, &self.config).await?,
            )),
            (HttpMethod::Get, "/organization/get-invitation") => Ok(Some(
                handlers::invitation::handle_get_invitation(req, ctx).await?,
            )),
            (HttpMethod::Get, "/organization/list-invitations") => Ok(Some(
                handlers::invitation::handle_list_invitations(req, ctx).await?,
            )),
            (HttpMethod::Get, "/organization/list-user-invitations") => Ok(Some(
                handlers::invitation::handle_list_user_invitations(req, ctx).await?,
            )),
            (HttpMethod::Post, "/organization/accept-invitation") => Ok(Some(
                handlers::invitation::handle_accept_invitation(req, ctx, &self.config).await?,
            )),
            (HttpMethod::Post, "/organization/reject-invitation") => Ok(Some(
                handlers::invitation::handle_reject_invitation(req, ctx).await?,
            )),
            (HttpMethod::Post, "/organization/cancel-invitation") => Ok(Some(
                handlers::invitation::handle_cancel_invitation(req, ctx, &self.config).await?,
            )),
            (HttpMethod::Post, "/organization/has-permission") => Ok(Some(
                handlers::handle_has_permission(req, ctx, &self.config).await?,
            )),
            _ => Ok(None),
        }
    }
}

#[cfg(feature = "axum")]
mod axum_impl {
    use super::*;
    use std::sync::Arc;

    use athena_auth_core::error::AuthError;
    use athena_auth_core::extractors::{CurrentSession, ValidatedJson};
    use axum::Json;
    use axum::extract::{Extension, Query, State};

    use super::handlers::has_permission_core;
    use super::handlers::invitation::{
        accept_invitation_core, cancel_invitation_core, get_invitation_core, invite_member_core,
        list_invitations_core, list_user_invitations_core, reject_invitation_core,
    };
    use super::handlers::member::{
        get_active_member_core, list_members_core, remove_member_core, update_member_role_core,
    };
    use super::handlers::org::{
        check_slug_core, create_organization_core, delete_organization_core,
        get_full_organization_core, leave_organization_core, list_organizations_cached_json_core,
        set_active_organization_core, update_organization_core,
    };
    use super::types::*;

    #[derive(Clone)]
    struct PluginState {
        config: OrganizationConfig,
    }

    async fn handle_create_organization<DB: DatabaseAdapter>(
        State(state): State<AuthState<DB>>,
        Extension(ps): Extension<Arc<PluginState>>,
        CurrentSession { user, session, .. }: CurrentSession<DB>,
        ValidatedJson(body): ValidatedJson<CreateOrganizationRequest>,
    ) -> Result<Json<CreateOrganizationResponse<DB::Organization, MemberResponse>>, AuthError> {
        let ctx = state.to_context();
        let result = create_organization_core(&body, &user, &session, &ps.config, &ctx).await?;
        Ok(Json(result))
    }

    async fn handle_update_organization<DB: DatabaseAdapter>(
        State(state): State<AuthState<DB>>,
        Extension(ps): Extension<Arc<PluginState>>,
        CurrentSession { user, session, .. }: CurrentSession<DB>,
        ValidatedJson(body): ValidatedJson<UpdateOrganizationRequest>,
    ) -> Result<Json<DB::Organization>, AuthError> {
        let ctx = state.to_context();
        let result = update_organization_core(&body, &user, &session, &ps.config, &ctx).await?;
        Ok(Json(result))
    }

    async fn handle_delete_organization<DB: DatabaseAdapter>(
        State(state): State<AuthState<DB>>,
        Extension(ps): Extension<Arc<PluginState>>,
        CurrentSession { user, .. }: CurrentSession<DB>,
        Json(body): Json<DeleteOrganizationRequest>,
    ) -> Result<Json<SuccessResponse>, AuthError> {
        let ctx = state.to_context();
        let result = delete_organization_core(&body, &user, &ps.config, &ctx).await?;
        Ok(Json(result))
    }

    async fn handle_list_organizations<DB: DatabaseAdapter>(
        State(state): State<AuthState<DB>>,
        CurrentSession { user, .. }: CurrentSession<DB>,
    ) -> Result<Json<serde_json::Value>, AuthError> {
        let ctx = state.to_context();
        let result = list_organizations_cached_json_core(&user, &ctx).await?;
        Ok(Json(result))
    }

    async fn handle_get_full_organization<DB: DatabaseAdapter>(
        State(state): State<AuthState<DB>>,
        CurrentSession { user, session, .. }: CurrentSession<DB>,
        Query(query): Query<GetFullOrganizationQuery>,
    ) -> Result<Json<Option<FullOrganizationResponse<DB::Organization, DB::Invitation>>>, AuthError>
    {
        let ctx = state.to_context();
        let result = get_full_organization_core(&query, &user, &session, &ctx).await?;
        Ok(Json(result))
    }

    async fn handle_check_slug<DB: DatabaseAdapter>(
        State(state): State<AuthState<DB>>,
        CurrentSession { .. }: CurrentSession<DB>,
        Json(body): Json<CheckSlugRequest>,
    ) -> Result<Json<CheckSlugResponse>, AuthError> {
        let ctx = state.to_context();
        let result = check_slug_core(&body, &ctx).await?;
        Ok(Json(result))
    }

    async fn handle_set_active_organization<DB: DatabaseAdapter>(
        State(state): State<AuthState<DB>>,
        CurrentSession { user, session, .. }: CurrentSession<DB>,
        Json(body): Json<SetActiveOrganizationRequest>,
    ) -> Result<Json<DB::Session>, AuthError> {
        let ctx = state.to_context();
        let result = set_active_organization_core(&body, &user, &session, &ctx).await?;
        Ok(Json(result))
    }

    async fn handle_leave_organization<DB: DatabaseAdapter>(
        State(state): State<AuthState<DB>>,
        CurrentSession { user, session, .. }: CurrentSession<DB>,
        Json(body): Json<LeaveOrganizationRequest>,
    ) -> Result<Json<SuccessResponse>, AuthError> {
        let ctx = state.to_context();
        let result = leave_organization_core(&body, &user, &session, &ctx).await?;
        Ok(Json(result))
    }

    async fn handle_get_active_member<DB: DatabaseAdapter>(
        State(state): State<AuthState<DB>>,
        CurrentSession { user, session, .. }: CurrentSession<DB>,
    ) -> Result<Json<MemberResponse>, AuthError> {
        let ctx = state.to_context();
        let result = get_active_member_core(&user, &session, &ctx).await?;
        Ok(Json(result))
    }

    async fn handle_list_members<DB: DatabaseAdapter>(
        State(state): State<AuthState<DB>>,
        CurrentSession { user, session, .. }: CurrentSession<DB>,
        Query(query): Query<ListMembersQuery>,
    ) -> Result<Json<ListMembersResponse>, AuthError> {
        let ctx = state.to_context();
        let result = list_members_core(&query, &user, &session, &ctx).await?;
        Ok(Json(result))
    }

    async fn handle_remove_member<DB: DatabaseAdapter>(
        State(state): State<AuthState<DB>>,
        Extension(ps): Extension<Arc<PluginState>>,
        CurrentSession { user, session, .. }: CurrentSession<DB>,
        Json(body): Json<RemoveMemberRequest>,
    ) -> Result<Json<RemovedMemberResponse>, AuthError> {
        let ctx = state.to_context();
        let result = remove_member_core(&body, &user, &session, &ps.config, &ctx).await?;
        Ok(Json(result))
    }

    async fn handle_update_member_role<DB: DatabaseAdapter>(
        State(state): State<AuthState<DB>>,
        Extension(ps): Extension<Arc<PluginState>>,
        CurrentSession { user, session, .. }: CurrentSession<DB>,
        Json(body): Json<UpdateMemberRoleRequest>,
    ) -> Result<Json<MemberWrappedResponse>, AuthError> {
        let ctx = state.to_context();
        let result = update_member_role_core(&body, &user, &session, &ps.config, &ctx).await?;
        Ok(Json(result))
    }

    async fn handle_invite_member<DB: DatabaseAdapter>(
        State(state): State<AuthState<DB>>,
        Extension(ps): Extension<Arc<PluginState>>,
        CurrentSession { user, session, .. }: CurrentSession<DB>,
        ValidatedJson(body): ValidatedJson<InviteMemberRequest>,
    ) -> Result<Json<DB::Invitation>, AuthError> {
        let ctx = state.to_context();
        let result = invite_member_core(&body, &user, &session, &ps.config, &ctx).await?;
        Ok(Json(result))
    }

    async fn handle_get_invitation<DB: DatabaseAdapter>(
        State(state): State<AuthState<DB>>,
        Query(query): Query<GetInvitationQuery>,
    ) -> Result<Json<GetInvitationResponse<DB::Invitation>>, AuthError> {
        let ctx = state.to_context();
        let result = get_invitation_core(&query, &ctx).await?;
        Ok(Json(result))
    }

    async fn handle_list_invitations<DB: DatabaseAdapter>(
        State(state): State<AuthState<DB>>,
        CurrentSession { user, session, .. }: CurrentSession<DB>,
        Query(query): Query<ListInvitationsQuery>,
    ) -> Result<Json<Vec<DB::Invitation>>, AuthError> {
        let ctx = state.to_context();
        let result = list_invitations_core(&query, &user, &session, &ctx).await?;
        Ok(Json(result))
    }

    async fn handle_list_user_invitations<DB: DatabaseAdapter>(
        State(state): State<AuthState<DB>>,
        CurrentSession { user, .. }: CurrentSession<DB>,
    ) -> Result<Json<Vec<DB::Invitation>>, AuthError> {
        let ctx = state.to_context();
        let result = list_user_invitations_core(&user, &ctx).await?;
        Ok(Json(result))
    }

    async fn handle_accept_invitation<DB: DatabaseAdapter>(
        State(state): State<AuthState<DB>>,
        Extension(ps): Extension<Arc<PluginState>>,
        CurrentSession { user, session, .. }: CurrentSession<DB>,
        Json(body): Json<AcceptInvitationRequest>,
    ) -> Result<Json<AcceptInvitationResponse<DB::Invitation>>, AuthError> {
        let ctx = state.to_context();
        let result = accept_invitation_core(&body, &user, &session, &ps.config, &ctx).await?;
        Ok(Json(result))
    }

    async fn handle_reject_invitation<DB: DatabaseAdapter>(
        State(state): State<AuthState<DB>>,
        CurrentSession { user, .. }: CurrentSession<DB>,
        Json(body): Json<RejectInvitationRequest>,
    ) -> Result<Json<SuccessResponse>, AuthError> {
        let ctx = state.to_context();
        let result = reject_invitation_core(&body, &user, &ctx).await?;
        Ok(Json(result))
    }

    async fn handle_cancel_invitation<DB: DatabaseAdapter>(
        State(state): State<AuthState<DB>>,
        Extension(ps): Extension<Arc<PluginState>>,
        CurrentSession { user, .. }: CurrentSession<DB>,
        Json(body): Json<CancelInvitationRequest>,
    ) -> Result<Json<SuccessResponse>, AuthError> {
        let ctx = state.to_context();
        let result = cancel_invitation_core(&body, &user, &ps.config, &ctx).await?;
        Ok(Json(result))
    }

    async fn handle_has_permission<DB: DatabaseAdapter>(
        State(state): State<AuthState<DB>>,
        Extension(ps): Extension<Arc<PluginState>>,
        CurrentSession { user, session, .. }: CurrentSession<DB>,
        Json(body): Json<HasPermissionRequest>,
    ) -> Result<Json<HasPermissionResponse>, AuthError> {
        let ctx = state.to_context();
        let result = has_permission_core(&body, &user, &session, &ps.config, &ctx).await?;
        Ok(Json(result))
    }

    #[async_trait]
    impl<DB: DatabaseAdapter> AxumPlugin<DB> for OrganizationPlugin {
        fn name(&self) -> &'static str {
            "organization"
        }

        fn router(&self) -> axum::Router<AuthState<DB>> {
            use axum::routing::{get, post};

            let plugin_state = Arc::new(PluginState {
                config: self.config.clone(),
            });

            axum::Router::new()
                .route(
                    "/organization/create",
                    post(handle_create_organization::<DB>),
                )
                .route(
                    "/organization/update",
                    post(handle_update_organization::<DB>),
                )
                .route(
                    "/organization/delete",
                    post(handle_delete_organization::<DB>),
                )
                .route("/organization/list", get(handle_list_organizations::<DB>))
                .route(
                    "/organization/get-full-organization",
                    get(handle_get_full_organization::<DB>),
                )
                .route("/organization/check-slug", post(handle_check_slug::<DB>))
                .route(
                    "/organization/set-active",
                    post(handle_set_active_organization::<DB>),
                )
                .route("/organization/leave", post(handle_leave_organization::<DB>))
                .route(
                    "/organization/get-active-member",
                    get(handle_get_active_member::<DB>),
                )
                .route("/organization/list-members", get(handle_list_members::<DB>))
                .route(
                    "/organization/remove-member",
                    post(handle_remove_member::<DB>),
                )
                .route(
                    "/organization/update-member-role",
                    post(handle_update_member_role::<DB>),
                )
                .route(
                    "/organization/invite-member",
                    post(handle_invite_member::<DB>),
                )
                .route(
                    "/organization/get-invitation",
                    get(handle_get_invitation::<DB>),
                )
                .route(
                    "/organization/list-invitations",
                    get(handle_list_invitations::<DB>),
                )
                .route(
                    "/organization/list-user-invitations",
                    get(handle_list_user_invitations::<DB>),
                )
                .route(
                    "/organization/accept-invitation",
                    post(handle_accept_invitation::<DB>),
                )
                .route(
                    "/organization/reject-invitation",
                    post(handle_reject_invitation::<DB>),
                )
                .route(
                    "/organization/cancel-invitation",
                    post(handle_cancel_invitation::<DB>),
                )
                .route(
                    "/organization/has-permission",
                    post(handle_has_permission::<DB>),
                )
                .layer(Extension(plugin_state))
        }
    }
}
