//! Auth handshake for the server-side `/crdt-sync` WebSocket.
//!
//! # Extended mutual-auth handshake protocol
//!
//! ```text
//! Connecting peer (client):          Responding node (server):
//!   hello(nonce) ──────────────────► receive hello
//!                ◄──────────────────  server_auth(pubkey, sign("huskies-v1:{nonce}"))
//!   verify server sig + trusted_keys
//!                ◄──────────────────  challenge(server_nonce)
//!   auth(pubkey, sign(server_nonce)) ►
//!                                     verify client sig + trusted_keys
//! ```
//!
//! Both sides verify the peer's pubkey against `trusted_keys`.  A peer whose
//! pubkey is absent from the allow-list is rejected with close code 4002.

#![allow(unused_imports, dead_code)]
use futures::{SinkExt, StreamExt};
use poem::web::websocket::Message as WsMessage;

use crate::crdt_state;
use crate::node_identity;
use crate::slog;

use super::AUTH_TIMEOUT_SECS;
use super::auth::trusted_keys;
use super::wire::{AuthMessage, ChallengeMessage, HelloMessage, ServerAuthMessage};

/// Perform the extended mutual-auth handshake for a freshly-upgraded WebSocket
/// connection.
///
/// **Protocol (server/responding-node side):**
/// 1. Receive `hello` from the connecting peer (contains client nonce).
/// 2. Sign `"huskies-v1:{nonce}"` and send `server_auth` (this node's pubkey +
///    signature) back to the connecting peer.
/// 3. Send a fresh challenge nonce to the connecting peer.
/// 4. Wait up to [`AUTH_TIMEOUT_SECS`] for a signed `auth` reply.
/// 5. Verify the connecting peer's signature and check its pubkey against the
///    trusted-key allow-list.
///
/// Returns `Some(AuthMessage)` on success.  On failure the connection has
/// already been closed with the appropriate close code (`auth_timeout` or
/// `auth_failed`); the caller should simply return.
pub(super) async fn perform_auth_handshake(
    sink: &mut futures::stream::SplitSink<poem::web::websocket::WebSocketStream, WsMessage>,
    stream: &mut futures::stream::SplitStream<poem::web::websocket::WebSocketStream>,
) -> Option<AuthMessage> {
    // ── Step 1: Receive hello from connecting peer ───────────────────
    let hello_result = tokio::time::timeout(
        std::time::Duration::from_secs(AUTH_TIMEOUT_SECS),
        stream.next(),
    )
    .await;

    let hello_text = match hello_result {
        Ok(Some(Ok(WsMessage::Text(text)))) => text,
        Ok(_) | Err(_) => {
            slog!("[crdt-sync] No hello from peer — closing");
            close_with_auth_failed(sink).await;
            return None;
        }
    };

    let hello: HelloMessage = match serde_json::from_str::<HelloMessage>(&hello_text) {
        Ok(m) if m.r#type == "hello" => m,
        _ => {
            slog!("[crdt-sync] Invalid hello message from peer");
            close_with_auth_failed(sink).await;
            return None;
        }
    };

    // ── Step 2: Sign versioned challenge and send server_auth ────────
    let (server_pubkey_hex, server_sig_hex) =
        match crdt_state::sign_versioned_challenge(&hello.nonce) {
            Some(v) => v,
            None => {
                slog!("[crdt-sync] CRDT not initialised — cannot produce server_auth");
                close_with_auth_failed(sink).await;
                return None;
            }
        };

    let server_auth = ServerAuthMessage {
        r#type: "server_auth".to_string(),
        pubkey_hex: server_pubkey_hex,
        signature_hex: server_sig_hex,
    };
    let server_auth_json = serde_json::to_string(&server_auth).ok()?;
    if sink.send(WsMessage::Text(server_auth_json)).await.is_err() {
        return None;
    }

    // ── Step 3: Send challenge nonce to connecting peer ──────────────
    let challenge = node_identity::generate_challenge();
    let challenge_msg = ChallengeMessage {
        r#type: "challenge".to_string(),
        nonce: challenge.clone(),
    };
    let challenge_json = serde_json::to_string(&challenge_msg).ok()?;
    if sink.send(WsMessage::Text(challenge_json)).await.is_err() {
        return None;
    }

    // ── Step 4: Await signed auth reply from connecting peer ─────────
    let auth_result = tokio::time::timeout(
        std::time::Duration::from_secs(AUTH_TIMEOUT_SECS),
        stream.next(),
    )
    .await;

    let auth_text = match auth_result {
        Ok(Some(Ok(WsMessage::Text(text)))) => text,
        Ok(_) | Err(_) => {
            slog!("[crdt-sync] Auth timeout or connection lost during handshake");
            let _ = sink
                .send(WsMessage::Close(Some((
                    poem::web::websocket::CloseCode::from(4001),
                    "auth_timeout".to_string(),
                ))))
                .await;
            let _ = sink.close().await;
            return None;
        }
    };

    let auth_msg: AuthMessage = match serde_json::from_str(&auth_text) {
        Ok(m) => m,
        Err(_) => {
            slog!("[crdt-sync] Invalid auth message from peer");
            close_with_auth_failed(sink).await;
            return None;
        }
    };

    // ── Step 5: Verify signature and check trusted-key allow-list ────
    let key_trusted = trusted_keys().iter().any(|k| k == &auth_msg.pubkey_hex);
    if !key_trusted {
        slog!(
            "[crdt-sync] Auth rejected: peer pubkey not in trusted_keys: {}",
            auth_msg.pubkey_hex
        );
        close_with_auth_failed(sink).await;
        return None;
    }

    let sig_valid =
        node_identity::verify_challenge(&auth_msg.pubkey_hex, &challenge, &auth_msg.signature_hex);
    if !sig_valid {
        slog!(
            "[crdt-sync] Auth rejected: invalid signature from peer {:.12}…",
            &auth_msg.pubkey_hex
        );
        close_with_auth_failed(sink).await;
        return None;
    }

    slog!(
        "[crdt-sync] Peer authenticated: {:.12}…",
        &auth_msg.pubkey_hex
    );

    Some(auth_msg)
}

/// Close the WebSocket with a generic `auth_failed` reason.
///
/// The close reason is intentionally the same for all auth failures
/// (bad signature, untrusted key, malformed message) to avoid leaking
/// which check failed.
async fn close_with_auth_failed(
    sink: &mut futures::stream::SplitSink<poem::web::websocket::WebSocketStream, WsMessage>,
) {
    let _ = sink
        .send(WsMessage::Close(Some((
            poem::web::websocket::CloseCode::from(4002),
            "auth_failed".to_string(),
        ))))
        .await;
    let _ = sink.close().await;
}

#[cfg(test)]
mod tests;
