use std::io::{self, BufRead, Write};
use std::net::SocketAddr;
use std::path::Path;

use std::sync::Arc;

use itsgoin_core::node::Node;
use itsgoin_core::types::DeviceProfile;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| "info,iroh=warn,swarm_discovery=warn".parse().unwrap()),
        )
        .init();

    let args: Vec<String> = std::env::args().collect();

    // Parse flags before anything else
    let mut data_dir = String::from("./itsgoin-data");
    let mut import_key: Option<String> = None;
    let mut bind_addr: Option<SocketAddr> = None;
    let mut daemon = false;
    let mut mobile = false;
    let mut web_port: Option<u16> = None;
    let mut i = 1;
    while i < args.len() {
        match args[i].as_str() {
            "--import-key" => {
                if i + 1 >= args.len() {
                    eprintln!("Error: --import-key requires a 64-char hex key argument");
                    std::process::exit(1);
                }
                import_key = Some(args[i + 1].clone());
                i += 2;
            }
            "--bind" => {
                if i + 1 >= args.len() {
                    eprintln!("Error: --bind requires an address argument (e.g. 0.0.0.0:4433)");
                    std::process::exit(1);
                }
                bind_addr = Some(args[i + 1].parse().unwrap_or_else(|e| {
                    eprintln!("Error: invalid bind address '{}': {}", args[i + 1], e);
                    std::process::exit(1);
                }));
                i += 2;
            }
            "--daemon" => {
                daemon = true;
                i += 1;
            }
            "--mobile" => {
                mobile = true;
                i += 1;
            }
            "--web" => {
                if i + 1 >= args.len() {
                    eprintln!("Error: --web requires a port argument (e.g. --web 8080)");
                    std::process::exit(1);
                }
                web_port = Some(args[i + 1].parse().unwrap_or_else(|e| {
                    eprintln!("Error: invalid web port '{}': {}", args[i + 1], e);
                    std::process::exit(1);
                }));
                i += 2;
            }
            _ => {
                if data_dir == "./itsgoin-data" {
                    data_dir = args[i].clone();
                }
                i += 1;
            }
        }
    }

    // Handle --import-key before opening the node
    if let Some(hex_key) = import_key {
        println!("Importing identity key into {}...", data_dir);
        match Node::import_identity(Path::new(&data_dir), &hex_key) {
            Ok(()) => println!("Identity key imported successfully."),
            Err(e) => {
                eprintln!("Error importing key: {}", e);
                std::process::exit(1);
            }
        }
    }

    let profile = if mobile { DeviceProfile::Mobile } else { DeviceProfile::Desktop };
    println!("Starting ItsGoin node (data: {}, profile: {:?})...", data_dir, profile);
    let node = Arc::new(Node::open_with_bind(&data_dir, bind_addr, profile).await?);

    // Wait briefly for address resolution
    tokio::time::sleep(std::time::Duration::from_secs(1)).await;

    let node_id_hex = hex::encode(node.node_id);
    let addr = node.endpoint_addr();
    let sockets: Vec<_> = addr.ip_addrs().collect();

    // Show our display name if set
    let my_name = node.get_display_name(&node.node_id).await.unwrap_or(None);
    let name_display = my_name.as_deref().unwrap_or("(not set)");

    println!();
    println!("========================================");
    println!("  ItsGoin node running");
    println!("  Name:    {}", name_display);
    println!("  Node ID: {}", node_id_hex);
    for sock in &sockets {
        println!("  Listen:  {}", sock);
    }
    println!("========================================");
    // Print a connect string others can paste
    if let Some(sock) = sockets.first() {
        println!("  Share this to connect:");
        println!("  connect {}@{}", node_id_hex, sock);
    }
    println!();
    println!("Commands:");
    println!("  post <text>                      Create a public post");
    println!("  post-to <target> <text>          Encrypted post (circle name, node_id, or 'friends')");
    println!("  feed                             Show your feed");
    println!("  posts                            Show all known posts");
    println!("  follow <node_id>                 Follow a node");
    println!("  unfollow <node_id>               Unfollow a node");
    println!("  follows                          List followed nodes");
    println!("  connect <id>@<ip:port>           Connect to a peer and sync");
    println!("  connect <node_id>                Connect via address resolution");
    println!("  sync                             Sync with all known peers");
    println!("  peers                            List known peers");
    println!("  circles                          List circles");
    println!("  create-circle <name>             Create a circle");
    println!("  delete-circle <name>             Delete a circle");
    println!("  add-to-circle <circle> <node_id> Add member to circle");
    println!("  remove-from-circle <circle> <id> Remove member from circle");
    println!("  delete <post_id_hex>             Delete one of your posts");
    println!("  revoke <id> <node_id> [mode]     Revoke access (mode: sync|reencrypt)");
    println!("  revoke-circle <circle> <nid> [m] Revoke circle access for a node");
    println!("  redundancy                       Show replica counts for your posts");
    println!("  audience                         List audience members");
    println!("  audience-request <node_id>      Request to join peer's audience");
    println!("  audience-pending                 Show pending audience requests");
    println!("  audience-approve <node_id>       Approve audience request");
    println!("  audience-remove <node_id>        Remove from audience");
    println!("  worm <node_id>                   Worm lookup (find peer beyond 3-hop map)");
    println!("  connections                       Show mesh connections");
    println!("  social-routes                    Show social routing cache");
    println!("  name <display_name>              Set your display name");
    println!("  stats                            Show node stats");
    println!("  export-key                       Export identity key (KEEP SECRET)");
    println!("  id                               Show this node's ID");
    println!("  quit                             Shut down");
    println!();

    // Start background tasks (v2: mesh connections)
    let _accept_handle = node.start_accept_loop();
    let _pull_handle = node.start_pull_cycle(300); // 5 min pull cycle
    let _diff_handle = node.start_diff_cycle(120); // 2 min routing diff
    let _rebalance_handle = node.start_rebalance_cycle(600); // 10 min rebalance
    let _growth_handle = node.start_growth_loop(); // reactive mesh growth
    let _recovery_handle = node.start_recovery_loop(); // reactive anchor reconnect on mesh loss
    let _checkin_handle = node.start_social_checkin_cycle(3600); // 1 hour social checkin
    let _anchor_handle = node.start_anchor_register_cycle(600); // 10 min anchor register
    let _upnp_handle = node.start_upnp_renewal_cycle(); // UPnP lease renewal (if mapped)
    let _upnp_tcp_handle = node.start_upnp_tcp_renewal_cycle(); // UPnP TCP lease renewal
    let _http_handle = node.start_http_server(); // HTTP post delivery (if publicly reachable)
    let _bootstrap_check = node.start_bootstrap_connectivity_check(); // 24h isolation check
    let _web_handle = if let Some(wp) = web_port {
        Some(node.start_web_handler(wp))
    } else {
        None
    };

    if daemon {
        println!("Running as daemon. Press Ctrl+C to stop.");
        tokio::signal::ctrl_c().await?;
        println!("Shutting down...");
        return Ok(());
    }

    // Interactive command loop
    let stdin = io::stdin();
    let reader = stdin.lock();

    print!("> ");
    io::stdout().flush()?;

    for line in reader.lines() {
        let line = line?;
        let line = line.trim().to_string();

        if line.is_empty() {
            print!("> ");
            io::stdout().flush()?;
            continue;
        }

        let parts: Vec<&str> = line.splitn(2, ' ').collect();
        let cmd = parts[0];
        let arg = parts.get(1).map(|s| s.trim());

        match cmd {
            "post" => {
                if let Some(text) = arg {
                    match node.create_post(text.to_string()).await {
                        Ok((id, _post)) => {
                            println!("Posted! ID: {}", hex::encode(id));
                        }
                        Err(e) => println!("Error: {}", e),
                    }
                } else {
                    println!("Usage: post <text>");
                }
            }

            "feed" => match node.get_feed().await {
                Ok(posts) => {
                    if posts.is_empty() {
                        println!("(feed is empty - follow some nodes first)");
                    }
                    for (id, post, vis, decrypted) in posts {
                        print_post(&id, &post, &vis, decrypted.as_deref(), &node).await;
                    }
                }
                Err(e) => println!("Error: {}", e),
            },

            "posts" => match node.get_all_posts().await {
                Ok(posts) => {
                    if posts.is_empty() {
                        println!("(no posts yet)");
                    }
                    for (id, post, vis, decrypted) in posts {
                        print_post(&id, &post, &vis, decrypted.as_deref(), &node).await;
                    }
                }
                Err(e) => println!("Error: {}", e),
            },

            "follow" => {
                if let Some(id_hex) = arg {
                    match itsgoin_core::parse_node_id_hex(id_hex) {
                        Ok(nid) => {
                            node.follow(&nid).await?;
                            println!("Following {}", &id_hex[..16.min(id_hex.len())]);
                        }
                        Err(e) => println!("Invalid node ID: {}", e),
                    }
                } else {
                    println!("Usage: follow <node_id_hex>");
                }
            }

            "unfollow" => {
                if let Some(id_hex) = arg {
                    match itsgoin_core::parse_node_id_hex(id_hex) {
                        Ok(nid) => {
                            node.unfollow(&nid).await?;
                            println!("Unfollowed {}", &id_hex[..16.min(id_hex.len())]);
                        }
                        Err(e) => println!("Invalid node ID: {}", e),
                    }
                } else {
                    println!("Usage: unfollow <node_id_hex>");
                }
            }

            "follows" => match node.list_follows().await {
                Ok(follows) => {
                    if follows.is_empty() {
                        println!("(not following anyone)");
                    }
                    for nid in follows {
                        let name = node.get_display_name(&nid).await.unwrap_or(None);
                        let label = if nid == node.node_id { " (you)" } else { "" };
                        if let Some(name) = name {
                            println!("  {} ({}){}", name, &hex::encode(nid)[..12], label);
                        } else {
                            println!("  {}{}", hex::encode(nid), label);
                        }
                    }
                }
                Err(e) => println!("Error: {}", e),
            },

            "connect" => {
                if let Some(addr_str) = arg {
                    if addr_str.contains('@') {
                        // Full connect string: node_id@ip:port
                        match itsgoin_core::parse_connect_string(addr_str) {
                            Ok((nid, endpoint_addr)) => {
                                println!("Connecting and syncing...");
                                node.add_peer(nid).await?;
                                node.follow(&nid).await?;
                                match node.sync_with_addr(endpoint_addr).await {
                                    Ok(()) => {
                                        let name = node.get_display_name(&nid).await.unwrap_or(None);
                                        if let Some(name) = name {
                                            println!("Sync complete with {}!", name);
                                        } else {
                                            println!("Sync complete!");
                                        }
                                    }
                                    Err(e) => println!("Sync failed: {}", e),
                                }
                            }
                            Err(e) => println!("Invalid address: {}", e),
                        }
                    } else {
                        // Node ID only: use address resolution (N2/N3 + worm)
                        match itsgoin_core::parse_node_id_hex(addr_str) {
                            Ok(nid) => {
                                println!("Resolving address...");
                                node.add_peer(nid).await?;
                                node.follow(&nid).await?;
                                match node.sync_with(nid).await {
                                    Ok(()) => {
                                        let name = node.get_display_name(&nid).await.unwrap_or(None);
                                        if let Some(name) = name {
                                            println!("Connected to {}!", name);
                                        } else {
                                            println!("Connected!");
                                        }
                                    }
                                    Err(e) => println!("Address resolution failed: {}", e),
                                }
                            }
                            Err(e) => println!("Invalid node ID: {}", e),
                        }
                    }
                } else {
                    println!("Usage: connect <node_id@ip:port> or connect <node_id>");
                }
            }

            "sync" => {
                println!("Syncing with all known peers...");
                match node.sync_all().await {
                    Ok(()) => println!("Sync complete!"),
                    Err(e) => println!("Sync error: {}", e),
                }
            }

            "post-to" => {
                if let Some(rest) = arg {
                    let parts: Vec<&str> = rest.splitn(2, ' ').collect();
                    if parts.len() < 2 {
                        println!("Usage: post-to <target> <text>");
                        println!("  target: 'friends', circle name, or node_id hex");
                    } else {
                        let target = parts[0];
                        let text = parts[1];
                        let intent = if target == "friends" {
                            itsgoin_core::types::VisibilityIntent::Friends
                        } else if let Ok(nid) = itsgoin_core::parse_node_id_hex(target) {
                            itsgoin_core::types::VisibilityIntent::Direct(vec![nid])
                        } else {
                            itsgoin_core::types::VisibilityIntent::Circle(target.to_string())
                        };
                        match node
                            .create_post_with_visibility(text.to_string(), intent, vec![])
                            .await
                        {
                            Ok((id, _post, _vis)) => {
                                println!("Encrypted post! ID: {}", hex::encode(id));
                            }
                            Err(e) => println!("Error: {}", e),
                        }
                    }
                } else {
                    println!("Usage: post-to <target> <text>");
                }
            }

            "circles" => match node.list_circles().await {
                Ok(circles) => {
                    if circles.is_empty() {
                        println!("(no circles)");
                    }
                    for c in circles {
                        println!(
                            "  {} ({} members, created {})",
                            c.name,
                            c.members.len(),
                            chrono_lite(c.created_at / 1000)
                        );
                        for m in &c.members {
                            let name = node.get_display_name(m).await.unwrap_or(None);
                            let label = name.unwrap_or_else(|| hex::encode(m)[..12].to_string());
                            println!("    - {}", label);
                        }
                    }
                }
                Err(e) => println!("Error: {}", e),
            },

            "create-circle" => {
                if let Some(name) = arg {
                    match node.create_circle(name.to_string()).await {
                        Ok(()) => println!("Circle '{}' created", name),
                        Err(e) => println!("Error: {}", e),
                    }
                } else {
                    println!("Usage: create-circle <name>");
                }
            }

            "delete-circle" => {
                if let Some(name) = arg {
                    match node.delete_circle(name.to_string()).await {
                        Ok(()) => println!("Circle '{}' deleted", name),
                        Err(e) => println!("Error: {}", e),
                    }
                } else {
                    println!("Usage: delete-circle <name>");
                }
            }

            "add-to-circle" => {
                if let Some(rest) = arg {
                    let parts: Vec<&str> = rest.splitn(2, ' ').collect();
                    if parts.len() < 2 {
                        println!("Usage: add-to-circle <circle_name> <node_id_hex>");
                    } else {
                        match itsgoin_core::parse_node_id_hex(parts[1]) {
                            Ok(nid) => {
                                match node.add_to_circle(parts[0].to_string(), nid).await {
                                    Ok(()) => println!("Added to circle '{}'", parts[0]),
                                    Err(e) => println!("Error: {}", e),
                                }
                            }
                            Err(e) => println!("Invalid node ID: {}", e),
                        }
                    }
                } else {
                    println!("Usage: add-to-circle <circle_name> <node_id_hex>");
                }
            }

            "remove-from-circle" => {
                if let Some(rest) = arg {
                    let parts: Vec<&str> = rest.splitn(2, ' ').collect();
                    if parts.len() < 2 {
                        println!("Usage: remove-from-circle <circle_name> <node_id_hex>");
                    } else {
                        match itsgoin_core::parse_node_id_hex(parts[1]) {
                            Ok(nid) => {
                                match node.remove_from_circle(parts[0].to_string(), nid).await {
                                    Ok(()) => println!("Removed from circle '{}'", parts[0]),
                                    Err(e) => println!("Error: {}", e),
                                }
                            }
                            Err(e) => println!("Invalid node ID: {}", e),
                        }
                    }
                } else {
                    println!("Usage: remove-from-circle <circle_name> <node_id_hex>");
                }
            }

            "delete" => {
                if let Some(id_hex) = arg {
                    match itsgoin_core::parse_node_id_hex(id_hex) {
                        Ok(post_id) => match node.delete_post(&post_id).await {
                            Ok(()) => println!("Post deleted: {}", &id_hex[..16.min(id_hex.len())]),
                            Err(e) => println!("Error: {}", e),
                        },
                        Err(e) => println!("Invalid post ID: {}", e),
                    }
                } else {
                    println!("Usage: delete <post_id_hex>");
                }
            }

            "revoke" => {
                if let Some(rest) = arg {
                    let parts: Vec<&str> = rest.splitn(3, ' ').collect();
                    if parts.len() < 2 {
                        println!("Usage: revoke <post_id_hex> <node_id_hex> [sync|reencrypt]");
                    } else {
                        let mode = match parts.get(2).unwrap_or(&"sync") {
                            &"reencrypt" => itsgoin_core::types::RevocationMode::ReEncrypt,
                            _ => itsgoin_core::types::RevocationMode::SyncAccessList,
                        };
                        match (
                            itsgoin_core::parse_node_id_hex(parts[0]),
                            itsgoin_core::parse_node_id_hex(parts[1]),
                        ) {
                            (Ok(post_id), Ok(node_id)) => {
                                match node.revoke_post_access(&post_id, &node_id, mode).await {
                                    Ok(Some(new_id)) => {
                                        println!("Re-encrypted. New post ID: {}", hex::encode(new_id));
                                    }
                                    Ok(None) => println!("Access revoked (sync mode)"),
                                    Err(e) => println!("Error: {}", e),
                                }
                            }
                            (Err(e), _) => println!("Invalid post ID: {}", e),
                            (_, Err(e)) => println!("Invalid node ID: {}", e),
                        }
                    }
                } else {
                    println!("Usage: revoke <post_id_hex> <node_id_hex> [sync|reencrypt]");
                }
            }

            "revoke-circle" => {
                if let Some(rest) = arg {
                    let parts: Vec<&str> = rest.splitn(3, ' ').collect();
                    if parts.len() < 2 {
                        println!("Usage: revoke-circle <circle_name> <node_id_hex> [sync|reencrypt]");
                    } else {
                        let mode = match parts.get(2).unwrap_or(&"sync") {
                            &"reencrypt" => itsgoin_core::types::RevocationMode::ReEncrypt,
                            _ => itsgoin_core::types::RevocationMode::SyncAccessList,
                        };
                        match itsgoin_core::parse_node_id_hex(parts[1]) {
                            Ok(node_id) => {
                                match node.revoke_circle_access(parts[0], &node_id, mode).await {
                                    Ok(count) => println!("Revoked access on {} posts", count),
                                    Err(e) => println!("Error: {}", e),
                                }
                            }
                            Err(e) => println!("Invalid node ID: {}", e),
                        }
                    }
                } else {
                    println!("Usage: revoke-circle <circle_name> <node_id_hex> [sync|reencrypt]");
                }
            }

            "redundancy" => {
                match node.get_redundancy_summary().await {
                    Ok((total, zero, one, two_plus)) => {
                        println!("Redundancy for your {} authored posts:", total);
                        println!("  No replicas:  {} posts", zero);
                        println!("  1 replica:    {} posts", one);
                        println!("  2+ replicas:  {} posts", two_plus);
                        if zero > 0 {
                            println!("  WARNING: {} posts have no peer replicas!", zero);
                        }
                    }
                    Err(e) => println!("Error: {}", e),
                }
            }

            "peers" => match node.list_peer_records().await {
                Ok(records) => {
                    if records.is_empty() {
                        println!("(no known peers)");
                    }
                    for rec in records {
                        let name = node.get_display_name(&rec.node_id).await.unwrap_or(None);
                        let id_short = &hex::encode(rec.node_id)[..12];
                        let label = if let Some(name) = name {
                            format!("{} ({})", name, id_short)
                        } else {
                            hex::encode(rec.node_id)
                        };
                        let anchor = if rec.is_anchor { " [anchor]" } else { "" };
                        let addrs = if rec.addresses.is_empty() {
                            String::from("(mDNS only)")
                        } else {
                            rec.addresses.iter().map(|a| a.to_string()).collect::<Vec<_>>().join(", ")
                        };
                        let intro = if let Some(ib) = rec.introduced_by {
                            let ib_name = node.get_display_name(&ib).await.unwrap_or(None);
                            format!(
                                " via {}",
                                ib_name.unwrap_or_else(|| hex::encode(ib)[..12].to_string())
                            )
                        } else {
                            String::new()
                        };
                        println!("  {}{} [{}]{}", label, anchor, addrs, intro);
                    }
                }
                Err(e) => println!("Error: {}", e),
            },

            "name" => {
                if let Some(display_name) = arg {
                    match node.set_profile(display_name.to_string(), String::new()).await {
                        Ok(profile) => {
                            println!("Display name set to: {}", profile.display_name);
                        }
                        Err(e) => println!("Error: {}", e),
                    }
                } else {
                    // Show current name
                    match node.my_profile().await {
                        Ok(Some(profile)) => println!("Your name: {}", profile.display_name),
                        Ok(None) => println!("No display name set. Usage: name <display_name>"),
                        Err(e) => println!("Error: {}", e),
                    }
                }
            }

            "stats" => match node.stats().await {
                Ok(stats) => {
                    println!("Posts: {}", stats.post_count);
                    println!("Peers: {}", stats.peer_count);
                    println!("Following: {}", stats.follow_count);
                }
                Err(e) => println!("Error: {}", e),
            },

            "export-key" => {
                match node.export_identity_hex() {
                    Ok(hex_key) => {
                        println!("WARNING: This is your SECRET identity key. Anyone with");
                        println!("this key can impersonate you. Keep it safe!");
                        println!();
                        println!("{}", hex_key);
                        println!();
                        println!("To import on another device, use:");
                        println!("  itsgoin <data_dir> --import-key {}", hex_key);
                    }
                    Err(e) => println!("Error: {}", e),
                }
            }

            "id" => {
                let addr = node.endpoint_addr();
                let sockets: Vec<_> = addr.ip_addrs().collect();
                println!("Node ID: {}", node_id_hex);
                for sock in &sockets {
                    if let Some(first_sock) = sockets.first() {
                        println!("Connect: {}@{}", node_id_hex, first_sock);
                        break;
                    }
                    println!("Listen: {}", sock);
                }
            }

            "audience" => {
                match node.list_audience_members().await {
                    Ok(members) => {
                        if members.is_empty() {
                            println!("(no audience members)");
                        } else {
                            println!("Audience members ({}):", members.len());
                            for nid in members {
                                let name = node.get_display_name(&nid).await.unwrap_or(None);
                                let label = name.unwrap_or_else(|| hex::encode(&nid)[..12].to_string());
                                println!("  {}", label);
                            }
                        }
                    }
                    Err(e) => println!("Error: {}", e),
                }
            }

            "audience-request" => {
                if let Some(id_hex) = arg {
                    match itsgoin_core::parse_node_id_hex(id_hex) {
                        Ok(nid) => {
                            match node.request_audience(&nid).await {
                                Ok(()) => println!("Audience request sent"),
                                Err(e) => println!("Error: {}", e),
                            }
                        }
                        Err(e) => println!("Invalid node ID: {}", e),
                    }
                } else {
                    println!("Usage: audience-request <node_id_hex>");
                }
            }

            "audience-pending" => {
                use itsgoin_core::types::{AudienceDirection, AudienceStatus};
                match node.list_audience(AudienceDirection::Inbound, Some(AudienceStatus::Pending)).await {
                    Ok(records) => {
                        if records.is_empty() {
                            println!("(no pending audience requests)");
                        } else {
                            println!("Pending audience requests ({}):", records.len());
                            for rec in records {
                                let name = node.get_display_name(&rec.node_id).await.unwrap_or(None);
                                let label = name.unwrap_or_else(|| hex::encode(&rec.node_id)[..12].to_string());
                                println!("  {}", label);
                            }
                        }
                    }
                    Err(e) => println!("Error: {}", e),
                }
            }

            "audience-approve" => {
                if let Some(id_hex) = arg {
                    match itsgoin_core::parse_node_id_hex(id_hex) {
                        Ok(nid) => {
                            match node.approve_audience(&nid).await {
                                Ok(()) => println!("Approved audience member"),
                                Err(e) => println!("Error: {}", e),
                            }
                        }
                        Err(e) => println!("Invalid node ID: {}", e),
                    }
                } else {
                    println!("Usage: audience-approve <node_id_hex>");
                }
            }

            "audience-remove" => {
                if let Some(id_hex) = arg {
                    match itsgoin_core::parse_node_id_hex(id_hex) {
                        Ok(nid) => {
                            match node.remove_audience(&nid).await {
                                Ok(()) => println!("Removed from audience"),
                                Err(e) => println!("Error: {}", e),
                            }
                        }
                        Err(e) => println!("Invalid node ID: {}", e),
                    }
                } else {
                    println!("Usage: audience-remove <node_id_hex>");
                }
            }

            "worm" => {
                if let Some(id_hex) = arg {
                    match itsgoin_core::parse_node_id_hex(id_hex) {
                        Ok(nid) => {
                            println!("Worm lookup for {}...", &id_hex[..16.min(id_hex.len())]);
                            let start = std::time::Instant::now();
                            match node.worm_lookup(&nid).await {
                                Ok(Some(wr)) => {
                                    let elapsed = start.elapsed();
                                    if wr.node_id == nid {
                                        println!("Found! ({:.1}ms)", elapsed.as_secs_f64() * 1000.0);
                                    } else {
                                        println!("Found via recent peer {}! ({:.1}ms)",
                                            &hex::encode(wr.node_id)[..12],
                                            elapsed.as_secs_f64() * 1000.0);
                                    }
                                    if wr.addresses.is_empty() {
                                        println!("  (no address resolved)");
                                    } else {
                                        for addr in &wr.addresses {
                                            println!("  Address: {}", addr);
                                        }
                                    }
                                    println!("  Reporter: {}", &hex::encode(wr.reporter)[..12]);
                                }
                                Ok(None) => {
                                    let elapsed = start.elapsed();
                                    println!("Not found ({:.1}ms)", elapsed.as_secs_f64() * 1000.0);
                                }
                                Err(e) => println!("Error: {}", e),
                            }
                        }
                        Err(e) => println!("Invalid node ID: {}", e),
                    }
                } else {
                    println!("Usage: worm <node_id_hex>");
                }
            }

            "connections" => {
                let conns = node.list_connections().await;
                if conns.is_empty() {
                    println!("(no mesh connections)");
                } else {
                    println!("Mesh connections ({}):", conns.len());
                    for (nid, slot_kind, connected_at) in conns {
                        let name = node.get_display_name(&nid).await.unwrap_or(None);
                        let id_short = &hex::encode(nid)[..12];
                        let label = name.map(|n| format!("{} ({})", n, id_short))
                            .unwrap_or_else(|| hex::encode(nid));
                        let duration_secs = {
                            let now = std::time::SystemTime::now()
                                .duration_since(std::time::UNIX_EPOCH)
                                .unwrap_or_default()
                                .as_millis() as u64;
                            (now.saturating_sub(connected_at)) / 1000
                        };
                        println!("  {} [{:?}] connected {}s ago", label, slot_kind, duration_secs);
                    }
                }
            }

            "social-routes" => {
                match node.list_social_routes().await {
                    Ok(routes) => {
                        if routes.is_empty() {
                            println!("(no social routes)");
                        } else {
                            println!("Social routes ({}):", routes.len());
                            for r in routes {
                                let name = node.get_display_name(&r.node_id).await.unwrap_or(None);
                                let id_short = &hex::encode(r.node_id)[..12];
                                let label = name.map(|n| format!("{} ({})", n, id_short))
                                    .unwrap_or_else(|| hex::encode(r.node_id));
                                let addrs = if r.addresses.is_empty() {
                                    String::from("(no addr)")
                                } else {
                                    r.addresses.iter().map(|a| a.to_string()).collect::<Vec<_>>().join(", ")
                                };
                                println!("  {} [{:?}] {} [{}] via {:?} peers:{}",
                                    label, r.relation, r.status, addrs,
                                    r.reach_method, r.peer_addresses.len());
                            }
                        }
                    }
                    Err(e) => println!("Error: {}", e),
                }
            }

            "quit" | "exit" | "q" => {
                println!("Shutting down...");
                break;
            }

            _ => {
                println!("Unknown command: {}", cmd);
            }
        }

        print!("> ");
        io::stdout().flush()?;
    }

    Ok(())
}

