mod common;

use axum::http::StatusCode;
use serde_json::json;
use sqlx::PgPool;
use tower::ServiceExt;
use uuid::Uuid;

async fn put_reaction(
    app: &axum::Router,
    cookie: &str,
    manga_id: Uuid,
    reaction: &str,
) -> StatusCode {
    let resp = app
        .clone()
        .oneshot(common::put_json_with_cookie(
            &format!("/api/v1/mangas/{manga_id}/reaction"),
            json!({ "reaction": reaction }),
            cookie,
        ))
        .await
        .unwrap();
    resp.status()
}

async fn get_reaction(app: &axum::Router, cookie: &str, manga_id: Uuid) -> serde_json::Value {
    let resp = app
        .clone()
        .oneshot(common::get_with_cookie(
            &format!("/api/v1/me/reactions/{manga_id}"),
            cookie,
        ))
        .await
        .unwrap();
    assert_eq!(resp.status(), StatusCode::OK);
    common::body_json(resp).await
}

#[sqlx::test(migrations = "./migrations")]
async fn put_creates_and_reads_back(pool: PgPool) {
    let h = common::harness(pool);
    let (_, cookie) = common::register_user(&h.app).await;
    let manga_id = common::seed_manga_via_api(&h.app, &cookie, "Berserk").await;

    assert_eq!(put_reaction(&h.app, &cookie, manga_id, "like").await, StatusCode::OK);
    let body = get_reaction(&h.app, &cookie, manga_id).await;
    assert_eq!(body["reaction"], "like");
    assert_eq!(body["manga_id"], manga_id.to_string());
}

#[sqlx::test(migrations = "./migrations")]
async fn put_toggles_like_to_dislike(pool: PgPool) {
    let h = common::harness(pool);
    let (_, cookie) = common::register_user(&h.app).await;
    let manga_id = common::seed_manga_via_api(&h.app, &cookie, "Berserk").await;

    let _ = put_reaction(&h.app, &cookie, manga_id, "like").await;
    assert_eq!(put_reaction(&h.app, &cookie, manga_id, "dislike").await, StatusCode::OK);
    assert_eq!(get_reaction(&h.app, &cookie, manga_id).await["reaction"], "dislike");
}

#[sqlx::test(migrations = "./migrations")]
async fn delete_clears_the_reaction(pool: PgPool) {
    let h = common::harness(pool);
    let (_, cookie) = common::register_user(&h.app).await;
    let manga_id = common::seed_manga_via_api(&h.app, &cookie, "Berserk").await;
    let _ = put_reaction(&h.app, &cookie, manga_id, "like").await;

    let resp = h
        .app
        .clone()
        .oneshot(common::delete_with_cookie(
            &format!("/api/v1/mangas/{manga_id}/reaction"),
            &cookie,
        ))
        .await
        .unwrap();
    assert_eq!(resp.status(), StatusCode::NO_CONTENT);
    assert_eq!(get_reaction(&h.app, &cookie, manga_id).await["reaction"], serde_json::Value::Null);
}

#[sqlx::test(migrations = "./migrations")]
async fn get_unset_returns_null(pool: PgPool) {
    let h = common::harness(pool);
    let (_, cookie) = common::register_user(&h.app).await;
    let manga_id = common::seed_manga_via_api(&h.app, &cookie, "Berserk").await;
    assert_eq!(get_reaction(&h.app, &cookie, manga_id).await["reaction"], serde_json::Value::Null);
}

#[sqlx::test(migrations = "./migrations")]
async fn reactions_are_per_user(pool: PgPool) {
    let h = common::harness(pool);
    let (_, a) = common::register_user(&h.app).await;
    let (_, b) = common::register_user(&h.app).await;
    let manga_id = common::seed_manga_via_api(&h.app, &a, "Berserk").await;
    let _ = put_reaction(&h.app, &a, manga_id, "like").await;
    // B sees no reaction of their own.
    assert_eq!(get_reaction(&h.app, &b, manga_id).await["reaction"], serde_json::Value::Null);
}

#[sqlx::test(migrations = "./migrations")]
async fn put_unknown_manga_is_404(pool: PgPool) {
    let h = common::harness(pool);
    let (_, cookie) = common::register_user(&h.app).await;
    assert_eq!(
        put_reaction(&h.app, &cookie, Uuid::new_v4(), "like").await,
        StatusCode::NOT_FOUND
    );
}

#[sqlx::test(migrations = "./migrations")]
async fn put_invalid_reaction_is_422(pool: PgPool) {
    let h = common::harness(pool);
    let (_, cookie) = common::register_user(&h.app).await;
    let manga_id = common::seed_manga_via_api(&h.app, &cookie, "Berserk").await;
    assert_eq!(
        put_reaction(&h.app, &cookie, manga_id, "meh").await,
        StatusCode::UNPROCESSABLE_ENTITY
    );
}

#[sqlx::test(migrations = "./migrations")]
async fn get_requires_authentication(pool: PgPool) {
    let h = common::harness(pool);
    let resp = h
        .app
        .clone()
        .oneshot(common::get(&format!("/api/v1/me/reactions/{}", Uuid::new_v4())))
        .await
        .unwrap();
    assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
