//! HTTP integration tests for advanced features and edge cases.
//!
//! Coverage:
//! - GET /v1/health - Health check endpoint
//! - POST /v1/admin/decay-trust-ranks - Admin decay operations
//! - Rate limiting via QuotaStore middleware
//! - 404 Not Found responses
//! - Edge cases and error scenarios

#![allow(clippy::expect_used)]

mod common;

use axum::{
    body::Body,
    http::{Request, StatusCode},
};
use serde_json::json;
use std::sync::Arc;
use tower::ServiceExt;

use stemedb_api::{create_router, create_router_with_meter, AppState};
use stemedb_storage::{HybridStore, QuotaStore};
use stemedb_wal::Journal;

// ============================================================================
// Health and System Tests
// ============================================================================

#[tokio::test]
async fn test_health_endpoint() {
    let env = common::create_test_env().await;
    let app = create_router(env.state);

    let request =
        Request::builder().uri("/v1/health").method("GET").body(Body::empty()).expect("Request");

    let response = app.oneshot(request).await.expect("Request");
    assert_eq!(response.status(), StatusCode::OK);

    let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.expect("Body");
    let json: serde_json::Value = serde_json::from_slice(&body).expect("JSON");

    assert_eq!(json["status"], "healthy");
    assert!(json.get("version").is_some());
}

#[tokio::test]
async fn test_not_found_endpoint() {
    let env = common::create_test_env().await;
    let app = create_router(env.state);

    let request = Request::builder()
        .uri("/v1/nonexistent")
        .method("GET")
        .body(Body::empty())
        .expect("Request");

    let response = app.oneshot(request).await.expect("Request");
    assert_eq!(response.status(), StatusCode::NOT_FOUND);
}

// ============================================================================
// POST /v1/admin/decay-trust-ranks - Admin Decay Tests
// ============================================================================

#[tokio::test]
async fn test_decay_trust_ranks_empty_store() {
    let env = common::create_test_env().await;
    let app = create_router(env.state);

    let request = Request::builder()
        .uri("/v1/admin/decay-trust-ranks")
        .method("POST")
        .header("content-type", "application/json")
        .body(Body::from(serde_json::to_vec(&json!({})).expect("JSON")))
        .expect("Request");

    let response = app.oneshot(request).await.expect("Request");
    assert_eq!(response.status(), StatusCode::OK);

    let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.expect("Body");
    let json: serde_json::Value = serde_json::from_slice(&body).expect("JSON");

    assert_eq!(json["decayed_count"], 0);
    assert!(json["timestamp_used"].is_number());
    assert_eq!(json["status"], "Decay operation completed");
}

#[tokio::test]
async fn test_decay_trust_ranks_with_custom_params() {
    let env = common::create_test_env().await;
    let app = create_router(env.state);

    let request_body = json!({
        "now": 1704067200,
        "half_life_seconds": 604800
    });

    let request = Request::builder()
        .uri("/v1/admin/decay-trust-ranks")
        .method("POST")
        .header("content-type", "application/json")
        .body(Body::from(serde_json::to_vec(&request_body).expect("JSON")))
        .expect("Request");

    let response = app.oneshot(request).await.expect("Request");
    assert_eq!(response.status(), StatusCode::OK);

    let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.expect("Body");
    let json: serde_json::Value = serde_json::from_slice(&body).expect("JSON");

    assert_eq!(json["timestamp_used"], 1704067200);
    assert_eq!(json["half_life_used"], 604800);
}

#[tokio::test]
async fn test_decay_trust_ranks_response_structure() {
    let env = common::create_test_env().await;
    let app = create_router(env.state);

    let request = Request::builder()
        .uri("/v1/admin/decay-trust-ranks")
        .method("POST")
        .header("content-type", "application/json")
        .body(Body::from(serde_json::to_vec(&json!({})).expect("JSON")))
        .expect("Request");

    let response = app.oneshot(request).await.expect("Request");
    let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.expect("Body");
    let json: serde_json::Value = serde_json::from_slice(&body).expect("JSON");

    // Verify all expected fields are present
    assert!(json.get("decayed_count").is_some());
    assert!(json.get("timestamp_used").is_some());
    assert!(json.get("half_life_used").is_some());
    assert!(json.get("status").is_some());
    assert_eq!(json.as_object().expect("object").len(), 4);
}

