//! GET /admin/crawler/history — unified, searchable, filterable job log.
//!
//! A pure DB-derived read over the `crawler_jobs` table across every state
//! and kind. Drives the "History" tab on the crawler dashboard. Depth is
//! bounded by the done-job reaper (recent window); `dead` jobs persist.

use axum::extract::{Query, State};
use axum::routing::get;
use axum::{Json, Router};
use serde::Deserialize;

use crate::app::AppState;
use crate::auth::extractor::RequireAdmin;
use crate::error::AppResult;
use crate::repo;
use crate::repo::crawler::{JobHistoryFilter, JobHistoryRow};

use super::default_limit;

pub(super) fn routes() -> Router<AppState> {
    Router::new().route("/admin/crawler/history", get(list_history))
}

/// State + kind filters and pagination/search for the history list.
#[derive(Debug, Deserialize, Default)]
struct HistoryParams {
    #[serde(default)]
    state: Option<String>,
    #[serde(default)]
    kind: Option<String>,
    #[serde(default)]
    search: Option<String>,
    #[serde(default = "default_limit")]
    limit: i64,
    #[serde(default)]
    offset: i64,
}

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