use super::{
    collect_member_user_ids_for_organization, get_cached_organization_list_json,
    refresh_organization_list_cache_for_user, refresh_organization_list_cache_for_users,
    require_session, resolve_organization_id,
};
use crate::plugins::helpers::resolve_optional_avatar_value;
use crate::plugins::organization::OrganizationConfig;
use crate::plugins::organization::rbac::{
    DELETE_ACTION, ORGANIZATION_RESOURCE, UPDATE_ACTION, has_permission_any,
};
use crate::plugins::organization::types::{
    CheckSlugRequest, CheckSlugResponse, CreateOrganizationRequest, CreateOrganizationResponse,
    DeleteOrganizationRequest, FullOrganizationResponse, GetFullOrganizationQuery,
    LeaveOrganizationRequest, MemberResponse, SetActiveOrganizationRequest, SuccessResponse,
    UpdateOrganizationRequest,
};
use athena_auth_core::adapters::DatabaseAdapter;
use athena_auth_core::entity::{AuthMember, AuthOrganization, AuthSession, AuthUser};
use athena_auth_core::error::{AuthError, AuthResult};
use athena_auth_core::plugin::AuthContext;
use athena_auth_core::types::{
    AuthRequest, AuthResponse, CreateMember, CreateOrganization, UpdateOrganization,
};
use athena_auth_core::{AvatarTemplateContext, resolve_avatar_response_urls_in_json};
use uuid::Uuid;

// ---------------------------------------------------------------------------
// Core functions
// ---------------------------------------------------------------------------

pub(crate) async fn create_organization_core<DB: DatabaseAdapter>(
    body: &CreateOrganizationRequest,
    user: &DB::User,
    session: &DB::Session,
    config: &OrganizationConfig,
    ctx: &AuthContext<DB>,
) -> AuthResult<CreateOrganizationResponse<DB::Organization, MemberResponse>> {
    if !config.allow_user_to_create_organization {
        return Err(AuthError::forbidden("Organization creation is not allowed"));
    }

    if let Some(limit) = config.organization_limit {
        let user_orgs = ctx.database.list_user_organizations(user.id()).await?;
        if user_orgs.len() >= limit {
            return Err(AuthError::bad_request(format!(
                "Organization limit of {} reached",
                limit
            )));
        }
    }

    if ctx
        .database
        .get_organization_by_slug(&body.slug)
        .await?
        .is_some()
    {
        return Err(AuthError::bad_request("Slug is already taken"));
    }

    let organization_id = Uuid::new_v4().to_string();
    let resolved_logo = resolve_optional_avatar_value(
        ctx,
        body.logo.as_deref().or(body.image.as_deref()),
        AvatarTemplateContext::for_organization(&organization_id).with_user_id(user.id()),
    )
    .await?;

    let org_data = CreateOrganization {
        id: Some(organization_id),
        name: body.name.clone(),
        slug: body.slug.clone(),
        logo: resolved_logo,
        metadata: body.metadata.clone(),
    };

    let organization = ctx.database.create_organization(org_data).await?;

    let member_data = CreateMember {
        organization_id: organization.id().to_string(),
        user_id: user.id().to_string(),
        role: config.creator_role.clone(),
    };

    let member = ctx.database.create_member(member_data).await?;
    let member_response = MemberResponse::from_member_and_user(&member, user);

    // New organizations should become active immediately for the current session
    // so follow-up endpoints that rely on active organization work out-of-the-box.
    ctx.database
        .update_session_active_organization(session.token(), Some(organization.id()))
        .await?;

    if let Err(cache_error) = refresh_organization_list_cache_for_user(user.id(), ctx).await {
        tracing::warn!(
            user_id = %user.id(),
            error = %cache_error,
            "Failed to refresh organization list cache after create_organization"
        );
    }

    Ok(CreateOrganizationResponse {
        organization,
        members: vec![member_response],
    })
}

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

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

    if !has_permission_any(
        member.role(),
        ORGANIZATION_RESOURCE,
        UPDATE_ACTION,
        &config.roles,
        &effective_grant_catalog,
    ) {
        return Err(AuthError::forbidden(
            "You don't have permission to update this organization",
        ));
    }

    if let Some(ref new_slug) = body.slug
        && let Some(existing) = ctx.database.get_organization_by_slug(new_slug).await?
        && existing.id() != org_id
    {
        return Err(AuthError::bad_request("Slug is already taken"));
    }

    let resolved_logo = resolve_optional_avatar_value(
        ctx,
        body.logo.as_deref().or(body.image.as_deref()),
        AvatarTemplateContext::for_organization(&org_id).with_user_id(user.id()),
    )
    .await?;

    let update_data = UpdateOrganization {
        name: body.name.clone(),
        slug: body.slug.clone(),
        logo: resolved_logo,
        metadata: body.metadata.clone(),
    };

    let updated = ctx
        .database
        .update_organization(&org_id, update_data)
        .await?;

    match collect_member_user_ids_for_organization(&org_id, ctx).await {
        Ok(affected_user_ids) => {
            if let Err(cache_error) =
                refresh_organization_list_cache_for_users(&affected_user_ids, ctx).await
            {
                tracing::warn!(
                    organization_id = %org_id,
                    error = %cache_error,
                    "Failed to refresh organization list cache after update_organization"
                );
            }
        }
        Err(cache_error) => {
            tracing::warn!(
                organization_id = %org_id,
                error = %cache_error,
                "Failed to resolve affected users for organization cache refresh after update_organization"
            );
        }
    }

    Ok(updated)
}

