//! Per-user manga reaction (like/dislike) persistence.

use sqlx::PgPool;
use uuid::Uuid;

use crate::domain::reaction::Reaction;
use crate::error::{AppError, AppResult};

/// Insert-or-overwrite the user's reaction on this manga (like ↔ dislike).
/// A foreign-key violation (unknown manga) maps to `NotFound` so the API
/// returns 404 rather than 500 — mirrors `read_progress::upsert`.
pub async fn upsert(
    pool: &PgPool,
    user_id: Uuid,
    manga_id: Uuid,
    reaction: Reaction,
) -> AppResult<()> {
    sqlx::query(
        r#"
        INSERT INTO manga_reactions (user_id, manga_id, reaction, created_at)
        VALUES ($1, $2, $3, now())
        ON CONFLICT (user_id, manga_id) DO UPDATE
          SET reaction   = EXCLUDED.reaction,
              created_at = now()
        "#,
    )
    .bind(user_id)
    .bind(manga_id)
    .bind(reaction.as_str())
    .execute(pool)
    .await
    .map_err(|e| match e {
        sqlx::Error::Database(ref db_err) if db_err.is_foreign_key_violation() => {
            AppError::NotFound
        }
        other => AppError::Database(other),
    })?;
    Ok(())
}

/// Remove the user's reaction on this manga. Idempotent — clearing a
/// non-existent reaction is a no-op.
pub async fn clear(pool: &PgPool, user_id: Uuid, manga_id: Uuid) -> AppResult<()> {
    sqlx::query("DELETE FROM manga_reactions WHERE user_id = $1 AND manga_id = $2")
        .bind(user_id)
        .bind(manga_id)
        .execute(pool)
        .await?;
    Ok(())
}

/// The user's current reaction on this manga, or `None` if unset.
pub async fn get(
    pool: &PgPool,
    user_id: Uuid,
    manga_id: Uuid,
) -> AppResult<Option<Reaction>> {
    let row: Option<(String,)> = sqlx::query_as(
        "SELECT reaction FROM manga_reactions WHERE user_id = $1 AND manga_id = $2",
    )
    .bind(user_id)
    .bind(manga_id)
    .fetch_optional(pool)
    .await?;
    Ok(row.and_then(|(s,)| Reaction::parse(&s)))
}
