//! Admin handlers for maintenance operations.

use axum::{extract::State, Json};
use tracing::{info, instrument, warn};

use crate::{
    dto::{DecayTrustRanksRequest, DecayTrustRanksResponse, RebuildIndexesResponse},
    error::Result,
    state::AppState,
};
use stemedb_storage::{
    key_codec, GenericIndexStore, GenericTrustRankStore, IndexStore, KVStore, TrustRankStore,
};

/// Default half-life for trust rank decay (30 days in seconds).
const DEFAULT_HALF_LIFE_SECONDS: u64 = 30 * 24 * 60 * 60;

/// Apply confidence half-life decay to all stored TrustRanks.
///
/// This is an admin operation that should be run periodically (e.g., daily)
/// by The Gardener. Agents who haven't been active will see their trust
/// scores gradually regress toward the mean.
#[utoipa::path(
    post,
    path = "/v1/admin/decay-trust-ranks",
    request_body = DecayTrustRanksRequest,
    responses(
        (status = 200, description = "Decay operation completed", body = DecayTrustRanksResponse),
        (status = 500, description = "Internal server error", body = crate::dto::ErrorResponse),
    ),
    tag = "admin"
)]
#[instrument(skip(state), fields(now = ?req.now, half_life_seconds = ?req.half_life_seconds))]
pub async fn decay_trust_ranks(
    State(state): State<AppState>,
    Json(req): Json<DecayTrustRanksRequest>,
) -> Result<Json<DecayTrustRanksResponse>> {
    let start = std::time::Instant::now();
    metrics::counter!("stemedb_http_requests_total", "method" => "POST", "path" => "/v1/admin/decay-trust-ranks").increment(1);

    // Determine timestamp to use (current time if not provided)
    let timestamp = req.now.unwrap_or_else(|| {
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map(|d| d.as_secs())
            .unwrap_or(0)
    });

    // Determine half-life to use
    let half_life = req.half_life_seconds.unwrap_or(DEFAULT_HALF_LIFE_SECONDS);

    // Create TrustRankStore from the shared store
    let trust_store = GenericTrustRankStore::new(state.store.clone());

    // Apply decay to all trust ranks
    let decayed_count = trust_store.decay_trust_ranks(timestamp, Some(half_life)).await?;

    // Track request duration (success case)
    metrics::histogram!("stemedb_http_request_duration_seconds",
        "method" => "POST",
        "path" => "/v1/admin/decay-trust-ranks",
        "status" => "200"
    )
    .record(start.elapsed().as_secs_f64());

    Ok(Json(DecayTrustRanksResponse {
        decayed_count,
        timestamp_used: timestamp,
        half_life_used: half_life,
        status: "Decay operation completed".to_string(),
    }))
}