#[tokio::test]
async fn test_decay_trust_ranks_actually_decays() {
    use stemedb_storage::{GenericTrustRankStore, TrustRank, TrustRankStore};

    let env = common::create_test_env().await;

    // Pre-populate TrustRank with an old timestamp
    let agent_id = [42u8; 32];
    let mut trust_rank = TrustRank::new(agent_id, 1000);
    trust_rank.score = 0.8;
    let trust_store = GenericTrustRankStore::new(env.state.store.clone());
    trust_store.put_trust_rank(&trust_rank).await.expect("put trust rank");

    let app = create_router(env.state.clone());

    // Decay with a timestamp far in the future (30+ days)
    let thirty_one_days = 31 * 24 * 60 * 60;
    let request_body = json!({
        "now": 1000 + thirty_one_days,
        "half_life_seconds": 30 * 24 * 60 * 60  // 30 days
    });

    let request = Request::builder()
        .uri("/v1/admin/decay-trust-ranks")
        .method("POST")
        .header("content-type", "application/json")
        .body(Body::from(serde_json::to_vec(&request_body).expect("JSON")))
        .expect("Request");

    let response = app.oneshot(request).await.expect("Request");
    assert_eq!(response.status(), StatusCode::OK);

    let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.expect("Body");
    let json: serde_json::Value = serde_json::from_slice(&body).expect("JSON");

    // Should have decayed exactly 1 TrustRank
    assert_eq!(json["decayed_count"], 1);

    // Verify the score was actually decayed (0.8 * 0.5^(31/30) ≈ 0.37)
    let decayed_trust = trust_store.get_trust_rank(&agent_id).await.expect("get trust rank");
    assert!(
        decayed_trust.score < 0.5,
        "Score should be significantly decayed, got {}",
        decayed_trust.score
    );
}

// ============================================================================
// Rate Limiting Tests (QuotaStore Middleware)
// ============================================================================

#[tokio::test]
async fn test_quota_consumption_with_meter() {
    let temp_dir = tempfile::tempdir().expect("temp dir");
    let wal_dir = temp_dir.path().join("wal");
    let db_dir = temp_dir.path().join("db");

    std::fs::create_dir_all(&wal_dir).expect("wal dir");
    std::fs::create_dir_all(&db_dir).expect("db dir");

    let write_journal = Journal::open(&wal_dir).expect("write journal");
    let read_journal = Journal::open(&wal_dir).expect("read journal");
    let store = Arc::new(HybridStore::open(&db_dir).expect("store"));

    let state = AppState::new(write_journal, read_journal, store.clone());
    let quota_store = state.quota_store.clone();

    let app = create_router_with_meter(state);

    let (agent_id, signature) = common::sign_message("test");
    let agent_id_hex = hex::encode(agent_id);

    // Set a low quota limit for testing
    quota_store.set_quota_limit(&agent_id, 50).await.expect("set quota");

    let assertion = json!({
        "subject": "QuotaTest",
        "predicate": "test",
        "object": {"type": "Number", "value": 1.0},
        "confidence": 0.9,
        "signatures": [{
            "agent_id": agent_id_hex,
            "signature": hex::encode(signature),
            "timestamp": 1000
        }],
        "source_hash": hex::encode([0u8; 32])
    });

    let request = Request::builder()
        .uri("/v1/assert")
        .method("POST")
        .header("content-type", "application/json")
        .header("x-agent-id", &agent_id_hex)
        .body(Body::from(serde_json::to_vec(&assertion).expect("JSON")))
        .expect("Request");

    let response = app.oneshot(request).await.expect("Request");

    // Should succeed and include quota headers
    assert_eq!(response.status(), StatusCode::CREATED);

    let headers = response.headers();
    assert!(headers.get("x-quota-remaining").is_some());
    assert!(headers.get("x-quota-limit").is_some());
    assert!(headers.get("x-quota-reset").is_some());
}

