//! HTTP handlers for quarantine management (Content Defense Phase 7C).
//!
//! # Security Warning
//!
//! These admin endpoints do NOT include authentication middleware.
//! In production deployments, these endpoints MUST be protected by one of:
//!
//! 1. **Network-level protection**: Run admin endpoints on a separate port
//!    that is only accessible from trusted networks (e.g., internal VPN).
//!
//! 2. **Reverse proxy authentication**: Use nginx/envoy/etc. to require
//!    authentication before requests reach these endpoints.
//!
//! 3. **Custom middleware**: Implement an `admin_auth` middleware layer
//!    that validates admin API keys or JWT tokens.
//!
//! Failing to protect these endpoints allows anyone to approve spam content
//! or reject legitimate content from the quarantine queue.

use crate::{
    dto::{
        ErrorResponse, QuarantineApproveResponse, QuarantineEventDto, QuarantineGetResponse,
        QuarantineListParams, QuarantineListResponse,
    },
    AppState,
};
use axum::{
    extract::{Path, Query, State},
    http::StatusCode,
    Json,
};
use stemedb_storage::{QuarantineStore, StorageError};
use tracing::instrument;

/// GET /v1/admin/quarantine
///
/// List pending quarantine events (or all events if include_reviewed=true).
#[utoipa::path(
    get,
    path = "/v1/admin/quarantine",
    params(
        ("limit" = Option<usize>, Query, description = "Maximum number of events to return (default: 100)"),
        ("include_reviewed" = Option<bool>, Query, description = "Include reviewed events (default: false)")
    ),
    responses(
        (status = 200, description = "Quarantine events retrieved successfully", body = QuarantineListResponse),
        (status = 500, description = "Internal server error", body = ErrorResponse)
    ),
    tag = "admin"
)]
#[instrument(skip(state))]
pub async fn list_quarantine(
    State(state): State<AppState>,
    Query(params): Query<QuarantineListParams>,
) -> std::result::Result<Json<QuarantineListResponse>, (StatusCode, Json<ErrorResponse>)> {
    let store = &state.quarantine_store;

    let events = if params.include_reviewed {
        store.list_all(params.limit).await.map_err(|e| {
            tracing::error!(error = %e, "Failed to list all quarantine events");
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ErrorResponse {
                    error: "Failed to retrieve quarantine events".to_string(),
                    code: "QUARANTINE_RETRIEVAL_ERROR".to_string(),
                }),
            )
        })?
    } else {
        store.list_pending(params.limit).await.map_err(|e| {
            tracing::error!(error = %e, "Failed to list pending quarantine events");
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ErrorResponse {
                    error: "Failed to retrieve pending quarantine events".to_string(),
                    code: "QUARANTINE_RETRIEVAL_ERROR".to_string(),
                }),
            )
        })?
    };

    let pending_count = store.pending_count().await.map_err(|e| {
        tracing::error!(error = %e, "Failed to count pending quarantine events");
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(ErrorResponse {
                error: "Failed to count pending quarantine events".to_string(),
                code: "QUARANTINE_COUNT_ERROR".to_string(),
            }),
        )
    })?;

    let dtos: Vec<QuarantineEventDto> = events.iter().map(QuarantineEventDto::from).collect();

    Ok(Json(QuarantineListResponse { quarantined: dtos, count: events.len(), pending_count }))
}

/// GET /v1/admin/quarantine/{hash}
///
/// Get a specific quarantine event by hash (includes assertion bytes).
#[utoipa::path(
    get,
    path = "/v1/admin/quarantine/{hash}",
    params(
        ("hash" = String, Path, description = "Hex-encoded hash of the quarantined assertion")
    ),
    responses(
        (status = 200, description = "Quarantine event retrieved successfully", body = QuarantineGetResponse),
        (status = 404, description = "Quarantine event not found", body = ErrorResponse),
        (status = 400, description = "Invalid hash format", body = ErrorResponse),
        (status = 500, description = "Internal server error", body = ErrorResponse)
    ),
    tag = "admin"
)]
#[instrument(skip(state))]
pub async fn get_quarantine(
    State(state): State<AppState>,
    Path(hash_hex): Path<String>,
) -> std::result::Result<Json<QuarantineGetResponse>, (StatusCode, Json<ErrorResponse>)> {
    let hash = parse_hash(&hash_hex)?;
    let store = &state.quarantine_store;

    let event = store.get_quarantine(&hash).await.map_err(|e| {
        tracing::error!(error = %e, hash = %hash_hex, "Failed to get quarantine event");
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(ErrorResponse {
                error: "Failed to retrieve quarantine event".to_string(),
                code: "QUARANTINE_RETRIEVAL_ERROR".to_string(),
            }),
        )
    })?;

    match event {
        Some(event) => {
            let dto = QuarantineEventDto::with_assertion_bytes(&event);
            Ok(Json(QuarantineGetResponse { event: dto }))
        }
        None => Err((
            StatusCode::NOT_FOUND,
            Json(ErrorResponse {
                error: "Quarantine event not found".to_string(),
                code: "QUARANTINE_NOT_FOUND".to_string(),
            }),
        )),
    }
}

