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

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

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

impl AppService {
    pub async fn issue_assign(
        &self,
        ctx: &Session,
        wk_name: &str,
        number: i64,
        params: AssignIssueUser,
    ) -> Result<Vec<IssueAuthor>, 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 issue = self.issue_resolve(wk.id, number).await?;
        let target = self
            .users_find_active_user_by_username(&params.username)
            .await?;
        self.workspace_require_member(wk.id, target.id).await?;

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

        sqlx::query(
            "INSERT INTO issue_event (id, issue, actor, event, from_value, to_value, created_at) \
             VALUES ($1, $2, $3, 'assigned', NULL, $4, $5)",
        )
        .bind(uuid::Uuid::now_v7())
        .bind(issue.id)
        .bind(user_uid)
        .bind(&params.username)
        .bind(now)
        .execute(self.db.writer())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        self.issue_assignees(issue.id).await
    }

    pub async fn issue_unassign(
        &self,
        ctx: &Session,
        wk_name: &str,
        number: i64,
        username: &str,
    ) -> Result<Vec<IssueAuthor>, 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 issue = self.issue_resolve(wk.id, number).await?;
        let target = self.users_find_active_user_by_username(username).await?;

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

        sqlx::query(
            "INSERT INTO issue_event (id, issue, actor, event, from_value, to_value, created_at) \
             VALUES ($1, $2, $3, 'unassigned', $4, NULL, $5)",
        )
        .bind(uuid::Uuid::now_v7())
        .bind(issue.id)
        .bind(user_uid)
        .bind(username)
        .bind(chrono::Utc::now())
        .execute(self.db.writer())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        self.issue_assignees(issue.id).await
    }

    pub async fn issue_assignees(
        &self,
        issue_id: uuid::Uuid,
    ) -> Result<Vec<IssueAuthor>, AppError> {
        let rows = 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 issue_assignee ia \
             INNER JOIN \"user\" u ON u.id = ia.\"user\" \
             WHERE ia.issue = $1 AND u.allow_use = true \
             ORDER BY ia.created_at ASC",
        )
        .bind(issue_id)
        .fetch_all(self.db.reader())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        Ok(rows.into_iter().map(issue_author).collect())
    }
}
