use db::sqlx;
use serde::Deserialize;
use session::Session;

use super::types::{
    WorkspaceMemberResponse, WorkspaceMemberRow, member_response,
};
use crate::{AppService, Pagination, error::AppError, session_user};

#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct AddWorkspaceMember {
    pub username: String,
    pub admin: Option<bool>,
}

#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct UpdateWorkspaceMember {
    pub admin: bool,
}

impl AppService {
    pub async fn workspace_members(
        &self,
        ctx: &Session,
        name: &str,
        pagination: Pagination,
    ) -> Result<Vec<WorkspaceMemberResponse>, AppError> {
        let user_uid = session_user(ctx)?;
        let wk = self.workspace_resolve(name).await?;
        self.workspace_require_member(wk.id, user_uid).await?;

        let rows = sqlx::query_as::<_, WorkspaceMemberRow>(
            "SELECT u.id, u.username, u.display_name, u.avatar_url, u.website_url, u.allow_use, u.can_search, \
                    u.last_sign_in_at, u.created_at, u.updated_at, m.owner, m.admin, m.join_at \
             FROM wk_member m \
             INNER JOIN \"user\" u ON u.id = m.\"user\" \
             WHERE m.wk = $1 AND m.leave_at IS NULL \
             ORDER BY m.owner DESC, m.admin DESC, m.join_at ASC \
             LIMIT $2 OFFSET $3",
        )
        .bind(wk.id)
        .bind(pagination.limit() as i64)
        .bind(pagination.offset() as i64)
        .fetch_all(self.db.reader())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        Ok(rows
            .into_iter()
            .map(WorkspaceMemberResponse::from)
            .collect())
    }

    pub async fn workspace_add_member(
        &self,
        ctx: &Session,
        name: &str,
        params: AddWorkspaceMember,
    ) -> Result<WorkspaceMemberResponse, AppError> {
        let user_uid = session_user(ctx)?;
        let wk = self.workspace_resolve(name).await?;
        self.workspace_require_admin(wk.id, user_uid).await?;
        let target = self
            .users_find_active_user_by_username(&params.username)
            .await?;

        let now = chrono::Utc::now();
        sqlx::query(
            "INSERT INTO wk_member (wk, \"user\", owner, admin, join_at, leave_at) \
             VALUES ($1, $2, false, $3, $4, NULL) \
             ON CONFLICT (wk, \"user\") DO UPDATE SET admin = EXCLUDED.admin, leave_at = NULL",
        )
        .bind(wk.id)
        .bind(target.id)
        .bind(params.admin.unwrap_or(false))
        .bind(now)
        .execute(self.db.writer())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        let member = self.workspace_member(wk.id, target.id).await?;
        Ok(member_response(
            target,
            member.owner,
            member.admin,
            member.join_at,
        ))
    }

    pub async fn workspace_update_member(
        &self,
        ctx: &Session,
        name: &str,
        username: &str,
        params: UpdateWorkspaceMember,
    ) -> Result<WorkspaceMemberResponse, AppError> {
        let user_uid = session_user(ctx)?;
        let wk = self.workspace_resolve(name).await?;
        self.workspace_require_owner(wk.id, user_uid).await?;
        let target = self.users_find_active_user_by_username(username).await?;
        let member = self.workspace_member(wk.id, target.id).await?;
        if member.owner {
            return Err(AppError::BadRequest(
                "cannot update workspace owner role".to_string(),
            ));
        }

        sqlx::query(
            "UPDATE wk_member SET admin = $1 WHERE wk = $2 AND \"user\" = $3 AND leave_at IS NULL",
        )
        .bind(params.admin)
        .bind(wk.id)
        .bind(target.id)
        .execute(self.db.writer())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        let member = self.workspace_member(wk.id, target.id).await?;
        Ok(member_response(
            target,
            member.owner,
            member.admin,
            member.join_at,
        ))
    }

    pub async fn workspace_remove_member(
        &self,
        ctx: &Session,
        name: &str,
        username: &str,
    ) -> Result<(), AppError> {
        let user_uid = session_user(ctx)?;
        let wk = self.workspace_resolve(name).await?;
        let target = self.users_find_active_user_by_username(username).await?;
        let target_member = self.workspace_member(wk.id, target.id).await?;
        if target_member.owner {
            return Err(AppError::BadRequest(
                "cannot remove workspace owner".to_string(),
            ));
        }
        if user_uid != target.id {
            self.workspace_require_admin(wk.id, user_uid).await?;
        }

        sqlx::query("UPDATE wk_member SET leave_at = $1 WHERE wk = $2 AND \"user\" = $3")
            .bind(chrono::Utc::now())
            .bind(wk.id)
            .bind(target.id)
            .execute(self.db.writer())
            .await
            .map_err(|e| AppError::DatabaseError(e.to_string()))?;
        Ok(())
    }
}
