use db::sqlx;
use model::issues::LabelModel;
use serde::Deserialize;
use session::Session;
use uuid::Uuid;

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

#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct AddPrLabel {
    pub label_id: Uuid,
}

impl AppService {
    pub async fn pr_add_label(
        &self,
        ctx: &Session,
        wk_name: &str,
        repo_name: &str,
        number: i64,
        params: AddPrLabel,
    ) -> Result<Vec<LabelResponse>, AppError> {
        let user_uid = session_user(ctx)?;
        let wk = self.workspace_resolve(wk_name).await?;
        self.workspace_require_member(wk.id, user_uid).await?;
        let repo = self.repo_resolve(wk.id, repo_name).await?;
        let pr = self.pr_resolve(repo.id, number).await?;

        let _label = sqlx::query_as::<_, LabelModel>(
            "SELECT id, wk, name, color, description, created_at, updated_at, deleted_at \
             FROM label WHERE id = $1 AND wk = $2 AND deleted_at IS NULL",
        )
        .bind(params.label_id)
        .bind(wk.id)
        .fetch_optional(self.db.reader())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?
        .ok_or(AppError::LabelNotFound)?;

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

        self.pr_labels(pr.id).await
    }

    pub async fn pr_remove_label(
        &self,
        ctx: &Session,
        wk_name: &str,
        repo_name: &str,
        number: i64,
        label_id: Uuid,
    ) -> Result<Vec<LabelResponse>, AppError> {
        let user_uid = session_user(ctx)?;
        let wk = self.workspace_resolve(wk_name).await?;
        self.workspace_require_member(wk.id, user_uid).await?;
        let repo = self.repo_resolve(wk.id, repo_name).await?;
        let pr = self.pr_resolve(repo.id, number).await?;

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

        self.pr_labels(pr.id).await
    }

    pub async fn pr_labels(
        &self,
        pr_id: uuid::Uuid,
    ) -> Result<Vec<LabelResponse>, AppError> {
        let labels = sqlx::query_as::<_, LabelModel>(
            "SELECT l.id, l.wk, l.name, l.color, l.description, l.created_at, l.updated_at, l.deleted_at \
             FROM pull_request_label pl INNER JOIN label l ON l.id = pl.label \
             WHERE pl.pull_request = $1 AND l.deleted_at IS NULL \
             ORDER BY l.name ASC",
        )
        .bind(pr_id)
        .fetch_all(self.db.reader())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        Ok(labels
            .into_iter()
            .map(crate::issues::types::label_response)
            .collect())
    }
}
