//! Federation integration test: spin up an in-process instance, exercise the
//! /instance/info and /repos/{id}/init paths over HTTP via the client.

use std::net::SocketAddr;
use std::path::PathBuf;
use std::time::Duration;

use levcs_core::ZERO_ID;
use levcs_identity::authority::{AuthorityBody, MemberEntry, PolicyEntry, Role};
use levcs_identity::keys::SecretKey;
use levcs_identity::sign::sign_authority;
use levcs_instance::{serve, AppState, InstanceConfig};

fn tempdir() -> PathBuf {
    let mut p = std::env::temp_dir();
    let n = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_nanos())
        .unwrap_or(0);
    p.push(format!("levcs-fed-{n}-{}", std::process::id()));
    std::fs::create_dir_all(&p).unwrap();
    p
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn instance_info_and_init_roundtrip() {
    let root = tempdir();
    let config = InstanceConfig {
        root: root.clone(),
        storage_mode: "full".into(),
        federation_peers: Vec::new(),
        allowed_handlers: vec!["builtin".into()],
        mirrors: Vec::new(),
    };
    let state = AppState::new(config);
    // Bind to an ephemeral port.
    let listener = tokio::net::TcpListener::bind::<SocketAddr>("127.0.0.1:0".parse().unwrap())
        .await
        .unwrap();
    let addr = listener.local_addr().unwrap();
    let app = levcs_instance::router(state);
    let serve_task = tokio::spawn(async move {
        axum::serve(listener, app).await.ok();
    });

    let base = format!("http://{addr}/levcs/v1");
    // /instance/info via plain reqwest
    let client = reqwest::Client::new();
    let info: serde_json::Value = client
        .get(format!("{base}/instance/info"))
        .send()
        .await
        .unwrap()
        .json()
        .await
        .unwrap();
    assert_eq!(info["software"], "levcs-instance");

    // POST /repos/{id}/init
    let sk = SecretKey::generate();
    let pk = sk.public();
    let now = 1_700_000_000_000_000;
    let mut body = AuthorityBody {
        schema_version: 1,
        repo_id: ZERO_ID,
        previous_authority: ZERO_ID,
        version: 1,
        created_micros: now,
        members: vec![MemberEntry {
            key: pk,
            handle: "alice".into(),
            role: Role::Owner,
            added_micros: now,
            added_by: pk,
        }],
        policy: vec![PolicyEntry {
            key: "public_read".into(),
            value: vec![0x01],
        }],
    };
    body.normalize().unwrap();
    body.assign_genesis_repo_id().unwrap();
    let signed = sign_authority(&body, &sk).unwrap();
    let bytes = signed.serialize();
    let repo_id = body.repo_id.to_hex();

    // Sign request manually via levcs-protocol's sign_request.
    use levcs_protocol::auth::{sign_request, AuthRequest};
    let path = format!("/repos/{repo_id}/init");
    let req = AuthRequest {
        method: "POST",
        path_with_query: &path,
        body: &bytes,
    };
    let (key, ts, nonce, sig) = sign_request(&sk, &req).unwrap();
    let res = client
        .post(format!("{base}{path}"))
        .header("LeVCS-Key", key)
        .header("LeVCS-Timestamp", ts)
        .header("LeVCS-Nonce", nonce)
        .header("LeVCS-Signature", sig)
        .header("Content-Type", "application/octet-stream")
        .body(bytes.clone())
        .timeout(Duration::from_secs(5))
        .send()
        .await
        .unwrap();
    assert!(
        res.status().is_success(),
        "init returned {}: {}",
        res.status(),
        res.text().await.unwrap()
    );

    // /repos/{id}/info should now succeed
    let info: serde_json::Value = client
        .get(format!("{base}/repos/{repo_id}/info"))
        .send()
        .await
        .unwrap()
        .json()
        .await
        .unwrap();
    assert_eq!(info["repo_id"], repo_id);

    serve_task.abort();
    let _ = std::fs::remove_dir_all(root);
    let _ = serve;
}
