//! API Tests Part 18: T-COV-95 Coverage Bridge (B2 + B3)
//!
//! Covers uncovered paths in:
//! - gpu_handlers.rs: struct serde, BatchConfig methods, ContinuousBatchResponse
//! - realize_handlers.rs: ContextWindowConfig, ContextWindowManager, format_chat_messages
//! - openai_handlers.rs: HTTP handlers, request/response serde
//! - mod.rs: AppState accessors, build_trace_data
//!
//! Refs PMAT-802: Protocol T-COV-95 Batches B2+B3

use axum::{
    body::Body,
    http::{Request, StatusCode},
};
use tower::util::ServiceExt;

use crate::api::test_helpers::create_test_app_shared;
use crate::api::AppState;

// ============================================================================
// B2: GPU Handler Struct Serde Round-Trips
// ============================================================================

#[test]
fn test_gpu_batch_request_serde() {
    use crate::api::gpu_handlers::GpuBatchRequest;

    let req = GpuBatchRequest {
        prompts: vec!["Hello".to_string(), "World".to_string()],
        max_tokens: 50,
        temperature: 0.7,
        top_k: 40,
        stop: vec!["<|endoftext|>".to_string()],
    };
    let json = serde_json::to_string(&req).unwrap();
    let deserialized: GpuBatchRequest = serde_json::from_str(&json).unwrap();
    assert_eq!(deserialized.prompts.len(), 2);
    assert_eq!(deserialized.max_tokens, 50);
    assert!((deserialized.temperature - 0.7).abs() < 1e-6);
    assert_eq!(deserialized.top_k, 40);
    assert_eq!(deserialized.stop.len(), 1);
}

#[test]
fn test_gpu_batch_response_serde() {
    use crate::api::gpu_handlers::{GpuBatchResponse, GpuBatchResult, GpuBatchStats};

    let resp = GpuBatchResponse {
        results: vec![GpuBatchResult {
            index: 0,
            token_ids: vec![1, 2, 3],
            text: "hello".to_string(),
            num_generated: 3,
        }],
        stats: GpuBatchStats {
            batch_size: 1,
            gpu_used: false,
            total_tokens: 3,
            processing_time_ms: 10.5,
            throughput_tps: 285.7,
        },
    };
    let json = serde_json::to_string(&resp).unwrap();
    let deserialized: GpuBatchResponse = serde_json::from_str(&json).unwrap();
    assert_eq!(deserialized.results.len(), 1);
    assert_eq!(deserialized.results[0].index, 0);
    assert_eq!(deserialized.results[0].num_generated, 3);
    assert_eq!(deserialized.stats.batch_size, 1);
    assert!(!deserialized.stats.gpu_used);
}

#[test]
fn test_gpu_batch_stats_serde() {
    use crate::api::gpu_handlers::GpuBatchStats;

    let stats = GpuBatchStats {
        batch_size: 32,
        gpu_used: true,
        total_tokens: 1600,
        processing_time_ms: 50.0,
        throughput_tps: 32000.0,
    };
    let json = serde_json::to_string(&stats).unwrap();
    let deserialized: GpuBatchStats = serde_json::from_str(&json).unwrap();
    assert_eq!(deserialized.batch_size, 32);
    assert!(deserialized.gpu_used);
    assert_eq!(deserialized.total_tokens, 1600);
}

#[test]
fn test_gpu_warmup_response_serde() {
    use crate::api::gpu_handlers::GpuWarmupResponse;

    let resp = GpuWarmupResponse {
        success: true,
        memory_bytes: 6_000_000_000,
        num_layers: 32,
        message: "GPU cache warmed up".to_string(),
    };
    let json = serde_json::to_string(&resp).unwrap();
    let deserialized: GpuWarmupResponse = serde_json::from_str(&json).unwrap();
    assert!(deserialized.success);
    assert_eq!(deserialized.memory_bytes, 6_000_000_000);
    assert_eq!(deserialized.num_layers, 32);
}

