use std::collections::HashSet;

use anyhow::Result;
use interim_models::{
    client::AppDbClient,
    workspace_user_perm::{self, WorkspaceUserPerm},
};
use interim_pgtypes::{
    client::WorkspaceClient,
    pg_acl::PgPrivilegeType,
    pg_database::PgDatabase,
    pg_role::{PgRole, RoleTree, user_id_from_rolname},
};
use sqlx::query;
use uuid::Uuid;

/// Derive workspace access control permissions from the permission grants of
/// a workspace's backing database.
pub(crate) async fn sync_for_workspace(
    workspace_id: Uuid,
    app_db: &mut AppDbClient,
    workspace_client: &mut WorkspaceClient,
    db_role_prefix: &str,
) -> Result<()> {
    let db = PgDatabase::current().fetch_one(workspace_client).await?;
    let explicit_roles = PgRole::with_name_in(
        db.datacl
            .unwrap_or_default()
            .into_iter()
            .filter(|item| {
                item.privileges
                    .iter()
                    .any(|privilege| privilege.privilege == PgPrivilegeType::Connect)
            })
            .map(|item| item.grantee)
            .collect(),
    )
    .fetch_all(workspace_client)
    .await?;
    let mut all_roles: HashSet<PgRole> = HashSet::new();
    for explicit_role in explicit_roles {
        if let Some(role_tree) = RoleTree::members_of_oid(explicit_role.oid)
            .fetch_tree(workspace_client)
            .await?
        {
            for implicit_role in role_tree.flatten_inherited() {
                all_roles.insert(implicit_role.clone());
            }
        }
    }
    let user_ids: Vec<Uuid> = all_roles
        .iter()
        .filter_map(|role| user_id_from_rolname(&role.rolname, db_role_prefix).ok())
        .collect();
    query!(
        "delete from workspace_user_perms where workspace_id = $1 and not (user_id = any($2))",
        workspace_id,
        user_ids.as_slice(),
    )
    .execute(app_db.get_conn())
    .await?;
    for user_id in user_ids {
        WorkspaceUserPerm::insert()
            .workspace_id(workspace_id)
            .user_id(user_id)
            .perm(workspace_user_perm::PermissionValue::Connect)
            .build()?
            .execute(app_db)
            .await?;
    }
    Ok(())
}