pub(crate) async fn delete_organization_core<DB: DatabaseAdapter>(
    body: &DeleteOrganizationRequest,
    user: &DB::User,
    config: &OrganizationConfig,
    ctx: &AuthContext<DB>,
) -> AuthResult<SuccessResponse> {
    let effective_grant_catalog = config.effective_grant_catalog();
    if config.disable_organization_deletion {
        return Err(AuthError::forbidden("Organization deletion is disabled"));
    }

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

    if !has_permission_any(
        member.role(),
        ORGANIZATION_RESOURCE,
        DELETE_ACTION,
        &config.roles,
        &effective_grant_catalog,
    ) {
        return Err(AuthError::forbidden(
            "You don't have permission to delete this organization",
        ));
    }

    let affected_user_ids =
        match collect_member_user_ids_for_organization(&body.organization_id, ctx).await {
            Ok(ids) => Some(ids),
            Err(cache_error) => {
                tracing::warn!(
                    organization_id = %body.organization_id,
                    error = %cache_error,
                    "Failed to resolve affected users before delete_organization cache refresh"
                );
                None
            }
        };

    ctx.database
        .delete_organization(&body.organization_id)
        .await?;

    if let Some(ids) = affected_user_ids
        && let Err(cache_error) = refresh_organization_list_cache_for_users(&ids, ctx).await
    {
        tracing::warn!(
            organization_id = %body.organization_id,
            error = %cache_error,
            "Failed to refresh organization list cache after delete_organization"
        );
    }

    Ok(SuccessResponse { success: true })
}

pub(crate) async fn list_organizations_core<DB: DatabaseAdapter>(
    user: &DB::User,
    ctx: &AuthContext<DB>,
) -> AuthResult<Vec<DB::Organization>> {
    let organizations = ctx.database.list_user_organizations(user.id()).await?;
    Ok(organizations)
}

pub(crate) async fn list_organizations_cached_json_core<DB: DatabaseAdapter>(
    user: &DB::User,
    ctx: &AuthContext<DB>,
) -> AuthResult<serde_json::Value> {
    if let Some(cached) = get_cached_organization_list_json(user.id()) {
        let mut response = cached;
        resolve_avatar_response_urls_in_json(&ctx.config, &mut response).await?;
        return Ok(response);
    }

    let organizations = list_organizations_core(user, ctx).await?;
    let payload = serde_json::to_value(&organizations).map_err(|error| {
        AuthError::internal(format!(
            "Failed to serialize organization list response: {error}"
        ))
    })?;
    super::cache_organization_list_json(user.id(), payload.clone());
    let mut response = payload;
    resolve_avatar_response_urls_in_json(&ctx.config, &mut response).await?;
    Ok(response)
}

pub(crate) async fn get_full_organization_core<DB: DatabaseAdapter>(
    query: &GetFullOrganizationQuery,
    user: &DB::User,
    session: &DB::Session,
    ctx: &AuthContext<DB>,
) -> AuthResult<Option<FullOrganizationResponse<DB::Organization, DB::Invitation>>> {
    let org_id = if query.organization_id.is_some() || query.organization_slug.is_some() {
        resolve_organization_id(
            query.organization_id.as_deref(),
            query.organization_slug.as_deref(),
            session,
            ctx,
        )
        .await?
    } else if let Some(active_org_id) = session.active_organization_id() {
        active_org_id.to_string()
    } else {
        return Ok(None);
    };

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

    let organization = ctx
        .database
        .get_organization_by_id(&org_id)
        .await?
        .ok_or_else(|| AuthError::not_found("Organization not found"))?;

    let members_raw = ctx.database.list_organization_members(&org_id).await?;
    let mut members = Vec::with_capacity(members_raw.len());

    for member in &members_raw {
        if let Some(user_info) = ctx.database.get_user_by_id(member.user_id()).await? {
            members.push(MemberResponse::from_member_and_user(member, &user_info));
        }
    }

    let invitations = ctx.database.list_organization_invitations(&org_id).await?;

    Ok(Some(FullOrganizationResponse {
        organization,
        members,
        invitations,
    }))
}

