//! Reading-progress and upload-history endpoints (Phase 5).
//!
//! All routes live under `/me/...` and require `CurrentUser`. They
//! never expose another user's data — the user id is taken from the
//! auth extractor, not from the path or body.

use axum::extract::{Path, Query, State};
use axum::http::StatusCode;
use axum::routing::{get, put};
use axum::{Json, Router};
use serde::Deserialize;
use serde_json::json;
use uuid::Uuid;

use crate::api::pagination::PagedResponse;
use crate::app::AppState;
use crate::auth::extractor::CurrentUser;
use crate::domain::read_progress::{
    ReadProgress, ReadProgressForManga, ReadProgressSummary, UpsertReadProgress,
};
use crate::domain::upload_entry::UploadEntry;
use crate::error::{AppError, AppResult};
use crate::repo;

pub fn routes() -> Router<AppState> {
    Router::new()
        .route("/me/read-progress", put(upsert).get(list))
        .route(
            "/me/read-progress/:manga_id",
            get(get_one).delete(delete_one),
        )
        .route("/me/uploads", get(uploads))
}

#[derive(Debug, Deserialize)]
pub struct ListParams {
    #[serde(default = "default_limit")]
    pub limit: i64,
    #[serde(default)]
    pub offset: i64,
}

fn default_limit() -> i64 {
    50
}

async fn upsert(
    State(state): State<AppState>,
    CurrentUser(user): CurrentUser,
    Json(input): Json<UpsertReadProgress>,
) -> AppResult<Json<ReadProgress>> {
    let page = input.page.unwrap_or(1);
    if page < 1 {
        return Err(AppError::ValidationFailed {
            message: "page must be 1 or greater".into(),
            details: json!({ "page": "must be >= 1" }),
        });
    }
    // Cross-link guard: the FKs on read_progress accept any valid
    // (manga_id, chapter_id), even when they refer to unrelated mangas.
    // Reject mismatched pairs so history can't end up rendering a
    // chapter number from the wrong manga.
    if let Some(chapter_id) = input.chapter_id {
        let belongs = repo::read_progress::chapter_belongs_to_manga(
            &state.db,
            input.manga_id,
            chapter_id,
        )
        .await?;
        if !belongs {
            return Err(AppError::ValidationFailed {
                message: "chapter does not belong to this manga".into(),
                details: json!({ "chapter_id": "must reference a chapter of the supplied manga" }),
            });
        }
    }
    let row = repo::read_progress::upsert(
        &state.db,
        user.id,
        input.manga_id,
        input.chapter_id,
        page,
    )
    .await?;
    Ok(Json(row))
}

async fn list(
    State(state): State<AppState>,
    CurrentUser(user): CurrentUser,
    Query(params): Query<ListParams>,
) -> AppResult<Json<PagedResponse<ReadProgressSummary>>> {
    let limit = params.limit.clamp(1, 200);
    let offset = params.offset.max(0);
    let (items, total) =
        repo::read_progress::list_for_user(&state.db, user.id, limit, offset).await?;
    Ok(Json(PagedResponse::with_total(items, limit, offset, total)))
}

async fn get_one(
    State(state): State<AppState>,
    CurrentUser(user): CurrentUser,
    Path(manga_id): Path<Uuid>,
) -> AppResult<Json<ReadProgressForManga>> {
    // Enriched with `chapter_number` so the manga page's Continue
    // CTA doesn't need to resolve the chapter id against the paged
    // chapters list.
    Ok(Json(
        repo::read_progress::get_for_manga(&state.db, user.id, manga_id).await?,
    ))
}

async fn delete_one(
    State(state): State<AppState>,
    CurrentUser(user): CurrentUser,
    Path(manga_id): Path<Uuid>,
) -> AppResult<StatusCode> {
    repo::read_progress::delete(&state.db, user.id, manga_id).await?;
    Ok(StatusCode::NO_CONTENT)
}

#[derive(Debug, Deserialize)]
pub struct UploadListParams {
    #[serde(default = "default_uploads_limit")]
    pub limit: i64,
}

fn default_uploads_limit() -> i64 {
    50
}

async fn uploads(
    State(state): State<AppState>,
    CurrentUser(user): CurrentUser,
    Query(params): Query<UploadListParams>,
) -> AppResult<Json<PagedResponse<UploadEntry>>> {
    // Limit-only pagination for now — keyset across two unrelated
    // tables is a future enhancement. Total comes from a fast count
    // query so the UI can show "N total" without dragging the rows
    // across the wire.
    let limit = params.limit.clamp(1, 200);
    let (items, total) =
        repo::upload_history::list_for_user(&state.db, user.id, limit).await?;
    Ok(Json(PagedResponse::with_total(items, limit, 0, total)))
}
