use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
use std::sync::atomic::{AtomicU64, Ordering};

const USE_CIRCUIT_CODE: &str = concat!(
    "400000000000ffff000344332211",
    "000102030405060708090a0b0c0d0e0f",
    "101112131415161718191a1b1c1d1e1f"
);
const UNKNOWN_PACKET: &str = "400000000000fe";
const EXIT_USAGE: i32 = 2;
const EXIT_INPUT: i32 = 3;
const EXIT_OUTPUT: i32 = 5;

static TEMP_ID: AtomicU64 = AtomicU64::new(0);

struct TestDir(PathBuf);

impl TestDir {
    fn new(test_name: &str) -> Self {
        let id = TEMP_ID.fetch_add(1, Ordering::Relaxed);
        let path = std::env::temp_dir().join(format!(
            "metacrate-packet-dump-{test_name}-{}-{id}",
            std::process::id()
        ));
        fs::create_dir(&path).expect("create isolated test directory");
        Self(path)
    }

    fn path(&self, name: &str) -> PathBuf {
        self.0.join(name)
    }
}

impl Drop for TestDir {
    fn drop(&mut self) {
        let _ = fs::remove_dir_all(&self.0);
    }
}

fn run(args: &[&str]) -> Output {
    Command::new(env!("CARGO_BIN_EXE_packet-dump"))
        .args(args)
        .stdin(Stdio::null())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .output()
        .expect("run packet-dump")
}

fn text(bytes: &[u8]) -> &str {
    std::str::from_utf8(bytes).expect("command output is UTF-8")
}

fn path_text(path: &Path) -> &str {
    path.to_str().expect("test path is UTF-8")
}

fn assert_exit(output: &Output, expected: i32) {
    assert_eq!(
        output.status.code(),
        Some(expected),
        "stdout:\n{}\nstderr:\n{}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );
}

fn script(directory: &TestDir) -> PathBuf {
    let path = directory.path("packets.tsv");
    fs::write(
        &path,
        format!(
            "# direction, simulator, and wire bytes\n\
             incoming\tAlpha Region\t{USE_CIRCUIT_CODE}\n\
             outgoing\tBeta Region\t{USE_CIRCUIT_CODE}\n\
             incoming\tAlpha Region\t{UNKNOWN_PACKET}\n"
        ),
    )
    .expect("write fake packet script");
    path
}

#[test]
fn help_preserves_upstream_arguments_and_documents_safe_capture_controls() {
    let output = run(&["--help"]);
    assert!(output.status.success());
    let help = text(&output.stdout);
    for marker in [
        "[FIRSTNAME]",
        "[LASTNAME]",
        "[PASSWORD]",
        "[SECONDS]",
        "--fake-script",
        "--direction",
        "--packet-type",
        "--raw",
        "--output",
        "--max-output-bytes",
        "--max-packets",
        "incoming<TAB>simulator<TAB>hex-bytes",
    ] {
        assert!(help.contains(marker), "help omitted {marker}:\n{help}");
    }

    let output = run(&[]);
    assert_exit(&output, EXIT_USAGE);
    assert!(text(&output.stderr).contains("Usage: packet-dump"));
}

#[test]
fn fake_capture_decodes_directions_types_headers_raw_bytes_and_malformed_data() {
    let directory = TestDir::new("capture");
    let script = script(&directory);
    let output = run(&["--fake-script", path_text(&script), "--raw"]);
    assert!(output.status.success(), "{}", text(&output.stderr));
    assert!(output.stderr.is_empty());
    let output = text(&output.stdout);
    assert!(output.contains(
        "IN type=UseCircuitCode simulator=Alpha Region bytes=46 sequence=0 frequency=Low id=3 reliable=true resent=false zerocoded=false appended_acks=false"
    ));
    assert!(output.contains(&format!("raw={USE_CIRCUIT_CODE}")));
    assert!(output.contains("OUT type=UseCircuitCode simulator=Beta Region bytes=46"));
    assert!(output.contains("IN malformed-or-unknown simulator=Alpha Region bytes=7"));
    assert!(output.contains("Capture complete; packets=3 dropped=0 active_tasks=0 open_sockets=0"));

    let output = run(&[
        "--fake-script",
        path_text(&script),
        "--direction",
        "incoming",
        "--packet-type",
        "UseCircuitCode",
    ]);
    assert!(output.status.success());
    let output = text(&output.stdout);
    assert!(output.contains("IN type=UseCircuitCode"));
    assert!(!output.contains("OUT type="));
    assert!(!output.contains("malformed-or-unknown"));
    assert!(output.contains("Capture complete; packets=1"));
}

#[test]
fn output_file_packet_and_byte_limits_are_enforced_without_unbounded_writes() {
    let directory = TestDir::new("limits");
    let script = script(&directory);
    let capture = directory.path("capture.log");
    let output = run(&[
        "--fake-script",
        path_text(&script),
        "--max-packets",
        "1",
        "--output",
        path_text(&capture),
    ]);
    assert!(output.status.success(), "{}", text(&output.stderr));
    assert!(output.stdout.is_empty());
    let saved = fs::read_to_string(capture).expect("read capture output");
    assert!(saved.contains("Packet limit reached"));
    assert!(saved.contains("Capture complete; packets=1"));

    let limited = directory.path("limited.log");
    let output = run(&[
        "--fake-script",
        path_text(&script),
        "--max-output-bytes",
        "10",
        "--output",
        path_text(&limited),
    ]);
    assert_exit(&output, EXIT_OUTPUT);
    assert!(text(&output.stderr).contains("output byte limit"));
    assert_eq!(
        fs::metadata(limited).expect("limited file metadata").len(),
        0
    );

    let output = run(&["--fake-script", path_text(&script), "--max-packets", "0"]);
    assert_exit(&output, EXIT_USAGE);
}

#[test]
fn invalid_scripts_filters_and_credentials_fail_without_leaking_secrets() {
    let directory = TestDir::new("invalid");
    let invalid = directory.path("invalid.tsv");
    fs::write(&invalid, "incoming\tRegion\tzz\n").expect("write invalid script");
    let output = run(&["--fake-script", path_text(&invalid)]);
    assert_exit(&output, EXIT_INPUT);
    assert!(output.stdout.is_empty());
    assert!(text(&output.stderr).contains("non-hex digit"));

    let valid = script(&directory);
    let output = run(&[
        "--fake-script",
        path_text(&valid),
        "--packet-type",
        "NotAPacket",
    ]);
    assert_exit(&output, EXIT_USAGE);
    assert!(text(&output.stderr).contains("unknown packet type"));

    let password = "do-not-print-this-password";
    let output = run(&[
        "First",
        "Last",
        password,
        "10",
        "--fake-script",
        path_text(&valid),
    ]);
    assert_exit(&output, EXIT_USAGE);
    assert!(!text(&output.stdout).contains(password));
    assert!(!text(&output.stderr).contains(password));
}
