//! CAS-serving: GET /cas/{hash} → stream fil fra disk.
//!
//! Slår opp MIME-type fra media-nodens metadata i PG.
//! Streamer filen med riktig Content-Type og Content-Length.
//! Hashen er i praksis en capability token — ingen auth kreves.

use axum::{
    body::Body,
    extract::{Path, State},
    http::{header, StatusCode},
    response::Response,
};
use tokio::fs::File;
use tokio_util::io::ReaderStream;

use crate::AppState;

/// Valider at hash er 64 hex-tegn (SHA-256).
fn is_valid_hash(hash: &str) -> bool {
    hash.len() == 64 && hash.chars().all(|c| c.is_ascii_hexdigit())
}

/// GET /cas/{hash} — stream CAS-fil med riktig Content-Type.
pub async fn get_cas_file(
    State(state): State<AppState>,
    Path(hash): Path<String>,
) -> Result<Response, StatusCode> {
    // Valider hash-format for å hindre path traversal
    if !is_valid_hash(&hash) {
        return Err(StatusCode::BAD_REQUEST);
    }

    // Sjekk at filen finnes i CAS
    let path = state.cas.path_for(&hash);
    if !path.exists() {
        return Err(StatusCode::NOT_FOUND);
    }

    // Slå opp MIME-type fra media-nodens metadata i PG
    let mime: String = sqlx::query_scalar(
        "SELECT metadata->>'mime' FROM nodes WHERE metadata->>'cas_hash' = $1 LIMIT 1",
    )
    .bind(&hash)
    .fetch_optional(&state.db)
    .await
    .map_err(|e| {
        tracing::error!(hash = %hash, error = %e, "Feil ved MIME-oppslag i PG");
        StatusCode::INTERNAL_SERVER_ERROR
    })?
    .unwrap_or_else(|| "application/octet-stream".to_string());

    // Hent filstørrelse
    let metadata = tokio::fs::metadata(&path).await.map_err(|e| {
        tracing::error!(hash = %hash, error = %e, "Kunne ikke lese filmetadata");
        StatusCode::INTERNAL_SERVER_ERROR
    })?;

    // Åpne fil og stream
    let file = File::open(&path).await.map_err(|e| {
        tracing::error!(hash = %hash, error = %e, "Kunne ikke åpne CAS-fil");
        StatusCode::INTERNAL_SERVER_ERROR
    })?;

    // Oppdater last_accessed_at asynkront (fire-and-forget for ytelse)
    let db_clone = state.db.clone();
    let hash_clone = hash.clone();
    tokio::spawn(async move {
        crate::pruning::touch_access(&db_clone, &hash_clone).await;
    });

    let stream = ReaderStream::new(file);
    let body = Body::from_stream(stream);

    Ok(Response::builder()
        .header(header::CONTENT_TYPE, mime)
        .header(header::CONTENT_LENGTH, metadata.len())
        .header(header::CACHE_CONTROL, "public, max-age=31536000, immutable")
        .body(body)
        .unwrap())
}
