//! Live integration tests — requires internet access to the public HyperDHT network.
//!
//! Run with: `cargo test -p peeroxide-cli --test live_commands -- --ignored`

#![deny(clippy::all)]

use std::io::{BufRead, BufReader};
use std::process::{Child, Command, Stdio};
use std::time::{Duration, SystemTime, UNIX_EPOCH};

fn bin_path() -> std::path::PathBuf {
    assert_cmd::cargo::cargo_bin("peeroxide")
}

fn kill_child(child: &mut Child) {
    let _ = child.kill();
    let _ = child.wait();
}

fn unique_topic() -> String {
    let ts = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_nanos();
    format!("peeroxide-live-test-{ts}")
}

#[tokio::test]
#[ignore = "requires internet — lookup on public HyperDHT"]
async fn test_live_lookup() {
    let result = tokio::time::timeout(Duration::from_secs(60), async {
        let topic = unique_topic();

        let output = tokio::task::spawn_blocking(move || {
            Command::new(bin_path())
                .args([
                    "--no-default-config", "--public",
                    "lookup", &topic, "--json",
                ])
                .output()
                .expect("failed to run lookup")
        })
        .await
        .unwrap();

        assert!(
            output.status.success(),
            "live lookup failed: {}",
            String::from_utf8_lossy(&output.stderr)
        );
    })
    .await;

    assert!(result.is_ok(), "test_live_lookup timed out after 60s");
}

#[tokio::test]
#[ignore = "requires internet — announce+lookup on public HyperDHT"]
async fn test_live_announce_then_lookup() {
    let result = tokio::time::timeout(Duration::from_secs(60), async {
        let topic = unique_topic();

        let mut announce = Command::new(bin_path())
            .args([
                "--no-default-config", "--public",
                "announce", &topic, "--duration", "30",
            ])
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()
            .expect("failed to spawn announce");

        tokio::time::sleep(Duration::from_secs(10)).await;

        let topic_clone = topic.clone();
        let output = tokio::task::spawn_blocking(move || {
            Command::new(bin_path())
                .args([
                    "--no-default-config", "--public",
                    "lookup", &topic_clone, "--json",
                ])
                .output()
                .expect("failed to run lookup")
        })
        .await
        .unwrap();

        kill_child(&mut announce);

        let stdout = String::from_utf8_lossy(&output.stdout);
        let stderr = String::from_utf8_lossy(&output.stderr);

        assert!(
            output.status.success(),
            "live lookup failed: {stderr}"
        );

        assert!(
            stdout.contains("\"peers_found\""),
            "expected peer data in output.\nstdout: {stdout}\nstderr: {stderr}"
        );
    })
    .await;

    assert!(result.is_ok(), "test_live_announce_then_lookup timed out after 60s");
}

#[tokio::test]
#[ignore = "requires internet — deaddrop roundtrip on public HyperDHT"]
async fn test_live_deaddrop_roundtrip() {
    let result = tokio::time::timeout(Duration::from_secs(60), async {
        let dir = tempfile::tempdir().unwrap();
        let msg_path = dir.path().join("live-msg.txt");
        std::fs::write(&msg_path, b"live deaddrop test message").unwrap();

        let msg_path_str = msg_path.to_str().unwrap().to_string();
        let mut leave_child = Command::new(bin_path())
            .args([
                "--no-default-config", "--public",
                "deaddrop", "leave", &msg_path_str, "--ttl", "45",
            ])
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()
            .expect("failed to spawn deaddrop leave");

        let stdout = leave_child.stdout.take().unwrap();
        let pickup_key = tokio::task::spawn_blocking(move || {
            let reader = BufReader::new(stdout);
            for line in reader.lines() {
                let line = line.unwrap_or_default();
                let trimmed = line.trim();
                if trimmed.len() == 64 && trimmed.chars().all(|c| c.is_ascii_hexdigit()) {
                    return Some(trimmed.to_string());
                }
            }
            None
        })
        .await
        .unwrap();

        let pickup_key = pickup_key.expect("deaddrop leave did not output pickup key");

        tokio::time::sleep(Duration::from_secs(3)).await;

        let pickup_output = tokio::task::spawn_blocking(move || {
            Command::new(bin_path())
                .args([
                    "--no-default-config", "--public",
                    "deaddrop", "pickup", &pickup_key,
                    "--timeout", "30",
                    "--no-ack",
                ])
                .output()
                .expect("failed to run deaddrop pickup")
        })
        .await
        .unwrap();

        kill_child(&mut leave_child);

        let pickup_stdout = String::from_utf8_lossy(&pickup_output.stdout);
        let pickup_stderr = String::from_utf8_lossy(&pickup_output.stderr);

        assert!(
            pickup_output.status.success(),
            "live deaddrop pickup failed: {pickup_stderr}"
        );

        assert_eq!(
            pickup_stdout.as_ref(), "live deaddrop test message",
            "pickup content mismatch.\nstdout: {pickup_stdout}\nstderr: {pickup_stderr}"
        );
    })
    .await;

    assert!(result.is_ok(), "test_live_deaddrop_roundtrip timed out after 60s");
}

#[tokio::test]
#[ignore = "requires internet — cp file transfer on public HyperDHT"]
async fn test_live_cp_send_recv() {
    let result = tokio::time::timeout(Duration::from_secs(60), async {
        let dir = tempfile::tempdir().unwrap();
        let send_path = dir.path().join("testfile.dat");
        let content = b"peeroxide cp live integration test content";
        std::fs::write(&send_path, content).unwrap();

        let recv_path = dir.path().join("received.dat");
        let send_path_str = send_path.to_str().unwrap().to_string();

        let mut send_child = Command::new(bin_path())
            .args([
                "--no-default-config", "--public",
                "cp", "send", &send_path_str,
            ])
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()
            .expect("failed to spawn cp send");

        let stdout = send_child.stdout.take().unwrap();
        let topic = tokio::task::spawn_blocking(move || {
            let reader = BufReader::new(stdout);
            for line in reader.lines() {
                let line = line.unwrap_or_default();
                let trimmed = line.trim();
                if trimmed.len() == 64 && trimmed.chars().all(|c| c.is_ascii_hexdigit()) {
                    return Some(trimmed.to_string());
                }
            }
            None
        })
        .await
        .unwrap();

        let topic = topic.expect("cp send did not output topic");

        tokio::time::sleep(Duration::from_secs(5)).await;

        let recv_path_str = recv_path.to_str().unwrap().to_string();
        let recv_output = tokio::task::spawn_blocking(move || {
            Command::new(bin_path())
                .args([
                    "--no-default-config", "--public",
                    "cp", "recv", &topic, &recv_path_str,
                    "--yes",
                    "--timeout", "30",
                ])
                .output()
                .expect("failed to run cp recv")
        })
        .await
        .unwrap();

        kill_child(&mut send_child);

        let recv_stderr = String::from_utf8_lossy(&recv_output.stderr);
        assert!(
            recv_output.status.success(),
            "live cp recv failed: {recv_stderr}"
        );

        let received = std::fs::read(&recv_path).expect("received file not found");
        assert_eq!(
            received, content,
            "received file content doesn't match.\nstderr: {recv_stderr}"
        );
    })
    .await;

    assert!(result.is_ok(), "test_live_cp_send_recv timed out after 60s");
}
