//! v2 CLI command implementations — thin wrappers over the SDK.

use quicproquo_sdk::client::QpqClient;
use quicproquo_sdk::error::SdkError;

/// Register a new user account via OPAQUE.
pub async fn cmd_register_user(
    client: &mut QpqClient,
    username: &str,
    password: &str,
) -> Result<(), SdkError> {
    client.register(username, password).await?;
    let key = client.identity_key().unwrap_or_default();
    println!("registered user: {username}");
    println!("identity key  : {}", hex::encode(key));
    Ok(())
}

/// Log in via OPAQUE and print session info.
pub async fn cmd_login(
    client: &mut QpqClient,
    username: &str,
    password: &str,
) -> Result<(), SdkError> {
    client.login(username, password).await?;
    println!("logged in as: {username}");
    if let Some(key) = client.identity_key() {
        println!("identity key: {}", hex::encode(key));
    }
    Ok(())
}

/// Print local identity information.
pub fn cmd_whoami(client: &QpqClient) {
    match client.username() {
        Some(u) => println!("username    : {u}"),
        None => println!("username    : (not logged in)"),
    }
    match client.identity_key() {
        Some(k) => println!("identity key: {}", hex::encode(k)),
        None => println!("identity key: (none)"),
    }
    println!("connected   : {}", client.is_connected());
    println!("authenticated: {}", client.is_authenticated());
}

/// Health check — connect to the server and report status.
pub async fn cmd_health(client: &mut QpqClient) -> Result<(), SdkError> {
    let start = std::time::Instant::now();
    // The SDK connect() already establishes a QUIC connection.
    // If we're already connected, just report success.
    if !client.is_connected() {
        client.connect().await?;
    }
    let rtt_ms = start.elapsed().as_millis();
    println!("status : ok");
    println!("rtt    : {rtt_ms}ms");
    Ok(())
}

/// Resolve a username to its identity key.
pub async fn cmd_resolve(client: &mut QpqClient, username: &str) -> Result<(), SdkError> {
    let rpc = client.rpc()?;
    match quicproquo_sdk::users::resolve_user(rpc, username).await? {
        Some(key) => {
            println!("{username} -> {}", hex::encode(&key));
        }
        None => {
            println!("{username}: not found");
        }
    }
    Ok(())
}

/// List registered devices.
pub async fn cmd_devices_list(client: &mut QpqClient) -> Result<(), SdkError> {
    let rpc = client.rpc()?;
    let devices = quicproquo_sdk::devices::list_devices(rpc).await?;
    if devices.is_empty() {
        println!("no devices registered");
    } else {
        println!("{:<36}  {:<20}  {}", "DEVICE ID", "NAME", "REGISTERED AT");
        for d in &devices {
            println!(
                "{:<36}  {:<20}  {}",
                hex::encode(&d.device_id),
                d.device_name,
                d.registered_at,
            );
        }
    }
    Ok(())
}

/// Register a new device.
pub async fn cmd_devices_register(
    client: &mut QpqClient,
    device_id: &str,
    device_name: &str,
) -> Result<(), SdkError> {
    let rpc = client.rpc()?;
    let id_bytes = hex::decode(device_id)
        .map_err(|e| SdkError::Other(anyhow::anyhow!("invalid device_id hex: {e}")))?;
    let was_new = quicproquo_sdk::devices::register_device(rpc, &id_bytes, device_name).await?;
    if was_new {
        println!("device registered: {device_name}");
    } else {
        println!("device already registered: {device_name}");
    }
    Ok(())
}

/// Revoke a device.
pub async fn cmd_devices_revoke(
    client: &mut QpqClient,
    device_id: &str,
) -> Result<(), SdkError> {
    let rpc = client.rpc()?;
    let id_bytes = hex::decode(device_id)
        .map_err(|e| SdkError::Other(anyhow::anyhow!("invalid device_id hex: {e}")))?;
    let revoked = quicproquo_sdk::devices::revoke_device(rpc, &id_bytes).await?;
    if revoked {
        println!("device revoked: {device_id}");
    } else {
        println!("device not found: {device_id}");
    }
    Ok(())
}

