use db::sqlx;
use model::users::UserModel;
use serde::Deserialize;
use session::Session;

use crate::{
    AppService, error::AppError, issues::types::IssueAuthor, session_user,
};

#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct AssignPrUser {
    pub username: String,
}

impl AppService {
    pub async fn pr_assign(
        &self,
        ctx: &Session,
        wk_name: &str,
        repo_name: &str,
        number: i64,
        params: AssignPrUser,
    ) -> Result<Vec<IssueAuthor>, AppError> {
        let user_uid = session_user(ctx)?;
        let (repo_id, _) =
            self.pr_resolve_repo(ctx, wk_name, repo_name).await?;
        let pr = self.pr_resolve(repo_id, number).await?;

        let assignee = sqlx::query_as::<_, UserModel>(
            "SELECT id, username, display_name, avatar_url, website_url, allow_use, can_search, \
             last_sign_in_at, created_at, updated_at \
             FROM \"user\" WHERE username = $1 AND allow_use = true",
        )
        .bind(&params.username)
        .fetch_optional(self.db.reader())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?
        .ok_or(AppError::UserNotFound)?;

        sqlx::query(
            "INSERT INTO pull_request_assignee (pull_request, \"user\", assigned_by, created_at) \
             VALUES ($1, $2, $3, $4) ON CONFLICT (pull_request, \"user\") DO NOTHING",
        )
        .bind(pr.id)
        .bind(assignee.id)
        .bind(user_uid)
        .bind(chrono::Utc::now())
        .execute(self.db.writer())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        self.pr_assignees_list(pr.id).await
    }

    pub async fn pr_unassign(
        &self,
        ctx: &Session,
        wk_name: &str,
        repo_name: &str,
        number: i64,
        username: &str,
    ) -> Result<Vec<IssueAuthor>, AppError> {
        let _user_uid = session_user(ctx)?;
        let (repo_id, _) =
            self.pr_resolve_repo(ctx, wk_name, repo_name).await?;
        let pr = self.pr_resolve(repo_id, number).await?;

        let assignee = sqlx::query_as::<_, UserModel>(
            "SELECT id, username, display_name, avatar_url, website_url, allow_use, can_search, \
             last_sign_in_at, created_at, updated_at \
             FROM \"user\" WHERE username = $1",
        )
        .bind(username)
        .fetch_optional(self.db.reader())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?
        .ok_or(AppError::UserNotFound)?;

        sqlx::query("DELETE FROM pull_request_assignee WHERE pull_request = $1 AND \"user\" = $2")
            .bind(pr.id)
            .bind(assignee.id)
            .execute(self.db.writer())
            .await
            .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        self.pr_assignees_list(pr.id).await
    }

    pub async fn pr_assignees_list(
        &self,
        pr_id: uuid::Uuid,
    ) -> Result<Vec<IssueAuthor>, AppError> {
        let assignees = sqlx::query_as::<_, UserModel>(
            "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 \
             FROM pull_request_assignee pa INNER JOIN \"user\" u ON u.id = pa.\"user\" \
             WHERE pa.pull_request = $1 \
             ORDER BY u.username ASC",
        )
        .bind(pr_id)
        .fetch_all(self.db.reader())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        Ok(assignees
            .into_iter()
            .map(|u| crate::issues::types::issue_author(u))
            .collect())
    }
}
