use super::*;

impl PostgresRegistry {
    pub async fn list_workspaces(&self) -> Result<Vec<WorkspaceRecord>, RegistryError> {
        let rows = sqlx::query!(
            "select
                id,
                slug,
                display_name,
                status,
                settings_json,
                created_at as \"created_at!: time::OffsetDateTime\",
                updated_at as \"updated_at!: time::OffsetDateTime\"
             from workspaces
             order by slug asc",
        )
        .fetch_all(&self.pool)
        .await?;

        rows.into_iter()
            .map(|row| {
                Ok(WorkspaceRecord {
                    workspace: Workspace {
                        id: WorkspaceId::new(row.id),
                        slug: row.slug,
                        display_name: row.display_name,
                        status: deserialize_enum_text(&row.status, "status")?,
                        settings: row.settings_json,
                        created_at: row.created_at,
                        updated_at: row.updated_at,
                    },
                })
            })
            .collect()
    }

    pub async fn list_workspaces_for_user(
        &self,
        user_id: &UserId,
    ) -> Result<Vec<WorkspaceMembershipRecord>, RegistryError> {
        let rows = sqlx::query!(
            "select
                w.id,
                w.slug,
                w.display_name,
                w.status,
                w.settings_json,
                w.created_at as \"created_at!: time::OffsetDateTime\",
                w.updated_at as \"updated_at!: time::OffsetDateTime\",
                m.role
             from memberships m
             join workspaces w on w.id = m.workspace_id
             where m.user_id = $1
             order by w.slug asc",
            user_id.as_str(),
        )
        .fetch_all(&self.pool)
        .await?;

        rows.into_iter()
            .map(|row| {
                Ok(WorkspaceMembershipRecord {
                    workspace: Workspace {
                        id: WorkspaceId::new(row.id),
                        slug: row.slug,
                        display_name: row.display_name,
                        status: deserialize_enum_text(&row.status, "status")?,
                        settings: row.settings_json,
                        created_at: row.created_at,
                        updated_at: row.updated_at,
                    },
                    role: deserialize_enum_text(&row.role, "role")?,
                })
            })
            .collect()
    }

    pub async fn list_memberships(
        &self,
        workspace_id: &WorkspaceId,
    ) -> Result<Vec<MembershipRecord>, RegistryError> {
        let rows = sqlx::query!(
            "select
                m.workspace_id,
                m.user_id,
                m.role,
                m.created_at as \"created_at!: time::OffsetDateTime\",
                u.email,
                u.display_name,
                u.status,
                u.created_at as \"user_created_at!: time::OffsetDateTime\"
             from memberships m
             join users u on u.id = m.user_id
             where m.workspace_id = $1
             order by u.email asc",
            workspace_id.as_str(),
        )
        .fetch_all(&self.pool)
        .await?;

        rows.into_iter()
            .map(|row| {
                Ok(MembershipRecord {
                    workspace_id: WorkspaceId::new(row.workspace_id),
                    user: User {
                        id: UserId::new(row.user_id),
                        email: row.email,
                        display_name: row.display_name,
                        status: deserialize_enum_text(&row.status, "status")?,
                        created_at: row.user_created_at,
                    },
                    role: deserialize_enum_text(&row.role, "role")?,
                    created_at: row.created_at,
                })
            })
            .collect()
    }

    pub async fn update_membership_role(
        &self,
        workspace_id: &WorkspaceId,
        user_id: &UserId,
        role: MembershipRole,
    ) -> Result<(), RegistryError> {
        let result = sqlx::query(
            "update memberships
             set role = $3
             where workspace_id = $1 and user_id = $2",
        )
        .bind(workspace_id.as_str())
        .bind(user_id.as_str())
        .bind(serialize_enum_text(&role, "role")?)
        .execute(&self.pool)
        .await?;

        if result.rows_affected() == 0 {
            return Err(RegistryError::MembershipNotFound {
                workspace_id: workspace_id.as_str().to_owned(),
                user_id: user_id.as_str().to_owned(),
            });
        }

        Ok(())
    }

    pub async fn delete_membership(
        &self,
        workspace_id: &WorkspaceId,
        user_id: &UserId,
    ) -> Result<(), RegistryError> {
        let result = sqlx::query(
            "delete from memberships
             where workspace_id = $1 and user_id = $2",
        )
        .bind(workspace_id.as_str())
        .bind(user_id.as_str())
        .execute(&self.pool)
        .await?;

        if result.rows_affected() == 0 {
            return Err(RegistryError::MembershipNotFound {
                workspace_id: workspace_id.as_str().to_owned(),
                user_id: user_id.as_str().to_owned(),
            });
        }

        Ok(())
    }

    pub async fn list_invitations(
        &self,
        workspace_id: &WorkspaceId,
    ) -> Result<Vec<InvitationRecord>, RegistryError> {
        let rows = sqlx::query!(
            "select
                id,
                workspace_id,
                email,
                role,
                status,
                token_hash,
                expires_at as \"expires_at!: time::OffsetDateTime\",
                created_at as \"created_at!: time::OffsetDateTime\"
             from invitation_tokens
             where workspace_id = $1
             order by created_at desc",
            workspace_id.as_str(),
        )
        .fetch_all(&self.pool)
        .await?;

        rows.into_iter()
            .map(|row| {
                Ok(InvitationRecord {
                    invitation: InvitationToken {
                        id: InvitationId::new(row.id),
                        workspace_id: WorkspaceId::new(row.workspace_id),
                        email: row.email,
                        role: deserialize_enum_text(&row.role, "role")?,
                        status: deserialize_enum_text(&row.status, "status")?,
                        token_hash: row.token_hash,
                        expires_at: row.expires_at,
                        created_at: row.created_at,
                    },
                })
            })
            .collect()
    }

