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

use crate::{
    client::AppDbClient,
    errors::{QueryError, QueryResult},
};

/// 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 upsert statement to create a new object. If the new object is a
    /// duplicate, no update is performed.
    pub fn upsert() -> UpsertBuilder {
        UpsertBuilder::default()
    }

    /// Build a delete statement to remove the matching record if it exists.
    pub fn delete() -> DeleteBuilder {
        DeleteBuilder::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)]
#[builder(build_fn(error = "QueryError"), vis = "pub", pattern = "owned")]
struct Upsert {
    workspace_id: Uuid,
    user_id: Uuid,
}

impl UpsertBuilder {
    pub async fn execute(self, app_db: &mut AppDbClient) -> QueryResult<()> {
        // To circumvent performance and complexity concerns, this method does not
        // return the upserted record. Refer to:
        // https://stackoverflow.com/a/42217872

        let spec = self.build()?;
        query!(
            r#"
insert into workspace_memberships (workspace_id, user_id) values ($1, $2)
on conflict (workspace_id, user_id) do nothing
"#,
            spec.workspace_id,
            spec.user_id,
        )
        .execute(app_db.get_conn())
        .await?;
        Ok(())
    }
}

#[derive(Builder, Clone, Debug)]
#[builder(build_fn(error = "QueryError"), vis = "pub", pattern = "owned")]
pub struct Delete {
    workspace_id: Uuid,
    user_id: Uuid,
}

impl DeleteBuilder {
    pub async fn execute(self, app_db: &mut AppDbClient) -> QueryResult<()> {
        let spec = self.build()?;
        query!(
            r#"
delete from workspace_memberships
where workspace_id = $1 and user_id = $2
"#,
            spec.workspace_id,
            spec.user_id,
        )
        .execute(app_db.get_conn())
        .await?;
        Ok(())
    }
}
