use derive_builder::Builder;
use serde::{Deserialize, Serialize};
use sqlx::query_as;
use uuid::Uuid;

use crate::client::AppDbClient;

/// Assigns an access control permission on a workspace to a user. These are
/// derived from the permission grants of the workspace's backing database.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct WorkspaceMembership {
    /// Primary key (defaults to UUIDv7).
    pub id: Uuid,

    /// Workspace to which the permission belongs.
    pub workspace_id: Uuid,

    /// **Synthesized field** generated by joining to the `workspaces` table.
    pub workspace_display_name: String,

    /// User to which the permission belongs.
    pub user_id: Uuid,
}

impl WorkspaceMembership {
    /// Construct a single-field query to fetch workspace permissions assigned
    /// to a user.
    pub fn belonging_to_user(id: Uuid) -> BelongingToUserQuery {
        BelongingToUserQuery { id }
    }

    /// Build an insert statement to create a new object.
    pub fn insert() -> InsertBuilder {
        InsertBuilder::default()
    }
}

#[derive(Clone, Debug)]
pub struct BelongingToUserQuery {
    id: Uuid,
}

impl BelongingToUserQuery {
    pub async fn fetch_all(
        self,
        app_db: &mut AppDbClient,
    ) -> Result<Vec<WorkspaceMembership>, sqlx::Error> {
        query_as!(
            WorkspaceMembership,
            r#"
select
    p.id as id,
    p.workspace_id as workspace_id,
    p.user_id as user_id,
    w.display_name as workspace_display_name
from workspace_memberships as p
    inner join workspaces as w
        on w.id = p.workspace_id
where p.user_id = $1
"#,
            self.id,
        )
        .fetch_all(app_db.get_conn())
        .await
    }
}

#[derive(Builder, Clone, Debug)]
pub struct Insert {
    workspace_id: Uuid,
    user_id: Uuid,
}

impl Insert {
    pub async fn execute(
        self,
        app_db: &mut AppDbClient,
    ) -> Result<WorkspaceMembership, sqlx::Error> {
        query_as!(
            WorkspaceMembership,
            r#"
with p as (
    insert into workspace_memberships (workspace_id, user_id) values ($1, $2)
    returning
        id,
        workspace_id,
        user_id
)
select
    p.id as id,
    p.workspace_id as workspace_id,
    p.user_id as user_id,
    w.display_name as workspace_display_name
from p inner join workspaces as w
    on w.id = p.workspace_id
"#,
            self.workspace_id,
            self.user_id,
        )
        .fetch_one(app_db.get_conn())
        .await
    }
}