    pub async fn create_invitation(
        &self,
        request: CreateInvitationRequest<'_>,
    ) -> Result<(), RegistryError> {
        sqlx::query(
            "insert into invitation_tokens (
                id,
                workspace_id,
                email,
                role,
                status,
                token_hash,
                expires_at,
                created_at
             ) values (
                $1, $2, $3, $4, $5, $6, $7::timestamptz, $8::timestamptz
             )",
        )
        .bind(request.invitation.id.as_str())
        .bind(request.invitation.workspace_id.as_str())
        .bind(&request.invitation.email)
        .bind(serialize_enum_text(&request.invitation.role, "role")?)
        .bind(serialize_enum_text(&request.invitation.status, "status")?)
        .bind(&request.invitation.token_hash)
        .bind(request.invitation.expires_at)
        .bind(request.invitation.created_at)
        .execute(&self.pool)
        .await?;

        Ok(())
    }

    pub async fn delete_invitation(
        &self,
        workspace_id: &WorkspaceId,
        invitation_id: &InvitationId,
    ) -> Result<(), RegistryError> {
        let result =
            sqlx::query("delete from invitation_tokens where workspace_id = $1 and id = $2")
                .bind(workspace_id.as_str())
                .bind(invitation_id.as_str())
                .execute(&self.pool)
                .await?;

        if result.rows_affected() == 0 {
            return Err(RegistryError::InvitationNotFound {
                invitation_id: invitation_id.as_str().to_owned(),
            });
        }

        Ok(())
    }

    pub async fn delete_workspace(&self, workspace_id: &WorkspaceId) -> Result<(), RegistryError> {
        let result = sqlx::query("delete from workspaces where id = $1")
            .bind(workspace_id.as_str())
            .execute(&self.pool)
            .await?;

        if result.rows_affected() == 0 {
            return Err(RegistryError::WorkspaceNotFound {
                workspace_id: workspace_id.as_str().to_owned(),
            });
        }

        Ok(())
    }

    pub async fn get_workspace(
        &self,
        workspace_id: &WorkspaceId,
    ) -> Result<Option<WorkspaceRecord>, RegistryError> {
        let row = sqlx::query!(
            "select
                id,
                slug,
                display_name,
                status,
                settings_json,
                created_at as \"created_at!: time::OffsetDateTime\",
                updated_at as \"updated_at!: time::OffsetDateTime\"
             from workspaces
             where id = $1",
            workspace_id.as_str(),
        )
        .fetch_optional(&self.pool)
        .await?;

        row.map(|row| {
            Ok(WorkspaceRecord {
                workspace: Workspace {
                    id: WorkspaceId::new(row.id),
                    slug: row.slug,
                    display_name: row.display_name,
                    status: deserialize_enum_text(&row.status, "status")?,
                    settings: row.settings_json,
                    created_at: row.created_at,
                    updated_at: row.updated_at,
                },
            })
        })
        .transpose()
    }

    pub async fn create_workspace(
        &self,
        request: CreateWorkspaceRequest<'_>,
    ) -> Result<(), RegistryError> {
        let result = sqlx::query(
            "insert into workspaces (
                id,
                slug,
                display_name,
                status,
                settings_json,
                created_at,
                updated_at
             ) values (
                $1, $2, $3, $4, $5, $6::timestamptz, $7::timestamptz
             )",
        )
        .bind(request.workspace.id.as_str())
        .bind(&request.workspace.slug)
        .bind(&request.workspace.display_name)
        .bind(serialize_enum_text(&request.workspace.status, "status")?)
        .bind(Json(request.workspace.settings.clone()))
        .bind(request.workspace.created_at)
        .bind(request.workspace.updated_at)
        .execute(&self.pool)
        .await;

        match result {
            Ok(_) => Ok(()),
            Err(sqlx::Error::Database(error))
                if error.constraint() == Some("workspaces_slug_key") =>
            {
                Err(RegistryError::WorkspaceSlugAlreadyExists {
                    slug: request.workspace.slug.clone(),
                })
            }
            Err(error) => Err(RegistryError::Storage(error)),
        }
    }

    pub async fn update_workspace(
        &self,
        request: UpdateWorkspaceRequest<'_>,
    ) -> Result<(), RegistryError> {
        let result = sqlx::query(
            "update workspaces
                set slug = $1,
                    display_name = $2,
                    status = $3,
                    settings_json = $4,
                    updated_at = $5::timestamptz
              where id = $6",
        )
        .bind(&request.workspace.slug)
        .bind(&request.workspace.display_name)
        .bind(serialize_enum_text(&request.workspace.status, "status")?)
        .bind(Json(request.workspace.settings.clone()))
        .bind(request.workspace.updated_at)
        .bind(request.workspace.id.as_str())
        .execute(&self.pool)
        .await;

        match result {
            Ok(result) if result.rows_affected() == 0 => Err(RegistryError::WorkspaceNotFound {
                workspace_id: request.workspace.id.as_str().to_owned(),
            }),
            Ok(_) => Ok(()),
            Err(sqlx::Error::Database(error))
                if error.constraint() == Some("workspaces_slug_key") =>
            {
                Err(RegistryError::WorkspaceSlugAlreadyExists {
                    slug: request.workspace.slug.clone(),
                })
            }
            Err(error) => Err(RegistryError::Storage(error)),
        }
    }
}