/// Rebuild secondary indexes (Redb) from assertion data (Fjall).
///
/// This is a repair operation for when Redb indexes are missing or stale
/// while Fjall assertion data is intact. It scans all assertion data from
/// Fjall and reconstructs the S:, SP:, SUBJECTS:, HASH_SUBJECT:, and SRC:
/// indexes in Redb, then corrects the META:assertion_count.
///
/// This endpoint is idempotent — running it multiple times is safe because
/// the index store uses append-with-dedup semantics.
#[utoipa::path(
    post,
    path = "/v1/admin/rebuild-indexes",
    responses(
        (status = 200, description = "Index rebuild completed", body = RebuildIndexesResponse),
        (status = 500, description = "Internal server error", body = crate::dto::ErrorResponse),
    ),
    tag = "admin"
)]
#[instrument(skip(state))]
pub async fn rebuild_indexes(
    State(state): State<AppState>,
) -> Result<Json<RebuildIndexesResponse>> {
    let start = std::time::Instant::now();
    metrics::counter!("stemedb_http_requests_total", "method" => "POST", "path" => "/v1/admin/rebuild-indexes").increment(1);

    info!("Starting index rebuild: scanning Fjall for all assertions");

    // Capture current time once for FEED index fallback (timestamp:0 assertions)
    let now = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_secs())
        .unwrap_or(0);

    // Scan all assertion key-value pairs from Fjall
    let assertion_kvs = state.store.scan_fjall_assertions().await?;
    let total_keys = assertion_kvs.len();
    info!(total_keys, "Found assertion keys in Fjall");

    // Create an IndexStore backed by the same HybridStore
    let index_store = GenericIndexStore::new(state.store.clone());

    let mut rebuilt_count: u64 = 0;
    let mut skipped_count: u64 = 0;
    let mut first_error: Option<String> = None;

    for (key, value) in &assertion_kvs {
        // Extract subject from key
        let subject = match key_codec::extract_subject(key) {
            Some(s) => s.to_string(),
            None => {
                let msg = format!(
                    "extract_subject failed: key_len={}, first_bytes={:?}",
                    key.len(),
                    &key[..key.len().min(40)]
                );
                warn!("{}", msg);
                if first_error.is_none() {
                    first_error = Some(msg);
                }
                skipped_count += 1;
                continue;
            }
        };

        // Extract hash_hex from tag (tag is "H:{hash_hex}")
        let tag = key_codec::extract_tag(key);
        let hash_hex = match tag.strip_prefix(b"H:") {
            Some(hex_bytes) => match std::str::from_utf8(hex_bytes) {
                Ok(s) => s.to_string(),
                Err(e) => {
                    let msg = format!("hash_hex UTF-8 error for subject={subject}: {e}");
                    warn!("{}", msg);
                    if first_error.is_none() {
                        first_error = Some(msg);
                    }
                    skipped_count += 1;
                    continue;
                }
            },
            None => {
                let msg = format!(
                    "tag strip_prefix H: failed for subject={subject}: tag={:?}",
                    String::from_utf8_lossy(tag)
                );
                warn!("{}", msg);
                if first_error.is_none() {
                    first_error = Some(msg);
                }
                skipped_count += 1;
                continue;
            }
        };

        // Deserialize the assertion to get predicate and source_hash.
        // Uses compat deserialization to handle legacy data (pre-narrative schema).
        let assertion: stemedb_core::types::Assertion =
            match stemedb_core::serde::deserialize_assertion_compat(value) {
                Ok(a) => a,
                Err(e) => {
                    let msg = format!(
                        "deserialize failed for subject={subject} hash={hash_hex}: {e} (value_len={})",
                        value.len()
                    );
                    warn!("{}", msg);
                    if first_error.is_none() {
                        first_error = Some(msg);
                    }
                    skipped_count += 1;
                    continue;
                }
            };

        // Decode assertion hash from hex
        let hash_bytes: [u8; 32] = match hex::decode(&hash_hex) {
            Ok(bytes) if bytes.len() == 32 => {
                let mut arr = [0u8; 32];
                arr.copy_from_slice(&bytes);
                arr
            }
            Ok(bytes) => {
                let msg = format!(
                    "hash decode wrong length for subject={subject} hash={hash_hex}: got {} bytes",
                    bytes.len()
                );
                warn!("{}", msg);
                if first_error.is_none() {
                    first_error = Some(msg);
                }
                skipped_count += 1;
                continue;
            }
            Err(e) => {
                let msg = format!("hex decode failed for subject={subject} hash={hash_hex}: {e}");
                warn!("{}", msg);
                if first_error.is_none() {
                    first_error = Some(msg);
                }
                skipped_count += 1;
                continue;
            }
        };

        // Rebuild S: and SP: indexes (includes SUBJECTS: discovery index)
        if let Err(e) =
            index_store.add_to_indexes(&subject, &assertion.predicate, &hash_bytes).await
        {
            warn!(%subject, %hash_hex, error = %e, "Failed to add to indexes");
            skipped_count += 1;
            continue;
        }

        // Rebuild HASH_SUBJECT: reverse index
        let hs_key = key_codec::hash_subject_key(&hash_hex);
        if let Err(e) = state.store.put(&hs_key, subject.as_bytes()).await {
            warn!(%subject, %hash_hex, error = %e, "Failed to write hash_subject index");
        }

        // Rebuild SRC: source index
        if let Err(e) = index_store.add_to_source_index(&assertion.source_hash, &hash_bytes).await {
            warn!(%subject, %hash_hex, error = %e, "Failed to add to source index");
        }

        // Rebuild FEED index: use assertion.timestamp as best-available proxy
        // for ingestion time. Fall back to current time for timestamp:0 assertions.
        let feed_ts = if assertion.timestamp > 0 { assertion.timestamp } else { now };
        let feed_idx_key = key_codec::feed_key(feed_ts, &hash_hex);
        if let Err(e) = state.store.put(&feed_idx_key, subject.as_bytes()).await {
            warn!(%subject, %hash_hex, error = %e, "Failed to write feed index");
        }

        rebuilt_count += 1;
    }

    // Correct the assertion count: total = rebuilt + skipped (both are real assertions).
    // The count key stores a u64 in little-endian format.
    let total_assertions = rebuilt_count + skipped_count;
    let count_key = key_codec::assertion_count_key();
    let count_bytes = total_assertions.to_le_bytes();
    state.store.put(&count_key, &count_bytes).await?;

    let elapsed_ms = start.elapsed().as_millis() as u64;

    info!(rebuilt_count, skipped_count, elapsed_ms, "Index rebuild complete");

    metrics::histogram!("stemedb_http_request_duration_seconds",
        "method" => "POST",
        "path" => "/v1/admin/rebuild-indexes",
        "status" => "200"
    )
    .record(start.elapsed().as_secs_f64());

    Ok(Json(RebuildIndexesResponse {
        rebuilt_count,
        skipped_count,
        elapsed_ms,
        status: format!(
            "Rebuilt indexes for {} assertions ({} skipped) in {}ms",
            rebuilt_count, skipped_count, elapsed_ms
        ),
        first_error,
    }))
}
