//! End-to-end smoke test for the HttpProxyRequest fallback.
//!
//! Mirrors what a logged-in client does when `OPTION_USE_RAW_TCP_FOR_API=Y`:
//!   1. Open TCP to hbbs's rendezvous port.
//!   2. Read the server-initiated `KeyExchange`.
//!   3. Verify the signature with the server's published Ed25519 pubkey.
//!   4. Reply with `KeyExchange { keys: [client_box_pk, sealed_sym_key] }`.
//!   5. Send `HttpProxyRequest { method, path, headers, body }`.
//!   6. Receive `HttpProxyResponse` and print status + body.
//!
//! Run from the same dir as hbbs's `id_ed25519.pub`:
//!   cargo run --example http_proxy_test -- 127.0.0.1:21116

use hbb_common::bytes::Bytes;
use hbb_common::protobuf::Message as _;
use hbb_common::rendezvous_proto::{
    rendezvous_message, HttpProxyRequest, KeyExchange, RendezvousMessage,
};
use hbb_common::tcp::FramedStream;
use hbb_common::tokio;
use sodiumoxide::crypto::{box_, secretbox, sign};

#[tokio::main(flavor = "current_thread")]
async fn main() {
    let addr_arg = std::env::args().nth(1).unwrap_or_else(|| "127.0.0.1:21116".into());
    let pubkey_path = std::env::args()
        .nth(2)
        .unwrap_or_else(|| "id_ed25519.pub".into());

    // 1. Connect.
    let addr: std::net::SocketAddr = addr_arg.parse().expect("bad addr");
    let raw = tokio::net::TcpStream::connect(addr).await.expect("connect");
    let mut fs = FramedStream::from(raw, addr);

    // 2. Read the server-pushed KeyExchange.
    let bytes = fs
        .next()
        .await
        .expect("server closed")
        .expect("read err");
    let msg = RendezvousMessage::parse_from_bytes(&bytes).expect("parse first frame");
    let kx_in = match msg.union {
        Some(rendezvous_message::Union::KeyExchange(ex)) => ex,
        other => panic!(
            "expected KeyExchange as first frame, got {:?}",
            other.map(|_| "<some other variant>")
        ),
    };
    assert_eq!(
        kx_in.keys.len(),
        1,
        "server KX must carry exactly one signed pubkey"
    );

    // 3. Verify the signature.
    let pk_b64 = std::fs::read_to_string(&pubkey_path)
        .expect("read pubkey")
        .trim()
        .to_string();
    let pk_bytes = base64::decode(&pk_b64).expect("base64 pubkey");
    assert_eq!(pk_bytes.len(), 32, "Ed25519 pubkey must be 32 bytes");
    let rs_pk = sign::PublicKey::from_slice(&pk_bytes).expect("pubkey");
    let their_box_pk_bytes =
        sign::verify(&kx_in.keys[0], &rs_pk).expect("KX signature mismatch");
    assert_eq!(their_box_pk_bytes.len(), 32, "box pk must be 32 bytes");
    let their_box_pk =
        box_::PublicKey::from_slice(&their_box_pk_bytes).expect("box pk shape");

    // 4. Generate ephemeral keypair + sym key, seal the sym key with NaCl box,
    //    send back KX.
    let (our_box_pk, our_box_sk) = box_::gen_keypair();
    let sym_key = secretbox::gen_key();
    let nonce = box_::Nonce([0u8; 24]);
    let sealed = box_::seal(&sym_key.0, &nonce, &their_box_pk, &our_box_sk);

    let mut out = RendezvousMessage::new();
    out.set_key_exchange(KeyExchange {
        keys: vec![Bytes::from(our_box_pk.0.to_vec()), Bytes::from(sealed)],
        ..Default::default()
    });
    fs.send(&out).await.expect("send KX");
    fs.set_key(sym_key);
    println!("[ok] secure_tcp handshake complete");

    // 5. HttpProxyRequest — exercise an unauthenticated route first.
    let mut req_msg = RendezvousMessage::new();
    req_msg.set_http_proxy_request(HttpProxyRequest {
        method: "GET".into(),
        path: "/api/login-options".into(),
        headers: vec![],
        body: Bytes::new(),
        ..Default::default()
    });
    fs.send(&req_msg).await.expect("send HttpProxyRequest");
    println!("[ok] sent HttpProxyRequest GET /api/login-options");

    // 6. Receive HttpProxyResponse.
    let bytes = fs
        .next()
        .await
        .expect("server closed mid-response")
        .expect("read err");
    let resp_msg =
        RendezvousMessage::parse_from_bytes(&bytes).expect("parse response");
    match resp_msg.union {
        Some(rendezvous_message::Union::HttpProxyResponse(r)) => {
            println!("[ok] response status = {}", r.status);
            println!(
                "[ok] response body   = {}",
                std::str::from_utf8(&r.body).unwrap_or("<non-utf8>")
            );
            for h in &r.headers {
                println!("       {}: {}", h.name, h.value);
            }
            assert_eq!(r.status, 200, "expected HTTP 200 from /api/login-options");
            assert!(
                std::str::from_utf8(&r.body)
                    .map(|s| s.contains("account"))
                    .unwrap_or(false),
                "body should mention `account`"
            );
            println!("[pass] full HTTP-over-rendezvous round trip verified");
        }
        other => panic!("expected HttpProxyResponse, got {:?}", other.is_some()),
    }
}
