//! GET /admin/crawler/dead-jobs — paginated dead-letter list
//! POST /admin/crawler/dead-jobs/requeue — flip dead jobs back to pending
//!
//! Requeue scopes:
//! - `all` (requires `confirm: true` so a careless click / CSRF bait
//!   can't flip the whole pile in one shot)
//! - `manga` (all dead jobs whose chapter belongs to a manga)
//! - `chapter` (all dead jobs for a single chapter)
//! - `job` (a single dead row by id)
//!
//! Each requeue emits an `admin_audit` row with the relevant
//! `target_id` populated, so an operator review post-incident can pin
//! exactly which scope was acted on.

use axum::extract::{Query, State};
use axum::routing::{get, post};
use axum::{Json, Router};
use serde::{Deserialize, Serialize};
use serde_json::json;
use uuid::Uuid;

use crate::app::AppState;
use crate::auth::extractor::RequireAdmin;
use crate::error::{AppError, AppResult};
use crate::repo;
use crate::repo::crawler::{DeadJob, RequeueScope};

use super::default_limit;

pub(super) fn routes() -> Router<AppState> {
    Router::new()
        .route("/admin/crawler/dead-jobs", get(list_dead_jobs))
        .route("/admin/crawler/dead-jobs/requeue", post(requeue_dead_jobs))
}

#[derive(Debug, Deserialize, Default)]
struct DeadJobsParams {
    #[serde(default)]
    search: Option<String>,
    #[serde(default = "default_limit")]
    limit: i64,
    #[serde(default)]
    offset: i64,
}

async fn list_dead_jobs(
    State(state): State<AppState>,
    _admin: RequireAdmin,
    Query(params): Query<DeadJobsParams>,
) -> AppResult<Json<crate::api::pagination::PagedResponse<DeadJob>>> {
    let limit = params.limit.clamp(1, 200);
    let offset = params.offset.max(0);
    let search = params.search.filter(|s| !s.trim().is_empty());
    let (items, total) =
        repo::crawler::list_dead_jobs(&state.db, search.as_deref(), limit, offset).await?;
    Ok(Json(crate::api::pagination::PagedResponse::with_total(
        items, limit, offset, total,
    )))
}

#[derive(Debug, Deserialize)]
#[serde(tag = "scope", rename_all = "snake_case")]
enum RequeueRequest {
    /// `confirm: true` is required so a careless click / CSRF bait can't
    /// flip the entire dead pile in one shot. Narrow scopes don't need it.
    All {
        #[serde(default)]
        confirm: bool,
    },
    Manga { manga_id: Uuid },
    Chapter { chapter_id: Uuid },
    Job { job_id: Uuid },
}

#[derive(Debug, Serialize)]
struct RequeueResponse {
    requeued: u64,
}

async fn requeue_dead_jobs(
    State(state): State<AppState>,
    admin: RequireAdmin,
    Json(body): Json<RequeueRequest>,
) -> AppResult<Json<RequeueResponse>> {
    // Reject scope=all without an explicit confirm so a single click or
    // CSRF bait can't flip the whole dead pile. Narrow scopes don't need
    // the confirmation — the operator already named a specific target.
    if let RequeueRequest::All { confirm: false } = &body {
        return Err(AppError::InvalidInput(
            "confirm: true is required for scope=all".into(),
        ));
    }
    let (scope, target_kind, target_id) = match &body {
        RequeueRequest::All { .. } => (RequeueScope::All, "crawler", None),
        RequeueRequest::Manga { manga_id } => (RequeueScope::Manga(*manga_id), "manga", Some(*manga_id)),
        RequeueRequest::Chapter { chapter_id } => {
            (RequeueScope::Chapter(*chapter_id), "chapter", Some(*chapter_id))
        }
        RequeueRequest::Job { job_id } => (RequeueScope::Job(*job_id), "crawler_job", Some(*job_id)),
    };
    let requeued = repo::crawler::requeue_dead_jobs(&state.db, scope).await?;
    repo::admin_audit::insert(
        &state.db,
        admin.0.id,
        "crawler_dead_jobs_requeue",
        target_kind,
        target_id,
        json!({ "requeued": requeued, "scope": scope_label(&body) }),
    )
    .await?;
    Ok(Json(RequeueResponse { requeued }))
}

fn scope_label(r: &RequeueRequest) -> &'static str {
    match r {
        RequeueRequest::All { .. } => "all",
        RequeueRequest::Manga { .. } => "manga",
        RequeueRequest::Chapter { .. } => "chapter",
        RequeueRequest::Job { .. } => "job",
    }
}