async fn print_post(
    id: &[u8; 32],
    post: &itsgoin_core::types::Post,
    vis: &itsgoin_core::types::PostVisibility,
    decrypted: Option<&str>,
    node: &Node,
) {
    let author_hex = hex::encode(post.author);
    let author_short = &author_hex[..12];
    let is_me = &post.author == &node.node_id;

    let author_name = node.get_display_name(&post.author).await.unwrap_or(None);
    let author_label = match (author_name, is_me) {
        (Some(name), true) => format!("{} (you)", name),
        (Some(name), false) => name,
        (None, true) => format!("{} (you)", author_short),
        (None, false) => author_short.to_string(),
    };

    let vis_label = match vis {
        itsgoin_core::types::PostVisibility::Public => String::new(),
        itsgoin_core::types::PostVisibility::Encrypted { recipients } => {
            format!(" [encrypted, {} recipients]", recipients.len())
        }
        itsgoin_core::types::PostVisibility::GroupEncrypted { epoch, .. } => {
            format!(" [group-encrypted, epoch {}]", epoch)
        }
    };

    let ts = post.timestamp_ms / 1000;
    let datetime = chrono_lite(ts);

    let display_content = match vis {
        itsgoin_core::types::PostVisibility::Public => post.content.as_str().to_string(),
        itsgoin_core::types::PostVisibility::Encrypted { .. }
        | itsgoin_core::types::PostVisibility::GroupEncrypted { .. } => match decrypted {
            Some(text) => text.to_string(),
            None => "(encrypted)".to_string(),
        },
    };

    println!("---");
    println!("  {} | {}{}", author_label, datetime, vis_label);
    println!("  {}", display_content);
    println!("  id: {}", &hex::encode(id)[..16]);
}

/// Minimal timestamp formatting without pulling in chrono
fn chrono_lite(unix_secs: u64) -> String {
    let secs_per_min = 60u64;
    let secs_per_hour = 3600u64;
    let secs_per_day = 86400u64;

    let days_since_epoch = unix_secs / secs_per_day;
    let time_of_day = unix_secs % secs_per_day;

    let hours = time_of_day / secs_per_hour;
    let minutes = (time_of_day % secs_per_hour) / secs_per_min;

    let mut year = 1970u64;
    let mut remaining_days = days_since_epoch;
    loop {
        let days_in_year = if is_leap(year) { 366 } else { 365 };
        if remaining_days < days_in_year {
            break;
        }
        remaining_days -= days_in_year;
        year += 1;
    }
    let month_days: Vec<u64> = if is_leap(year) {
        vec![31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    } else {
        vec![31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    };
    let mut month = 1u64;
    for &d in &month_days {
        if remaining_days < d {
            break;
        }
        remaining_days -= d;
        month += 1;
    }
    let day = remaining_days + 1;

    format!(
        "{:04}-{:02}-{:02} {:02}:{:02} UTC",
        year, month, day, hours, minutes
    )
}

fn is_leap(year: u64) -> bool {
    (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
}