#[test]
fn test_gpu_status_response_serde() {
    use crate::api::gpu_handlers::GpuStatusResponse;

    let resp = GpuStatusResponse {
        cache_ready: false,
        cache_memory_bytes: 0,
        batch_threshold: 32,
        recommended_min_batch: 32,
    };
    let json = serde_json::to_string(&resp).unwrap();
    let deserialized: GpuStatusResponse = serde_json::from_str(&json).unwrap();
    assert!(!deserialized.cache_ready);
    assert_eq!(deserialized.batch_threshold, 32);
}

#[test]
fn test_gpu_batch_result_serde() {
    use crate::api::gpu_handlers::GpuBatchResult;

    let result = GpuBatchResult {
        index: 5,
        token_ids: vec![100, 200, 300, 400],
        text: "test output".to_string(),
        num_generated: 4,
    };
    let json = serde_json::to_string(&result).unwrap();
    let deserialized: GpuBatchResult = serde_json::from_str(&json).unwrap();
    assert_eq!(deserialized.index, 5);
    assert_eq!(deserialized.token_ids, vec![100, 200, 300, 400]);
    assert_eq!(deserialized.text, "test output");
}

#[test]
fn test_gpu_batch_request_defaults() {
    // Test default values via serde deserialization with minimal JSON
    use crate::api::gpu_handlers::GpuBatchRequest;

    let json = r#"{"prompts":["hello"]}"#;
    let req: GpuBatchRequest = serde_json::from_str(json).unwrap();
    assert_eq!(req.prompts, vec!["hello"]);
    // max_tokens should use default_max_tokens()
    assert!(req.max_tokens > 0);
    // temperature default is 0.0
    assert!((req.temperature - 0.0).abs() < 1e-6);
    // stop default is empty
    assert!(req.stop.is_empty());
}

// ============================================================================
// B2: HTTP Endpoint Integration Tests
// ============================================================================