/// Set up account recovery — generate codes and upload encrypted bundles.
pub async fn cmd_recovery_setup(client: &mut QpqClient) -> Result<(), SdkError> {
    // Load identity seed from state file.
    let state_path = client.config_state_path();
    let stored = quicproquo_sdk::state::load_state(&state_path, None)
        .map_err(|e| SdkError::Crypto(format!("load identity for recovery: {e}")))?;

    let rpc = client.rpc()?;
    let codes =
        quicproquo_sdk::recovery::setup_recovery(rpc, &stored.identity_seed, &[]).await?;

    println!("=== RECOVERY CODES ===");
    println!("Save these codes securely. They will NOT be shown again.");
    println!("Each code can independently recover your account.");
    println!();
    for (i, code) in codes.iter().enumerate() {
        println!("  {}. {}", i + 1, code);
    }
    println!();
    println!("{} codes generated and uploaded.", codes.len());
    Ok(())
}

// ── Outbox commands ──────────────────────────────────────────────────────────

/// List pending outbox entries.
pub fn cmd_outbox_list(client: &QpqClient) -> Result<(), SdkError> {
    let store = client.conversations()?;
    let entries = quicproquo_sdk::outbox::list_pending(store)?;
    if entries.is_empty() {
        println!("outbox is empty — no pending messages");
    } else {
        println!("{:<6}  {:<34}  {:<8}  PAYLOAD SIZE", "ID", "CONVERSATION", "RETRIES");
        for e in &entries {
            println!(
                "{:<6}  {:<34}  {:<8}  {} bytes",
                e.id,
                e.conversation_id.hex(),
                e.retry_count,
                e.payload.len(),
            );
        }
        println!("\n{} pending entries", entries.len());
    }
    Ok(())
}

/// Retry sending all pending outbox entries.
pub async fn cmd_outbox_retry(client: &mut QpqClient) -> Result<(), SdkError> {
    let rpc = client.rpc()?;
    let store = client.conversations()?;
    let (sent, failed) = quicproquo_sdk::outbox::flush_outbox(rpc, store).await?;
    println!("outbox flush: {sent} sent, {failed} permanently failed");
    Ok(())
}

/// Clear permanently failed outbox entries.
pub fn cmd_outbox_clear(client: &QpqClient) -> Result<(), SdkError> {
    let store = client.conversations()?;
    let cleared = quicproquo_sdk::outbox::clear_failed(store)?;
    println!("cleared {cleared} failed outbox entries");
    Ok(())
}

/// Recover an account from a recovery code.
pub async fn cmd_recovery_restore(
    client: &mut QpqClient,
    code: &str,
) -> Result<(), SdkError> {
    let rpc = client.rpc()?;
    let (identity_seed, conversation_ids) =
        quicproquo_sdk::recovery::recover_account(rpc, code).await?;

    // Restore identity.
    let keypair = quicproquo_core::IdentityKeypair::from_seed(identity_seed);
    client.set_identity_key(keypair.public_key_bytes().to_vec());

    println!("account recovered successfully");
    println!("identity key: {}", hex::encode(keypair.public_key_bytes()));
    if !conversation_ids.is_empty() {
        println!(
            "{} conversations need rejoin (peers must re-invite this device)",
            conversation_ids.len()
        );
    }

    // Save recovered state.
    let state = quicproquo_sdk::state::StoredState {
        identity_seed,
        group: None,
        hybrid_key: None,
        member_keys: Vec::new(),
    };
    let state_path = client.config_state_path();
    quicproquo_sdk::state::save_state(&state_path, &state, None)?;
    println!("state saved to {}", state_path.display());

    Ok(())
}