pub(crate) async fn check_slug_core<DB: DatabaseAdapter>(
    body: &CheckSlugRequest,
    ctx: &AuthContext<DB>,
) -> AuthResult<CheckSlugResponse> {
    let exists = ctx
        .database
        .get_organization_by_slug(&body.slug)
        .await?
        .is_some();

    Ok(CheckSlugResponse { status: !exists })
}

pub(crate) async fn set_active_organization_core<DB: DatabaseAdapter>(
    body: &SetActiveOrganizationRequest,
    user: &DB::User,
    session: &DB::Session,
    ctx: &AuthContext<DB>,
) -> AuthResult<DB::Session> {
    let org_id = if body.organization_id.is_some() || body.organization_slug.is_some() {
        Some(
            resolve_organization_id(
                body.organization_id.as_deref(),
                body.organization_slug.as_deref(),
                session,
                ctx,
            )
            .await?,
        )
    } else {
        None
    };

    if let Some(ref oid) = org_id {
        ctx.database
            .get_member(oid, user.id())
            .await?
            .ok_or_else(|| AuthError::forbidden("Not a member of this organization"))?;
    }

    let updated_session = ctx
        .database
        .update_session_active_organization(session.token(), org_id.as_deref())
        .await?;

    Ok(updated_session)
}

pub(crate) async fn leave_organization_core<DB: DatabaseAdapter>(
    body: &LeaveOrganizationRequest,
    user: &DB::User,
    session: &DB::Session,
    ctx: &AuthContext<DB>,
) -> AuthResult<SuccessResponse> {
    let member = ctx
        .database
        .get_member(&body.organization_id, user.id())
        .await?
        .ok_or_else(|| AuthError::forbidden("Not a member of this organization"))?;

    if member.role().contains("owner") {
        let all_members = ctx
            .database
            .list_organization_members(&body.organization_id)
            .await?;
        let owner_count = all_members
            .iter()
            .filter(|m| m.role().contains("owner"))
            .count();

        if owner_count <= 1 {
            return Err(AuthError::bad_request(
                "Cannot leave organization as the last owner. Delete the organization or transfer ownership first.",
            ));
        }
    }

    ctx.database.delete_member(member.id()).await?;

    if session.active_organization_id() == Some(&body.organization_id) {
        ctx.database
            .update_session_active_organization(session.token(), None)
            .await?;
    }

    if let Err(cache_error) = refresh_organization_list_cache_for_user(user.id(), ctx).await {
        tracing::warn!(
            user_id = %user.id(),
            error = %cache_error,
            "Failed to refresh organization list cache after leave_organization"
        );
    }

    Ok(SuccessResponse { success: true })
}

// ---------------------------------------------------------------------------
// Old handlers (rewritten to call core)
// ---------------------------------------------------------------------------

/// Handle create organization request
pub async fn handle_create_organization<DB: DatabaseAdapter>(
    req: &AuthRequest,
    ctx: &AuthContext<DB>,
    config: &OrganizationConfig,
) -> AuthResult<AuthResponse> {
    let (user, session) = require_session(req, ctx).await?;
    let body: CreateOrganizationRequest = match athena_auth_core::validate_request_body(req) {
        Ok(v) => v,
        Err(resp) => return Ok(resp),
    };
    let response = create_organization_core(&body, &user, &session, config, ctx).await?;
    let mut response_json = serde_json::to_value(&response).map_err(|error| {
        AuthError::internal(format!(
            "Failed to serialize create organization response: {error}"
        ))
    })?;
    resolve_avatar_response_urls_in_json(&ctx.config, &mut response_json).await?;
    Ok(AuthResponse::json(200, &response_json)?)
}

