//! Handler for the `/v1/feed` endpoint (newest-first assertion browsing).
//!
//! Uses a dedicated FEED index (`\x00FEED:{inverted_ts}:{hash_hex}`) for
//! O(page_size) reads instead of loading all assertions into memory.

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

use crate::{
    dto::{FeedParams, QueryResponse},
    error::Result,
    extractors::QsQuery,
    state::AppState,
};

use stemedb_core::serde::deserialize_assertion_compat;
use stemedb_storage::{key_codec, KVStore};

use super::query::assertion_to_dto_with_warning;

/// Browse all assertions in newest-first order with pagination.
///
/// Returns assertions ordered by ingestion time descending (when the system
/// received each assertion), useful for "what was just written?" dashboards
/// and dev workflows. No lens resolution is applied — this is a raw feed.
///
/// # Pagination
///
/// - `limit`: Max results per page (default 50, max 500)
/// - `offset`: Number of results to skip (default 0)
#[utoipa::path(
    get,
    path = "/v1/feed",
    params(
        ("limit" = Option<usize>, Query, description = "Max results (default 50, max 500)"),
        ("offset" = Option<usize>, Query, description = "Pagination offset (default 0)")
    ),
    responses(
        (status = 200, description = "Feed results", body = QueryResponse),
        (status = 500, description = "Internal server error", body = crate::dto::ErrorResponse)
    ),
    tag = "query"
)]
#[instrument(skip(state), fields(limit = params.limit, offset = params.offset))]
pub async fn feed(
    State(state): State<AppState>,
    QsQuery(params): QsQuery<FeedParams>,
) -> Result<Json<QueryResponse>> {
    metrics::counter!("stemedb_queries_total", "endpoint" => "feed").increment(1);
    let query_start = std::time::Instant::now();

    // Scan the FEED index — keys are in newest-first order (inverted timestamp).
    let feed_prefix = key_codec::feed_scan_prefix();
    let entries = state.store.scan_prefix(&feed_prefix).await?;

    let total_count = entries.len();
    let limit = params.clamped_limit();
    let offset = params.offset;
    let has_more = offset + limit < total_count;

    // Paginate the index entries (cheap — no assertion data loaded yet)
    let page_entries: Vec<_> = entries.into_iter().skip(offset).take(limit).collect();

    // Fetch actual assertion data only for the current page
    let mut assertion_responses = Vec::with_capacity(page_entries.len());
    for (key, value) in &page_entries {
        let hash_hex = match extract_hash_hex_from_feed_key(key) {
            Some(h) => h,
            None => {
                warn!(key_len = key.len(), "Malformed FEED index key, skipping");
                continue;
            }
        };
        let subject = match std::str::from_utf8(value) {
            Ok(s) => s,
            Err(e) => {
                warn!(error = %e, "Invalid UTF-8 in FEED index value, skipping");
                continue;
            }
        };

        let assertion_data_key = key_codec::assertion_key(subject, hash_hex);
        let data = match state.store.get(&assertion_data_key).await? {
            Some(d) => d,
            None => {
                warn!(%hash_hex, %subject, "FEED index references missing assertion data, skipping");
                continue;
            }
        };

        match deserialize_assertion_compat(&data) {
            Ok(a) => match assertion_to_dto_with_warning(a, None) {
                Ok(dto) => assertion_responses.push(dto),
                Err(e) => {
                    warn!(%hash_hex, error = %e, "Failed to convert assertion to DTO, skipping");
                }
            },
            Err(e) => {
                warn!(%hash_hex, error = %e, "Failed to deserialize assertion, skipping");
            }
        }
    }

    metrics::histogram!("stemedb_query_latency_seconds", "endpoint" => "feed")
        .record(query_start.elapsed().as_secs_f64());

    Ok(Json(QueryResponse {
        assertions: assertion_responses,
        total_count,
        has_more,
        conflict_score: None,
        resolution_confidence: None,
        changes_since: None,
    }))
}

/// Extract the hash_hex portion from a FEED index key.
///
/// Key format: `\x00FEED:{16 hex chars (inverted ts)}:{64 hex chars (hash)}`
/// Prefix `\x00FEED:` = 6 bytes, inverted_ts = 16 bytes, `:` = 1 byte → 23 bytes offset.
fn extract_hash_hex_from_feed_key(key: &[u8]) -> Option<&str> {
    // \x00FEED: = 6 bytes, inverted_ts = 16 hex chars, : = 1 byte
    const HASH_OFFSET: usize = 6 + 16 + 1; // 23
    if key.len() <= HASH_OFFSET {
        return None;
    }
    std::str::from_utf8(&key[HASH_OFFSET..]).ok()
}