/// POST /v1/admin/quarantine/{hash}/approve
///
/// Approve a quarantined assertion, returning the assertion bytes for indexing.
#[utoipa::path(
    post,
    path = "/v1/admin/quarantine/{hash}/approve",
    params(
        ("hash" = String, Path, description = "Hex-encoded hash of the quarantined assertion")
    ),
    responses(
        (status = 200, description = "Quarantine event approved successfully", body = QuarantineApproveResponse),
        (status = 404, description = "Quarantine event not found", body = ErrorResponse),
        (status = 400, description = "Invalid hash format", body = ErrorResponse),
        (status = 500, description = "Internal server error", body = ErrorResponse)
    ),
    tag = "admin"
)]
#[instrument(skip(state))]
pub async fn approve_quarantine(
    State(state): State<AppState>,
    Path(hash_hex): Path<String>,
) -> std::result::Result<Json<QuarantineApproveResponse>, (StatusCode, Json<ErrorResponse>)> {
    let hash = parse_hash(&hash_hex)?;
    let store = &state.quarantine_store;

    let event = store.approve(&hash).await.map_err(|e| match e {
        StorageError::NotFound(_) => (
            StatusCode::NOT_FOUND,
            Json(ErrorResponse {
                error: "Quarantine event not found".to_string(),
                code: "QUARANTINE_NOT_FOUND".to_string(),
            }),
        ),
        _ => {
            tracing::error!(error = %e, hash = %hash_hex, "Failed to approve quarantine event");
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ErrorResponse {
                    error: "Failed to approve quarantine event".to_string(),
                    code: "QUARANTINE_APPROVE_ERROR".to_string(),
                }),
            )
        }
    })?;

    tracing::info!(hash = %hash_hex, "Quarantine event approved");

    Ok(Json(QuarantineApproveResponse {
        hash: hash_hex,
        message: "Assertion approved and ready for indexing".to_string(),
        assertion_bytes_hex: hex::encode(&event.assertion_bytes),
    }))
}

/// POST /v1/admin/quarantine/{hash}/reject
///
/// Reject a quarantined assertion (remains in quarantine for audit trail).
#[utoipa::path(
    post,
    path = "/v1/admin/quarantine/{hash}/reject",
    params(
        ("hash" = String, Path, description = "Hex-encoded hash of the quarantined assertion")
    ),
    responses(
        (status = 200, description = "Quarantine event rejected successfully"),
        (status = 404, description = "Quarantine event not found", body = ErrorResponse),
        (status = 400, description = "Invalid hash format", body = ErrorResponse),
        (status = 500, description = "Internal server error", body = ErrorResponse)
    ),
    tag = "admin"
)]
#[instrument(skip(state))]
pub async fn reject_quarantine(
    State(state): State<AppState>,
    Path(hash_hex): Path<String>,
) -> std::result::Result<StatusCode, (StatusCode, Json<ErrorResponse>)> {
    let hash = parse_hash(&hash_hex)?;
    let store = &state.quarantine_store;

    store.reject(&hash).await.map_err(|e| match e {
        StorageError::NotFound(_) => (
            StatusCode::NOT_FOUND,
            Json(ErrorResponse {
                error: "Quarantine event not found".to_string(),
                code: "QUARANTINE_NOT_FOUND".to_string(),
            }),
        ),
        _ => {
            tracing::error!(error = %e, hash = %hash_hex, "Failed to reject quarantine event");
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ErrorResponse {
                    error: "Failed to reject quarantine event".to_string(),
                    code: "QUARANTINE_REJECT_ERROR".to_string(),
                }),
            )
        }
    })?;

    tracing::info!(hash = %hash_hex, "Quarantine event rejected");

    Ok(StatusCode::OK)
}

/// Parse and validate a hex-encoded hash.
fn parse_hash(hash_hex: &str) -> std::result::Result<[u8; 32], (StatusCode, Json<ErrorResponse>)> {
    let hash_bytes = hex::decode(hash_hex).map_err(|_| {
        (
            StatusCode::BAD_REQUEST,
            Json(ErrorResponse {
                error: "Invalid hash format (must be hex)".to_string(),
                code: "INVALID_HASH_FORMAT".to_string(),
            }),
        )
    })?;

    if hash_bytes.len() != 32 {
        return Err((
            StatusCode::BAD_REQUEST,
            Json(ErrorResponse {
                error: "Hash must be 32 bytes (64 hex characters)".to_string(),
                code: "INVALID_HASH_LENGTH".to_string(),
            }),
        ));
    }

    let mut hash = [0u8; 32];
    hash.copy_from_slice(&hash_bytes);
    Ok(hash)
}
