use db::sqlx;
use model::pull_request::PullRequestReactionModel;
use serde::Deserialize;
use session::Session;
use uuid::Uuid;

use crate::{
    AppService, error::AppError,
    pull_request::types::PullRequestReactionResponse, session_user,
};

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

impl AppService {
    pub async fn pr_add_reaction(
        &self,
        ctx: &Session,
        wk_name: &str,
        repo_name: &str,
        number: i64,
        params: AddPrReaction,
    ) -> Result<PullRequestReactionResponse, 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 now = chrono::Utc::now();
        let reaction = sqlx::query_as::<_, PullRequestReactionModel>(
            "INSERT INTO pull_request_reaction (id, pull_request, comment, \"user\", reaction, created_at) \
             VALUES ($1, $2, NULL, $3, $4, $5) \
             RETURNING id, pull_request, comment, \"user\", reaction, created_at",
        )
        .bind(uuid::Uuid::now_v7())
        .bind(pr.id)
        .bind(user_uid)
        .bind(&params.reaction)
        .bind(now)
        .fetch_one(self.db.writer())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        let user = self.users_find_by_id(reaction.user).await?;
        Ok(PullRequestReactionResponse {
            id: reaction.id,
            user: crate::issues::types::issue_author(user),
            reaction: reaction.reaction,
            created_at: reaction.created_at,
        })
    }

    pub async fn pr_remove_reaction(
        &self,
        ctx: &Session,
        wk_name: &str,
        repo_name: &str,
        _number: i64,
        reaction_id: Uuid,
    ) -> Result<(), AppError> {
        let user_uid = session_user(ctx)?;
        let (_repo_id, _) =
            self.pr_resolve_repo(ctx, wk_name, repo_name).await?;

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

        Ok(())
    }

    pub async fn pr_comment_add_reaction(
        &self,
        ctx: &Session,
        wk_name: &str,
        repo_name: &str,
        _number: i64,
        comment_id: Uuid,
        params: AddPrReaction,
    ) -> Result<PullRequestReactionResponse, AppError> {
        let user_uid = session_user(ctx)?;
        let (repo_id, _) =
            self.pr_resolve_repo(ctx, wk_name, repo_name).await?;

        let now = chrono::Utc::now();
        let reaction = sqlx::query_as::<_, PullRequestReactionModel>(
            "INSERT INTO pull_request_reaction (id, pull_request, comment, \"user\", reaction, created_at) \
             VALUES ($1, $2, $3, $4, $5, $6) \
             RETURNING id, pull_request, comment, \"user\", reaction, created_at",
        )
        .bind(uuid::Uuid::now_v7())
        .bind(repo_id)
        .bind(comment_id)
        .bind(user_uid)
        .bind(&params.reaction)
        .bind(now)
        .fetch_one(self.db.writer())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        let user = self.users_find_by_id(reaction.user).await?;
        Ok(PullRequestReactionResponse {
            id: reaction.id,
            user: crate::issues::types::issue_author(user),
            reaction: reaction.reaction,
            created_at: reaction.created_at,
        })
    }

    pub async fn pr_comment_remove_reaction(
        &self,
        ctx: &Session,
        wk_name: &str,
        repo_name: &str,
        _number: i64,
        reaction_id: Uuid,
    ) -> Result<(), AppError> {
        let user_uid = session_user(ctx)?;
        let (_repo_id, _) =
            self.pr_resolve_repo(ctx, wk_name, repo_name).await?;

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

        Ok(())
    }
}