#[tokio::test]
async fn test_tokenize_endpoint() {
    let app = create_test_app_shared();
    let request = Request::builder()
        .method("POST")
        .uri("/v1/tokenize")
        .header("content-type", "application/json")
        .body(Body::from(r#"{"text":"Hello world"}"#))
        .unwrap();

    let response = app.oneshot(request).await.unwrap();
    // Mock state may return NOT_FOUND or 200
    assert!(
        response.status() == StatusCode::OK
            || response.status() == StatusCode::NOT_FOUND
            || response.status() == StatusCode::UNPROCESSABLE_ENTITY,
    );
}

#[tokio::test]
async fn test_generate_endpoint() {
    let app = create_test_app_shared();
    let request = Request::builder()
        .method("POST")
        .uri("/v1/generate")
        .header("content-type", "application/json")
        .body(Body::from(
            r#"{"prompt":"Hello","max_tokens":5,"temperature":0.0,"strategy":"greedy","top_k":1,"top_p":1.0}"#,
        ))
        .unwrap();

    let response = app.oneshot(request).await.unwrap();
    assert!(
        response.status() == StatusCode::OK
            || response.status() == StatusCode::NOT_FOUND
            || response.status() == StatusCode::UNPROCESSABLE_ENTITY
            || response.status() == StatusCode::INTERNAL_SERVER_ERROR,
    );
}

#[tokio::test]
async fn test_batch_tokenize_endpoint() {
    let app = create_test_app_shared();
    let request = Request::builder()
        .method("POST")
        .uri("/v1/batch/tokenize")
        .header("content-type", "application/json")
        .body(Body::from(r#"{"texts":["Hello","World"]}"#))
        .unwrap();

    let response = app.oneshot(request).await.unwrap();
    assert!(
        response.status() == StatusCode::OK
            || response.status() == StatusCode::NOT_FOUND
            || response.status() == StatusCode::UNPROCESSABLE_ENTITY,
    );
}

#[tokio::test]
async fn test_batch_generate_endpoint() {
    let app = create_test_app_shared();
    let request = Request::builder()
        .method("POST")
        .uri("/v1/batch/generate")
        .header("content-type", "application/json")
        .body(Body::from(
            r#"{"prompts":["Hello"],"max_tokens":5,"temperature":0.0,"strategy":"greedy","top_k":1,"top_p":1.0}"#,
        ))
        .unwrap();

    let response = app.oneshot(request).await.unwrap();
    assert!(
        response.status() == StatusCode::OK
            || response.status() == StatusCode::NOT_FOUND
            || response.status() == StatusCode::UNPROCESSABLE_ENTITY
            || response.status() == StatusCode::INTERNAL_SERVER_ERROR,
    );
}

#[tokio::test]
async fn test_gpu_batch_completions_endpoint() {
    let app = create_test_app_shared();
    let request = Request::builder()
        .method("POST")
        .uri("/v1/batch/completions")
        .header("content-type", "application/json")
        .body(Body::from(
            r#"{"prompts":["Hello world"],"max_tokens":10,"temperature":0.0}"#,
        ))
        .unwrap();

    let response = app.oneshot(request).await.unwrap();
    // GPU batch endpoint returns SERVICE_UNAVAILABLE without GPU feature
    assert!(
        response.status() == StatusCode::OK
            || response.status() == StatusCode::SERVICE_UNAVAILABLE
            || response.status() == StatusCode::BAD_REQUEST
            || response.status() == StatusCode::UNPROCESSABLE_ENTITY,
    );
}

#[tokio::test]
async fn test_gpu_batch_completions_empty_prompts() {
    let app = create_test_app_shared();
    let request = Request::builder()
        .method("POST")
        .uri("/v1/batch/completions")
        .header("content-type", "application/json")
        .body(Body::from(r#"{"prompts":[],"max_tokens":10}"#))
        .unwrap();

    let response = app.oneshot(request).await.unwrap();
    assert!(
        response.status() == StatusCode::BAD_REQUEST
            || response.status() == StatusCode::SERVICE_UNAVAILABLE
            || response.status() == StatusCode::UNPROCESSABLE_ENTITY,
    );
}

#[tokio::test]
async fn test_models_endpoint() {
    let app = create_test_app_shared();
    let request = Request::builder()
        .method("GET")
        .uri("/v1/models")
        .body(Body::empty())
        .unwrap();

    let response = app.oneshot(request).await.unwrap();
    assert!(response.status() == StatusCode::OK || response.status() == StatusCode::NOT_FOUND,);
}

#[tokio::test]
async fn test_generate_invalid_json() {
    let app = create_test_app_shared();
    let request = Request::builder()
        .method("POST")
        .uri("/v1/generate")
        .header("content-type", "application/json")
        .body(Body::from("not json"))
        .unwrap();

    let response = app.oneshot(request).await.unwrap();
    // Invalid JSON: server should not return 200 OK
    let status = response.status().as_u16();
    assert!(
        status != 200,
        "Expected non-200 for invalid JSON, got {status}"
    );
}

#[tokio::test]
async fn test_tokenize_invalid_json() {
    let app = create_test_app_shared();
    let request = Request::builder()
        .method("POST")
        .uri("/v1/tokenize")
        .header("content-type", "application/json")
        .body(Body::from("{invalid"))
        .unwrap();

    let response = app.oneshot(request).await.unwrap();
    // Invalid JSON: server should not return 200 OK
    let status = response.status().as_u16();
    assert!(
        status != 200,
        "Expected non-200 for invalid JSON, got {status}"
    );
}

// ============================================================================
// B3: Realize Handlers - ContextWindowConfig
// ============================================================================

#[test]
fn test_context_window_config_new() {
    use crate::api::realize_handlers::ContextWindowConfig;

    let config = ContextWindowConfig::new(4096);
    assert_eq!(config.max_tokens, 4096);
    assert!(config.reserved_output_tokens > 0);
    assert!(config.preserve_system);
}

#[test]
fn test_context_window_config_with_reserved_output() {
    use crate::api::realize_handlers::ContextWindowConfig;

    let config = ContextWindowConfig::new(4096).with_reserved_output(512);
    assert_eq!(config.reserved_output_tokens, 512);
}

#[test]
fn test_context_window_config_available_tokens() {
    use crate::api::realize_handlers::ContextWindowConfig;

    let config = ContextWindowConfig::new(4096).with_reserved_output(1024);
    let available = config.available_tokens();
    assert_eq!(available, 4096 - 1024);
}

#[test]
fn test_context_window_config_available_tokens_overflow_protection() {
    use crate::api::realize_handlers::ContextWindowConfig;

    // reserved > max: should not underflow
    let config = ContextWindowConfig {
        max_tokens: 100,
        reserved_output_tokens: 200,
        preserve_system: true,
    };
    let available = config.available_tokens();
    assert_eq!(available, 0);
}

// ============================================================================
// B3: ContextWindowManager
// ============================================================================

#[test]
fn test_context_window_manager_default() {
    use crate::api::realize_handlers::ContextWindowManager;

    let mgr = ContextWindowManager::default_manager();
    // Default manager should have reasonable defaults
    let _ = mgr; // Ensure it constructs without panic
}

#[test]
fn test_context_window_manager_needs_truncation_empty() {
    use crate::api::realize_handlers::{ContextWindowConfig, ContextWindowManager};

    let config = ContextWindowConfig::new(4096);
    let mgr = ContextWindowManager::new(config);
    let messages: Vec<crate::api::ChatMessage> = vec![];
    assert!(!mgr.needs_truncation(&messages));
}

#[test]
fn test_context_window_manager_estimate_tokens() {
    use crate::api::realize_handlers::{ContextWindowConfig, ContextWindowManager};

    let config = ContextWindowConfig::new(4096);
    let mgr = ContextWindowManager::new(config);
    let messages = vec![crate::api::ChatMessage {
        role: "user".to_string(),
        content: "Hello world".to_string(),
        name: None,
    }];
    let estimate = mgr.estimate_total_tokens(&messages);
    assert!(estimate > 0);
}

#[test]
fn test_context_window_manager_truncate_short_messages() {
    use crate::api::realize_handlers::{ContextWindowConfig, ContextWindowManager};

    let config = ContextWindowConfig::new(4096);
    let mgr = ContextWindowManager::new(config);
    let messages = vec![crate::api::ChatMessage {
        role: "user".to_string(),
        content: "Short".to_string(),
        name: None,
    }];
    let (truncated, was_truncated) = mgr.truncate_messages(&messages);
    assert!(!was_truncated);
    assert_eq!(truncated.len(), 1);
}

// ============================================================================
// B3: format_chat_messages
// ============================================================================

#[test]
fn test_format_chat_messages_empty() {
    use crate::api::realize_handlers::format_chat_messages;

    let messages: Vec<crate::api::ChatMessage> = vec![];
    let result = format_chat_messages(&messages, None);
    assert!(result.is_empty() || result.contains(""));
}

#[test]
fn test_format_chat_messages_single_user() {
    use crate::api::realize_handlers::format_chat_messages;

    let messages = vec![crate::api::ChatMessage {
        role: "user".to_string(),
        content: "Hello!".to_string(),
        name: None,
    }];
    let result = format_chat_messages(&messages, None);
    assert!(result.contains("Hello!"));
}

#[test]
fn test_format_chat_messages_multi_turn() {
    use crate::api::realize_handlers::format_chat_messages;

    let messages = vec![
        crate::api::ChatMessage {
            role: "system".to_string(),
            content: "You are helpful.".to_string(),
            name: None,
        },
        crate::api::ChatMessage {
            role: "user".to_string(),
            content: "Hi".to_string(),
            name: None,
        },
        crate::api::ChatMessage {
            role: "assistant".to_string(),
            content: "Hello!".to_string(),
            name: None,
        },
    ];
    let result = format_chat_messages(&messages, None);
    assert!(result.contains("Hi") || result.contains("Hello!"));
}

#[test]
fn test_format_chat_messages_with_model_name() {
    use crate::api::realize_handlers::format_chat_messages;

    let messages = vec![crate::api::ChatMessage {
        role: "user".to_string(),
        content: "Test".to_string(),
        name: None,
    }];
    let result = format_chat_messages(&messages, Some("llama"));
    assert!(!result.is_empty());
}

// ============================================================================
// B3: clean_chat_output
// ============================================================================

#[test]
fn test_clean_chat_output_no_markers() {
    use crate::api::realize_handlers::clean_chat_output;
    let result = clean_chat_output("Just plain text");
    assert_eq!(result, "Just plain text");
}

#[test]
fn test_clean_chat_output_chatml_markers() {
    use crate::api::realize_handlers::clean_chat_output;
    // clean_chat_output truncates at the earliest stop sequence
    // <|im_start|> is at position 0, so everything is truncated
    let text = "<|im_start|>assistant\nHello there<|im_end|>";
    let result = clean_chat_output(text);
    assert!(result.is_empty() || !result.contains("<|im_start|>"));
}

#[test]
fn test_clean_chat_output_empty() {
    use crate::api::realize_handlers::clean_chat_output;
    let result = clean_chat_output("");
    assert!(result.is_empty());
}

#[test]
fn test_clean_chat_output_partial_markers() {
    use crate::api::realize_handlers::clean_chat_output;
    // Text before <|im_end|> is preserved; <|im_start|> at position 0 truncates all
    let text = "Hello world<|im_end|>extra stuff";
    let result = clean_chat_output(text);
    assert!(result.contains("Hello world"));
    assert!(!result.contains("extra stuff"));
}

// ============================================================================
// B3: HTTP Handler Integration - Realize Endpoints
// ============================================================================

#[tokio::test]
async fn test_realize_embed_endpoint() {
    let app = create_test_app_shared();
    let request = Request::builder()
        .method("POST")
        .uri("/v1/embed")
        .header("content-type", "application/json")
        .body(Body::from(r#"{"input":"Hello world"}"#))
        .unwrap();

    let response = app.oneshot(request).await.unwrap();
    assert!(
        response.status() == StatusCode::OK
            || response.status() == StatusCode::NOT_FOUND
            || response.status() == StatusCode::INTERNAL_SERVER_ERROR,
    );
}

#[tokio::test]
async fn test_realize_model_endpoint() {
    let app = create_test_app_shared();
    let request = Request::builder()
        .method("GET")
        .uri("/v1/model")
        .body(Body::empty())
        .unwrap();

    let response = app.oneshot(request).await.unwrap();
    assert!(response.status() == StatusCode::OK || response.status() == StatusCode::NOT_FOUND,);
}

#[tokio::test]
async fn test_realize_reload_endpoint() {
    let app = create_test_app_shared();
    let request = Request::builder()
        .method("POST")
        .uri("/v1/reload")
        .header("content-type", "application/json")
        .body(Body::from(r#"{"model":"test"}"#))
        .unwrap();

    let response = app.oneshot(request).await.unwrap();
    assert!(
        response.status() == StatusCode::OK
            || response.status() == StatusCode::NOT_FOUND
            || response.status() == StatusCode::UNPROCESSABLE_ENTITY,
    );
}

#[tokio::test]
async fn test_openai_completions_endpoint() {
    let app = create_test_app_shared();
    let request = Request::builder()
        .method("POST")
        .uri("/v1/completions")
        .header("content-type", "application/json")
        .body(Body::from(
            r#"{"model":"test","prompt":"Hello","max_tokens":5}"#,
        ))
        .unwrap();

    let response = app.oneshot(request).await.unwrap();
    assert!(
        response.status() == StatusCode::OK
            || response.status() == StatusCode::NOT_FOUND
            || response.status() == StatusCode::INTERNAL_SERVER_ERROR
            || response.status() == StatusCode::UNPROCESSABLE_ENTITY,
    );
}

#[tokio::test]
async fn test_openai_embeddings_endpoint() {
    let app = create_test_app_shared();
    let request = Request::builder()
        .method("POST")
        .uri("/v1/embeddings")
        .header("content-type", "application/json")
        .body(Body::from(r#"{"input":"Hello"}"#))
        .unwrap();

    let response = app.oneshot(request).await.unwrap();
    assert!(
        response.status() == StatusCode::OK
            || response.status() == StatusCode::NOT_FOUND
            || response.status() == StatusCode::INTERNAL_SERVER_ERROR,
    );
}

// ============================================================================
// B3: OpenAI Handlers
// ============================================================================

#[tokio::test]
async fn test_openai_models_endpoint() {
    let app = create_test_app_shared();
    let request = Request::builder()
        .method("GET")
        .uri("/v1/models")
        .body(Body::empty())
        .unwrap();

    let response = app.oneshot(request).await.unwrap();
    assert!(response.status() == StatusCode::OK || response.status() == StatusCode::NOT_FOUND,);
}

#[tokio::test]
async fn test_openai_chat_completions_endpoint() {
    let app = create_test_app_shared();
    let request = Request::builder()
        .method("POST")
        .uri("/v1/chat/completions")
        .header("content-type", "application/json")
        .body(Body::from(
            r#"{"model":"test","messages":[{"role":"user","content":"Hello"}]}"#,
        ))
        .unwrap();

    let response = app.oneshot(request).await.unwrap();
    assert!(
        response.status() == StatusCode::OK
            || response.status() == StatusCode::NOT_FOUND
            || response.status() == StatusCode::INTERNAL_SERVER_ERROR
            || response.status() == StatusCode::UNPROCESSABLE_ENTITY,
    );
}

#[tokio::test]
async fn test_openai_chat_completions_with_temperature() {
    let app = create_test_app_shared();
    let request = Request::builder()
        .method("POST")
        .uri("/v1/chat/completions")
        .header("content-type", "application/json")
        .body(Body::from(
            r#"{"model":"test","messages":[{"role":"user","content":"Hi"}],"temperature":0.5,"max_tokens":10}"#,
        ))
        .unwrap();

    let response = app.oneshot(request).await.unwrap();
    assert!(
        response.status() == StatusCode::OK
            || response.status() == StatusCode::NOT_FOUND
            || response.status() == StatusCode::INTERNAL_SERVER_ERROR
            || response.status() == StatusCode::UNPROCESSABLE_ENTITY,
    );
}

#[tokio::test]
async fn test_openai_chat_completions_streaming() {
    let app = create_test_app_shared();
    let request = Request::builder()
        .method("POST")
        .uri("/v1/chat/completions")
        .header("content-type", "application/json")
        .body(Body::from(
            r#"{"model":"test","messages":[{"role":"user","content":"Hi"}],"stream":true}"#,
        ))
        .unwrap();

    let response = app.oneshot(request).await.unwrap();
    assert!(
        response.status() == StatusCode::OK
            || response.status() == StatusCode::NOT_FOUND
            || response.status() == StatusCode::INTERNAL_SERVER_ERROR
            || response.status() == StatusCode::UNPROCESSABLE_ENTITY,
    );
}

// ============================================================================
// B3: AppState Accessors
// ============================================================================

#[test]
fn test_appstate_has_quantized_model_demo() {
    let state = AppState::demo_mock().unwrap();
    // Demo mock state typically has no quantized model
    let _ = state.has_quantized_model();
    let _ = state.quantized_model();
}

#[test]
fn test_appstate_has_apr_transformer_demo() {
    let state = AppState::demo_mock().unwrap();
    let _ = state.has_apr_transformer();
    let _ = state.apr_transformer();
}

#[test]
fn test_appstate_verbose() {
    let state = AppState::demo_mock().unwrap();
    assert!(!state.is_verbose());
    let state_verbose = state.with_verbose(true);
    assert!(state_verbose.is_verbose());
}

#[test]
fn test_appstate_demo_creates_valid_state() {
    let state = AppState::demo();
    assert!(state.is_ok());
}

#[test]
fn test_appstate_demo_mock_creates_valid_state() {
    let state = AppState::demo_mock();
    assert!(state.is_ok());
}

// ============================================================================
// B3: build_trace_data
// ============================================================================

#[test]
fn test_build_trace_data_none() {
    let (brick, step, layer) = crate::api::build_trace_data(None, 100, 10, 5, 4);
    assert!(brick.is_none());
    assert!(step.is_none());
    assert!(layer.is_none());
}

#[test]
fn test_build_trace_data_brick() {
    let (brick, step, layer) = crate::api::build_trace_data(Some("brick"), 100, 10, 5, 4);
    assert!(brick.is_some());
    assert!(step.is_none());
    assert!(layer.is_none());
    let b = brick.unwrap();
    assert_eq!(b.level, "brick");
}

#[test]
fn test_build_trace_data_step() {
    let (brick, step, layer) = crate::api::build_trace_data(Some("step"), 200, 10, 5, 4);
    assert!(brick.is_none());
    assert!(step.is_some());
    assert!(layer.is_none());
    let s = step.unwrap();
    assert_eq!(s.level, "step");
}

#[test]
fn test_build_trace_data_layer() {
    let (brick, step, layer) = crate::api::build_trace_data(Some("layer"), 300, 10, 5, 8);
    assert!(brick.is_none());
    assert!(step.is_none());
    assert!(layer.is_some());
    let l = layer.unwrap();
    assert_eq!(l.level, "layer");
}

// ============================================================================
// B3: Request/Response Struct Serde Round-Trips
// ============================================================================

#[test]
fn test_chat_message_serde() {
    let msg = crate::api::ChatMessage {
        role: "user".to_string(),
        content: "Hello".to_string(),
        name: Some("alice".to_string()),
    };
    let json = serde_json::to_string(&msg).unwrap();
    let deserialized: crate::api::ChatMessage = serde_json::from_str(&json).unwrap();
    assert_eq!(deserialized.role, "user");
    assert_eq!(deserialized.content, "Hello");
    assert_eq!(deserialized.name, Some("alice".to_string()));
}

#[test]
fn test_chat_message_without_name() {
    let json = r#"{"role":"assistant","content":"Hi!"}"#;
    let msg: crate::api::ChatMessage = serde_json::from_str(json).unwrap();
    assert_eq!(msg.role, "assistant");
    assert!(msg.name.is_none());
}

#[test]
fn test_error_response_serde() {
    let err = crate::api::ErrorResponse {
        error: "something went wrong".to_string(),
    };
    let json = serde_json::to_string(&err).unwrap();
    let deserialized: crate::api::ErrorResponse = serde_json::from_str(&json).unwrap();
    assert_eq!(deserialized.error, "something went wrong");
}

#[test]
fn test_health_response_serde() {
    let health = crate::api::HealthResponse {
        status: "ok".to_string(),
        version: "0.3.5".to_string(),
        compute_mode: "cpu".to_string(),
    };
    let json = serde_json::to_string(&health).unwrap();
    let deserialized: crate::api::HealthResponse = serde_json::from_str(&json).unwrap();
    assert_eq!(deserialized.status, "ok");
}

#[test]
fn test_generate_request_serde() {
    let req = crate::api::GenerateRequest {
        prompt: "Hello".to_string(),
        max_tokens: 10,
        temperature: 0.5,
        strategy: "greedy".to_string(),
        top_k: 1,
        top_p: 1.0,
        seed: Some(42),
        model_id: None,
    };
    let json = serde_json::to_string(&req).unwrap();
    let deserialized: crate::api::GenerateRequest = serde_json::from_str(&json).unwrap();
    assert_eq!(deserialized.prompt, "Hello");
    assert_eq!(deserialized.seed, Some(42));
}

#[test]
fn test_generate_response_serde() {
    let resp = crate::api::GenerateResponse {
        token_ids: vec![1, 2, 3],
        text: "hello".to_string(),
        num_generated: 3,
    };
    let json = serde_json::to_string(&resp).unwrap();
    let deserialized: crate::api::GenerateResponse = serde_json::from_str(&json).unwrap();
    assert_eq!(deserialized.num_generated, 3);
}

#[test]
fn test_tokenize_request_serde() {
    let req = crate::api::TokenizeRequest {
        text: "Hello world".to_string(),
        model_id: None,
    };
    let json = serde_json::to_string(&req).unwrap();
    let deserialized: crate::api::TokenizeRequest = serde_json::from_str(&json).unwrap();
    assert_eq!(deserialized.text, "Hello world");
}

#[test]
fn test_tokenize_response_serde() {
    let resp = crate::api::TokenizeResponse {
        token_ids: vec![1, 2, 3, 4],
        num_tokens: 4,
    };
    let json = serde_json::to_string(&resp).unwrap();
    let deserialized: crate::api::TokenizeResponse = serde_json::from_str(&json).unwrap();
    assert_eq!(deserialized.num_tokens, 4);
}

#[test]
fn test_usage_serde() {
    let usage = crate::api::Usage {
        prompt_tokens: 10,
        completion_tokens: 5,
        total_tokens: 15,
    };
    let json = serde_json::to_string(&usage).unwrap();
    let deserialized: crate::api::Usage = serde_json::from_str(&json).unwrap();
    assert_eq!(deserialized.total_tokens, 15);
}

// ============================================================================
// B3: Additional endpoint error paths
// ============================================================================

#[tokio::test]
async fn test_health_endpoint() {
    let app = create_test_app_shared();
    let request = Request::builder()
        .method("GET")
        .uri("/health")
        .body(Body::empty())
        .unwrap();

    let response = app.oneshot(request).await.unwrap();
    assert_eq!(response.status(), StatusCode::OK);
}

#[tokio::test]
async fn test_nonexistent_endpoint_404() {
    let app = create_test_app_shared();
    let request = Request::builder()
        .method("GET")
        .uri("/v1/nonexistent")
        .body(Body::empty())
        .unwrap();

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

#[tokio::test]
async fn test_wrong_method_for_endpoint() {
    let app = create_test_app_shared();
    // GET on a POST-only endpoint
    let request = Request::builder()
        .method("GET")
        .uri("/v1/generate")
        .body(Body::empty())
        .unwrap();

    let response = app.oneshot(request).await.unwrap();
    assert!(
        response.status() == StatusCode::METHOD_NOT_ALLOWED
            || response.status() == StatusCode::NOT_FOUND,
    );
}

#[tokio::test]
async fn test_chat_completions_missing_messages() {
    let app = create_test_app_shared();
    let request = Request::builder()
        .method("POST")
        .uri("/v1/chat/completions")
        .header("content-type", "application/json")
        .body(Body::from(r#"{"model":"test"}"#))
        .unwrap();

    let response = app.oneshot(request).await.unwrap();
    assert!(
        response.status() == StatusCode::BAD_REQUEST
            || response.status() == StatusCode::UNPROCESSABLE_ENTITY,
    );
}

#[tokio::test]
async fn test_chat_completions_with_trace_header() {
    let app = create_test_app_shared();
    let request = Request::builder()
        .method("POST")
        .uri("/v1/chat/completions")
        .header("content-type", "application/json")
        .header("x-trace-level", "brick")
        .body(Body::from(
            r#"{"model":"test","messages":[{"role":"user","content":"Hi"}]}"#,
        ))
        .unwrap();

    let response = app.oneshot(request).await.unwrap();
    assert!(
        response.status() == StatusCode::OK
            || response.status() == StatusCode::NOT_FOUND
            || response.status() == StatusCode::INTERNAL_SERVER_ERROR
            || response.status() == StatusCode::UNPROCESSABLE_ENTITY,
    );
}
