use db::sqlx;
use session::Session;

use crate::{AppService, error::AppError, session_user};

impl AppService {
    pub async fn git_repo_watch(
        &self,
        ctx: &Session,
        wk_name: &str,
        repo_name: &str,
        level: Option<String>,
    ) -> Result<serde_json::Value, AppError> {
        let user_uid = session_user(ctx)?;
        let repo = self.git_require_member(ctx, wk_name, repo_name).await?;
        let level = level.unwrap_or_else(|| "participating".to_string());

        sqlx::query(
            "INSERT INTO repo_watch (repo, \"user\", \"level\", created_at, updated_at) \
             VALUES ($1, $2, $3, $4, $5) \
             ON CONFLICT (repo, \"user\") DO UPDATE SET level = $3, updated_at = $5",
        )
        .bind(repo.id)
        .bind(user_uid)
        .bind(&level)
        .bind(chrono::Utc::now())
        .bind(chrono::Utc::now())
        .execute(self.db.writer())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        let count: (i64,) =
            sqlx::query_as("SELECT COUNT(*) FROM repo_watch WHERE repo = $1")
                .bind(repo.id)
                .fetch_one(self.db.reader())
                .await
                .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        Ok(
            serde_json::json!({ "watching": true, "count": count.0, "level": level }),
        )
    }

    pub async fn git_repo_unwatch(
        &self,
        ctx: &Session,
        wk_name: &str,
        repo_name: &str,
    ) -> Result<serde_json::Value, AppError> {
        let user_uid = session_user(ctx)?;
        let repo = self.git_require_member(ctx, wk_name, repo_name).await?;

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

        let count: (i64,) =
            sqlx::query_as("SELECT COUNT(*) FROM repo_watch WHERE repo = $1")
                .bind(repo.id)
                .fetch_one(self.db.reader())
                .await
                .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        Ok(serde_json::json!({ "watching": false, "count": count.0 }))
    }

    pub async fn git_repo_watch_status(
        &self,
        ctx: &Session,
        wk_name: &str,
        repo_name: &str,
    ) -> Result<serde_json::Value, AppError> {
        let user_uid = session_user(ctx)?;
        let repo = self.git_require_member(ctx, wk_name, repo_name).await?;

        let watch: Option<(bool, String)> = sqlx::query_as(
            "SELECT EXISTS(SELECT 1 FROM repo_watch WHERE repo = $1 AND \"user\" = $2), \
             COALESCE((SELECT level FROM repo_watch WHERE repo = $1 AND \"user\" = $2), '')",
        )
        .bind(repo.id)
        .bind(user_uid)
        .fetch_optional(self.db.reader())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        let count: (i64,) =
            sqlx::query_as("SELECT COUNT(*) FROM repo_watch WHERE repo = $1")
                .bind(repo.id)
                .fetch_one(self.db.reader())
                .await
                .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        Ok(serde_json::json!({
            "watching": watch.as_ref().map(|(w, _)| *w).unwrap_or(false),
            "count": count.0,
            "level": watch.map(|(_, l)| l).unwrap_or_default(),
        }))
    }
}