/// Handle update organization request
pub async fn handle_update_organization<DB: DatabaseAdapter>(
    req: &AuthRequest,
    ctx: &AuthContext<DB>,
    config: &OrganizationConfig,
) -> AuthResult<AuthResponse> {
    let (user, session) = require_session(req, ctx).await?;
    let body: UpdateOrganizationRequest = match athena_auth_core::validate_request_body(req) {
        Ok(v) => v,
        Err(resp) => return Ok(resp),
    };
    let updated = update_organization_core(&body, &user, &session, config, ctx).await?;
    let mut updated_json = serde_json::to_value(&updated).map_err(|error| {
        AuthError::internal(format!(
            "Failed to serialize update organization response: {error}"
        ))
    })?;
    resolve_avatar_response_urls_in_json(&ctx.config, &mut updated_json).await?;
    Ok(AuthResponse::json(200, &updated_json)?)
}

/// Handle delete organization request
pub async fn handle_delete_organization<DB: DatabaseAdapter>(
    req: &AuthRequest,
    ctx: &AuthContext<DB>,
    config: &OrganizationConfig,
) -> AuthResult<AuthResponse> {
    let (user, _session) = require_session(req, ctx).await?;
    let body: DeleteOrganizationRequest = match athena_auth_core::validate_request_body(req) {
        Ok(v) => v,
        Err(resp) => return Ok(resp),
    };
    let response = delete_organization_core(&body, &user, config, ctx).await?;
    Ok(AuthResponse::json(200, &response)?)
}

/// Handle list organizations request
pub async fn handle_list_organizations<DB: DatabaseAdapter>(
    req: &AuthRequest,
    ctx: &AuthContext<DB>,
) -> AuthResult<AuthResponse> {
    let (user, _session) = require_session(req, ctx).await?;
    let organizations = list_organizations_cached_json_core(&user, ctx).await?;
    Ok(AuthResponse::json(200, &organizations)?)
}

/// Handle get full organization request
pub async fn handle_get_full_organization<DB: DatabaseAdapter>(
    req: &AuthRequest,
    ctx: &AuthContext<DB>,
) -> AuthResult<AuthResponse> {
    let (user, session) = require_session(req, ctx).await?;
    let query = parse_query::<GetFullOrganizationQuery>(&req.query);
    let response = get_full_organization_core(&query, &user, &session, ctx).await?;
    let mut response_json = serde_json::to_value(&response).map_err(|error| {
        AuthError::internal(format!(
            "Failed to serialize full organization response: {error}"
        ))
    })?;
    resolve_avatar_response_urls_in_json(&ctx.config, &mut response_json).await?;
    Ok(AuthResponse::json(200, &response_json)?)
}

/// Handle check slug request
pub async fn handle_check_slug<DB: DatabaseAdapter>(
    req: &AuthRequest,
    ctx: &AuthContext<DB>,
) -> AuthResult<AuthResponse> {
    let _session = require_session(req, ctx).await?;
    let body: CheckSlugRequest = match athena_auth_core::validate_request_body(req) {
        Ok(v) => v,
        Err(resp) => return Ok(resp),
    };
    let response = check_slug_core(&body, ctx).await?;
    Ok(AuthResponse::json(200, &response)?)
}

/// Handle set active organization request
pub async fn handle_set_active_organization<DB: DatabaseAdapter>(
    req: &AuthRequest,
    ctx: &AuthContext<DB>,
) -> AuthResult<AuthResponse> {
    let (user, session) = require_session(req, ctx).await?;
    let body: SetActiveOrganizationRequest = match athena_auth_core::validate_request_body(req) {
        Ok(v) => v,
        Err(resp) => return Ok(resp),
    };
    let updated_session = set_active_organization_core(&body, &user, &session, ctx).await?;
    Ok(AuthResponse::json(200, &updated_session)?)
}

/// Handle leave organization request
pub async fn handle_leave_organization<DB: DatabaseAdapter>(
    req: &AuthRequest,
    ctx: &AuthContext<DB>,
) -> AuthResult<AuthResponse> {
    let (user, session) = require_session(req, ctx).await?;
    let body: LeaveOrganizationRequest = match athena_auth_core::validate_request_body(req) {
        Ok(v) => v,
        Err(resp) => return Ok(resp),
    };
    let response = leave_organization_core(&body, &user, &session, ctx).await?;
    Ok(AuthResponse::json(200, &response)?)
}

/// Helper function to parse query parameters into a struct
fn parse_query<T: Default + serde::de::DeserializeOwned>(
    query: &std::collections::HashMap<String, String>,
) -> T {
    let json_value =
        serde_json::to_value(query).unwrap_or(serde_json::Value::Object(Default::default()));
    serde_json::from_value(json_value).unwrap_or_default()
}
