use derive_builder::Builder;
use redact::Secret;
use sqlx::query_as;
use url::Url;
use uuid::Uuid;

use crate::client::AppDbClient;

/// A workspace is 1:1 with a Postgres "database".
#[derive(Clone, Debug)]
pub struct Workspace {
    /// Primary key (defaults to UUIDv7).
    pub id: Uuid,

    /// Human friendly name for the workspace.
    pub name: String,

    /// `postgresql://` URL of the instance and database hosting this workspace.
    pub url: Secret<String>,

    /// ID of the user account that created this workspace.
    pub owner_id: Uuid,
}

impl Workspace {
    /// Build an insert statement to create a new workspace.
    pub fn insert() -> InsertableWorkspaceBuilder {
        InsertableWorkspaceBuilder::default()
    }

    /// Build a single-field query by workspace ID.
    pub fn with_id(id: Uuid) -> WithIdQuery {
        WithIdQuery { id }
    }

    /// Build a query for workspaces filtered by a user's Phono permissions.
    pub fn with_permission_in<I: IntoIterator<Item = &'static str>>(
        perms: I,
    ) -> WithPermissionInQueryPartial {
        let perms: Vec<String> = perms.into_iter().map(ToOwned::to_owned).collect();
        WithPermissionInQueryPartial { perms }
    }
}

pub struct WithPermissionInQueryPartial {
    perms: Vec<String>,
}

impl WithPermissionInQueryPartial {
    pub fn for_user(self, user_id: Uuid) -> WithPermissionInQuery {
        WithPermissionInQuery {
            perms: self.perms,
            user_id,
        }
    }
}

pub struct WithPermissionInQuery {
    perms: Vec<String>,
    user_id: Uuid,
}

impl WithPermissionInQuery {
    pub async fn fetch_all(self, app_db: &mut AppDbClient) -> Result<Vec<Workspace>, sqlx::Error> {
        query_as!(
            Workspace,
            "
select workspaces.*
from workspaces inner join workspace_user_perms as p
    on p.workspace_id = workspaces.id
where p.user_id = $1 and perm = ANY($2)
",
            self.user_id,
            self.perms.as_slice(),
        )
        .fetch_all(&mut *app_db.conn)
        .await
    }
}

pub struct WithIdQuery {
    id: Uuid,
}

impl WithIdQuery {
    pub async fn fetch_optional(
        self,
        app_db: &mut AppDbClient,
    ) -> Result<Option<Workspace>, sqlx::Error> {
        query_as!(
            Workspace,
            "select * from workspaces where id = $1",
            &self.id
        )
        .fetch_optional(&mut *app_db.conn)
        .await
    }

    pub async fn fetch_one(self, app_db: &mut AppDbClient) -> Result<Workspace, sqlx::Error> {
        query_as!(
            Workspace,
            "select * from workspaces where id = $1",
            &self.id
        )
        .fetch_one(&mut *app_db.conn)
        .await
    }
}

#[derive(Builder)]
pub struct InsertableWorkspace {
    url: Url,
    owner_id: Uuid,
}

impl InsertableWorkspace {
    pub async fn insert(self, app_db: &mut AppDbClient) -> Result<Workspace, sqlx::Error> {
        query_as!(
            Workspace,
            "
insert into workspaces
(url, owner_id)
values ($1, $2)
returning *
",
            self.url.to_string(),
            self.owner_id
        )
        .fetch_one(&mut *app_db.conn)
        .await
    }
}
