//! Integration test for the slice-1 REST surface (spec §4.1, §6.4).
//!
//! Spins up the axum app on an ephemeral port, then exercises the API
//! contract: POST /v1/sessions → JSON { session_id }; GET / serves a
//! text/html page. We do NOT exercise the WebRTC handshake here (that
//! needs a real peer); the manual e2e plan in README.md covers it.

use axum::body::Body;
use axum::http::{Request, StatusCode};
use rutster::media_thread::{MediaThread, MediaThreadOpts};
use rutster::session_map::AppState;
use tower::ServiceExt; // enables `oneshot` on the Router for sync tests

fn default_tap_url() -> url::Url {
    url::Url::parse("ws://127.0.0.1:8081/echo").unwrap()
}

/// Build an `AppState` whose command channel terminates in a real
/// `MediaThread`. The rewired slice-4 routes require a running media thread
/// to create sessions; the integration test sets one up exactly like
/// `main.rs` does.
fn app_state_with_thread() -> (AppState, MediaThread) {
    let default = default_tap_url();
    let (placeholder_tx, _placeholder_rx) = tokio::sync::mpsc::channel(1);
    let mut state = AppState::new(placeholder_tx, default.clone());
    let media_thread = MediaThread::spawn(
        default,
        MediaThreadOpts::default(),
        tokio::runtime::Handle::current(),
    )
    .expect("media thread spawn");
    state.cmd_tx = media_thread.cmd_tx();
    (state, media_thread)
}

#[tokio::test]
async fn post_v1_sessions_returns_a_session_id() {
    let (state, media_thread) = app_state_with_thread();
    let app = rutster::routes::router(state);
    let resp = app
        .oneshot(
            Request::builder()
                .method("POST")
                .uri("/v1/sessions")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();

    assert_eq!(resp.status(), StatusCode::OK);
    let body = axum::body::to_bytes(resp.into_body(), 1024).await.unwrap();
    let v: serde_json::Value = serde_json::from_slice(&body).unwrap();
    assert!(v["session_id"].is_string(), "response has session_id");
    assert_eq!(v["session_id"].as_str().unwrap().len(), 36); // UUID v4
    tokio::task::spawn_blocking(move || media_thread.shutdown())
        .await
        .unwrap();
}

#[tokio::test]
async fn create_session_returns_503_when_node_full() {
    use rutster::media_thread::MediaThreadOpts;
    let state = rutster::session_map::AppState::default();
    let opts = MediaThreadOpts {
        max_sessions: 0, // a node that can never admit — the LB-shed path
        ..Default::default()
    };
    let (state, _thread) = state
        .spawn_media_thread_with(opts, tokio::runtime::Handle::current())
        .expect("spawn");
    let app = rutster::routes::router(state);
    let resp = app
        .oneshot(
            Request::builder()
                .method("POST")
                .uri("/v1/sessions")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();
    assert_eq!(resp.status(), StatusCode::SERVICE_UNAVAILABLE);
}

#[tokio::test]
async fn get_root_serves_html() {
    let (state, media_thread) = app_state_with_thread();
    let app = rutster::routes::router(state);
    let resp = app
        .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
        .await
        .unwrap();

    assert_eq!(resp.status(), StatusCode::OK);
    assert_eq!(
        resp.headers()
            .get("content-type")
            .map(|v| v.to_str().unwrap()),
        Some("text/html; charset=utf-8")
    );
    tokio::task::spawn_blocking(move || media_thread.shutdown())
        .await
        .unwrap();
}

#[tokio::test]
async fn readyz_200_with_stats_json_when_thread_alive() {
    let state = rutster::session_map::AppState::default();
    let (state, media_thread) = state
        .spawn_media_thread(tokio::runtime::Handle::current())
        .expect("spawn");
    let app = rutster::routes::router(state);
    let resp = app
        .oneshot(
            Request::builder()
                .uri("/readyz")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();
    assert_eq!(resp.status(), StatusCode::OK);
    let body = axum::body::to_bytes(resp.into_body(), 64 * 1024)
        .await
        .unwrap();
    let v: serde_json::Value = serde_json::from_slice(&body).unwrap();
    assert_eq!(v["draining"], false);
    assert!(v["max_sessions"].as_u64().unwrap() > 0);
    tokio::task::spawn_blocking(move || media_thread.shutdown())
        .await
        .unwrap();
}
