//! Admin endpoint for listing WAL records permanently rejected by the IngestWorker.
//!
//! These records passed API-level validation but were skipped during WAL replay
//! due to permanent failures (invalid signatures, corrupt serialization, etc.).
//! With the API-side signature verification fix, new rejected records should be rare.

use axum::{extract::State, Json};
use serde::{Deserialize, Serialize};
use stemedb_storage::{key_codec, KVStore};
use tracing::instrument;
use utoipa::ToSchema;

use crate::{dto::ErrorResponse, state::AppState};

/// Query parameters for listing rejected records.
#[derive(Debug, Deserialize)]
pub struct RejectedParams {
    /// Maximum number of records to return (default: 100).
    pub limit: Option<usize>,
}

/// A WAL record that was permanently skipped by the IngestWorker.
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct RejectedRecordDto {
    /// WAL offset where the record was found.
    pub offset: u64,
    /// The record type (Assertion, Vote, Epoch).
    pub record_type: String,
    /// Why the record was rejected.
    pub reason: String,
    /// When the record was skipped (Unix timestamp).
    pub timestamp: u64,
}

/// Response listing rejected WAL records.
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct RejectedRecordsResponse {
    /// List of rejected records.
    pub rejected: Vec<RejectedRecordDto>,
    /// Total number of rejected records found.
    pub count: usize,
}

/// GET /v1/admin/rejected
///
/// List WAL records that were permanently rejected by the IngestWorker.
#[utoipa::path(
    get,
    path = "/v1/admin/rejected",
    params(
        ("limit" = Option<usize>, Query, description = "Maximum records to return (default: 100)")
    ),
    responses(
        (status = 200, description = "Rejected records listed", body = RejectedRecordsResponse),
        (status = 500, description = "Internal server error", body = ErrorResponse)
    ),
    tag = "admin"
)]
#[instrument(skip(state))]
pub async fn list_rejected(
    State(state): State<AppState>,
    axum::extract::Query(params): axum::extract::Query<RejectedParams>,
) -> std::result::Result<Json<RejectedRecordsResponse>, (axum::http::StatusCode, Json<ErrorResponse>)>
{
    let limit = params.limit.unwrap_or(100);
    let prefix = key_codec::rejected_records_scan_prefix();

    let entries = state.store.scan_prefix(&prefix).await.map_err(|e| {
        tracing::error!(error = %e, "Failed to scan rejected records");
        (
            axum::http::StatusCode::INTERNAL_SERVER_ERROR,
            Json(ErrorResponse {
                error: "Failed to retrieve rejected records".to_string(),
                code: "REJECTED_SCAN_ERROR".to_string(),
            }),
        )
    })?;

    let mut rejected = Vec::new();
    for (_key, value) in entries.into_iter().take(limit) {
        let json_str = String::from_utf8_lossy(&value);
        if let Ok(dto) = serde_json::from_str::<RejectedRecordDto>(&json_str) {
            rejected.push(dto);
        }
    }

    let count = rejected.len();
    Ok(Json(RejectedRecordsResponse { rejected, count }))
}