#[tokio::test]
async fn test_quota_exceeded_response() {
    let temp_dir = tempfile::tempdir().expect("temp dir");
    let wal_dir = temp_dir.path().join("wal");
    let db_dir = temp_dir.path().join("db");

    std::fs::create_dir_all(&wal_dir).expect("wal dir");
    std::fs::create_dir_all(&db_dir).expect("db dir");

    let write_journal = Journal::open(&wal_dir).expect("write journal");
    let read_journal = Journal::open(&wal_dir).expect("read journal");
    let store = Arc::new(HybridStore::open(&db_dir).expect("store"));

    let state = AppState::new(write_journal, read_journal, store.clone());
    let quota_store = state.quota_store.clone();

    let app = create_router_with_meter(state);

    let (agent_id, _) = common::sign_message("test");
    let agent_id_hex = hex::encode(agent_id);

    // Set quota to 0 to immediately trigger quota exceeded
    quota_store.set_quota_limit(&agent_id, 0).await.expect("set quota");

    let request = Request::builder()
        .uri("/v1/query?subject=Test")
        .method("GET")
        .header("x-agent-id", &agent_id_hex)
        .body(Body::empty())
        .expect("Request");

    let response = app.oneshot(request).await.expect("Request");

    // Should return 429 Too Many Requests
    assert_eq!(response.status(), StatusCode::TOO_MANY_REQUESTS);

    let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.expect("Body");
    let json: serde_json::Value = serde_json::from_slice(&body).expect("JSON");

    assert!(json.get("error").is_some());
    assert_eq!(json["code"], "QUOTA_EXCEEDED");
    assert!(json.get("remaining").is_some());
    assert!(json.get("limit").is_some());
    assert!(json.get("reset_at").is_some());
}

#[tokio::test]
async fn test_quota_headers_format() {
    let temp_dir = tempfile::tempdir().expect("temp dir");
    let wal_dir = temp_dir.path().join("wal");
    let db_dir = temp_dir.path().join("db");

    std::fs::create_dir_all(&wal_dir).expect("wal dir");
    std::fs::create_dir_all(&db_dir).expect("db dir");

    let write_journal = Journal::open(&wal_dir).expect("write journal");
    let read_journal = Journal::open(&wal_dir).expect("read journal");
    let store = Arc::new(HybridStore::open(&db_dir).expect("store"));

    let state = AppState::new(write_journal, read_journal, store.clone());
    let quota_store = state.quota_store.clone();

    let app = create_router_with_meter(state);

    let (agent_id, _) = common::sign_message("test");
    let agent_id_hex = hex::encode(agent_id);

    quota_store.set_quota_limit(&agent_id, 100).await.expect("set quota");

    let request = Request::builder()
        .uri("/v1/query?subject=Test")
        .method("GET")
        .header("x-agent-id", &agent_id_hex)
        .body(Body::empty())
        .expect("Request");

    let response = app.oneshot(request).await.expect("Request");
    assert_eq!(response.status(), StatusCode::OK);

    let headers = response.headers();

    // Verify quota headers are present and parseable
    let remaining = headers.get("x-quota-remaining").expect("remaining header");
    let limit = headers.get("x-quota-limit").expect("limit header");
    let reset = headers.get("x-quota-reset").expect("reset header");

    assert!(remaining.to_str().expect("remaining str").parse::<u64>().is_ok());
    assert!(limit.to_str().expect("limit str").parse::<u64>().is_ok());
    assert!(reset.to_str().expect("reset str").parse::<u64>().is_ok());
}
