//! Reliable TCP connectivity logic.
//!
//! Implements the client-side TCP connection handling, including framing,
//! serialization, and the initial authentication handshake.

use anyhow::{Context, Result};
use core_protocol::constants;
use core_protocol::tcp_events::TcpEvent;
use futures::{SinkExt, StreamExt};
use tokio::net::TcpStream;
use tokio_serde::SymmetricallyFramed;
use tokio_serde::formats::Bincode;
use tokio_util::codec::{Framed, LengthDelimitedCodec};
use tracing::{info, warn};

/// A type alias for the symmetrically framed TCP stream to simplify syntax.
type FramedStream = SymmetricallyFramed<
    Framed<TcpStream, LengthDelimitedCodec>,
    TcpEvent,
    Bincode<TcpEvent, TcpEvent>,
>;

/// Connects to the server and performs the initial authentication handshake.
///
/// This establishes a length-delimited TCP connection to prevent fragmentation,
/// sends an `AuthRequest` with the given username, and awaits the `AuthResponse`.
///
/// # Arguments
/// * `username` - The requested display name for this client.
///
/// # Errors
/// Returns an `anyhow::Result` if the TCP socket cannot connect, if the serialization/
/// deserialization fails, or if the server closes the connection prematurely.
pub async fn connect_and_auth(username: &str) -> Result<()> {
    let addr = format!("127.0.0.1:{}", constants::TCP_PORT);
    info!("Connecting to server at {}...", addr);

    let stream = TcpStream::connect(&addr)
        .await
        .context("Failed to connect to server")?;
    info!("Connected!");

    // Construct the codec pipeline exactly mirroring the server's configuration
    // to ensure reliable packet framing.
    let length_delimited = Framed::new(stream, LengthDelimitedCodec::new());
    let mut framed: FramedStream =
        SymmetricallyFramed::new(length_delimited, Bincode::<TcpEvent, TcpEvent>::default());

    let auth_req = TcpEvent::AuthRequest {
        username: username.to_string(),
    };

    framed
        .send(auth_req)
        .await
        .context("Failed to send AuthRequest")?;
    info!("Sent AuthRequest for user: {}", username);

    if let Some(response) = framed.next().await {
        let response = response.context("Failed to deserialize response")?;

        match response {
            TcpEvent::AuthResponse { session_token } => {
                info!(
                    "Successfully authenticated! Session token: {}",
                    session_token
                );
            }
            _ => {
                warn!("Received unexpected event instead of AuthResponse");
            }
        }
    } else {
        warn!("Connection closed before receiving AuthResponse");
    }

    Ok(())
}
