//! Auth handshake protocol for QUIC connections.
//!
//! The session token is sent as a "connection init" message on the first
//! bi-stream before any RPC calls.
//!
//! ## Protocol
//! ```text
//! Client → Server: [0x01 magic][token_len: u16 BE][token bytes]
//! Server → Client: [0x01 magic][0x00 status OK]
//! ```

use crate::error::RpcError;

/// Magic byte identifying an auth init frame.
pub const AUTH_INIT_MAGIC: u8 = 0x01;

/// Status byte: auth accepted.
const AUTH_STATUS_OK: u8 = 0x00;

/// Maximum token length (64 KiB).
const MAX_TOKEN_LEN: usize = 65535;

/// Write an auth init frame to a QUIC send stream.
pub async fn send_auth_init(
    send: &mut quinn::SendStream,
    token: &[u8],
) -> Result<(), RpcError> {
    if token.len() > MAX_TOKEN_LEN {
        return Err(RpcError::Encode(format!(
            "auth token too large: {} bytes (max {MAX_TOKEN_LEN})",
            token.len()
        )));
    }

    let token_len = token.len() as u16;
    let mut buf = Vec::with_capacity(1 + 2 + token.len());
    buf.push(AUTH_INIT_MAGIC);
    buf.extend_from_slice(&token_len.to_be_bytes());
    buf.extend_from_slice(token);

    send.write_all(&buf)
        .await
        .map_err(|e| RpcError::Connection(format!("send auth init: {e}")))?;

    Ok(())
}

/// Read an auth init frame from a QUIC recv stream.
pub async fn recv_auth_init(
    recv: &mut quinn::RecvStream,
) -> Result<Vec<u8>, RpcError> {
    // Read magic byte.
    let mut header = [0u8; 3];
    recv.read_exact(&mut header)
        .await
        .map_err(|e| RpcError::Connection(format!("read auth init header: {e}")))?;

    if header[0] != AUTH_INIT_MAGIC {
        return Err(RpcError::Decode(format!(
            "bad auth init magic: expected 0x{AUTH_INIT_MAGIC:02x}, got 0x{:02x}",
            header[0]
        )));
    }

    let token_len = u16::from_be_bytes([header[1], header[2]]) as usize;
    if token_len > MAX_TOKEN_LEN {
        return Err(RpcError::Decode(format!(
            "auth token length {token_len} exceeds max {MAX_TOKEN_LEN}"
        )));
    }

    let mut token = vec![0u8; token_len];
    if token_len > 0 {
        recv.read_exact(&mut token)
            .await
            .map_err(|e| RpcError::Connection(format!("read auth token: {e}")))?;
    }

    Ok(token)
}

/// Send auth ack (success response).
pub async fn send_auth_ack(
    send: &mut quinn::SendStream,
) -> Result<(), RpcError> {
    let buf = [AUTH_INIT_MAGIC, AUTH_STATUS_OK];
    send.write_all(&buf)
        .await
        .map_err(|e| RpcError::Connection(format!("send auth ack: {e}")))?;
    Ok(())
}

/// Read auth ack from the server.
pub async fn recv_auth_ack(
    recv: &mut quinn::RecvStream,
) -> Result<(), RpcError> {
    let mut buf = [0u8; 2];
    recv.read_exact(&mut buf)
        .await
        .map_err(|e| RpcError::Connection(format!("read auth ack: {e}")))?;

    if buf[0] != AUTH_INIT_MAGIC {
        return Err(RpcError::Decode(format!(
            "bad auth ack magic: expected 0x{AUTH_INIT_MAGIC:02x}, got 0x{:02x}",
            buf[0]
        )));
    }

    if buf[1] != AUTH_STATUS_OK {
        return Err(RpcError::Decode(format!(
            "auth rejected with status 0x{:02x}",
            buf[1]
        )));
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn auth_init_magic_is_0x01() {
        assert_eq!(AUTH_INIT_MAGIC, 0x01);
    }

    #[test]
    fn token_too_large_returns_encode_error() {
        // We cannot call send_auth_init without a real stream, but we can
        // verify the length check logic by constructing the guard condition.
        let big_token = vec![0u8; MAX_TOKEN_LEN + 1];
        assert!(big_token.len() > MAX_TOKEN_LEN);
    }
}
