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

const MASTER: &str = "11111111-2222-3333-4444-555555555555";
const SESSION: &str = "22222222-3333-4444-5555-666666666666";
const OTHER: &str = "bbbbbbbb-cccc-dddd-eeee-ffffffffffff";
const SELF_ID: &str = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
const EXIT_USAGE: i32 = 2;
const EXIT_INPUT: i32 = 3;

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

struct TestDir(PathBuf);

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

    fn write(&self, contents: &str) -> PathBuf {
        let path = self.0.join("bridge.tsv");
        fs::write(&path, contents).expect("write bridge script");
        path
    }
}

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

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

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

fn stdout(output: &Output) -> &str {
    std::str::from_utf8(&output.stdout).expect("UTF-8 stdout")
}

fn stderr(output: &Output) -> &str {
    std::str::from_utf8(&output.stderr).expect("UTF-8 stderr")
}

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

#[test]
fn help_preserves_upstream_arguments_and_documents_controls() {
    let output = run(&["--help"]);
    assert!(output.status.success());
    let help = stdout(&output);
    for marker in [
        "[FIRSTNAME]",
        "[LASTNAME]",
        "[PASSWORD]",
        "[MASTER_UUID]",
        "[IRC_HOST]",
        "[IRC_PORT]",
        "[#CHANNEL]",
        "--fake-script",
        "--messages-per-second",
        "--burst",
        "irc-connect",
        "grid-chat",
        "teleport",
    ] {
        assert!(help.contains(marker), "help omitted {marker}:\n{help}");
    }

    let output = run(&[]);
    assert_exit(&output, EXIT_USAGE);
}

#[test]
fn scripted_bidirectional_bridge_filters_maps_and_accepts_only_master() {
    let directory = TestDir::new("bidirectional");
    let script = directory.write(&format!(
        "irc-connect\n\
         grid-login\tconnected to https://grid.invalid/cap/private\n\
         grid-chat\t{OTHER}\tAlice <Resident>\tnormal\tfull\thello IRC ☃\n\
         grid-chat\t{OTHER}\tAlice <Resident>\twhisper\tfull\tignored type\n\
         grid-chat\t{OTHER}\tAlice <Resident>\tnormal\tpartial\tignored range\n\
         grid-chat\t{SELF_ID}\tGateway Resident\tnormal\tfull\tignored self\n\
         irc\t#metacrate\tBob\thello grid ☃\n\
         irc\t#other\tMallory\tignored channel\n\
         irc\t#metacrate\tSLGateway\tignored self\n\
         teleport\t{OTHER}\t{SESSION}\n\
         teleport\t{MASTER}\t{SESSION}\n\
         shutdown\n"
    ));
    let output = run(&["--fake-script", path_text(&script)]);
    assert!(output.status.success(), "{}", stderr(&output));
    assert!(output.stderr.is_empty());
    let text = stdout(&output);
    for expected in [
        "CALL irc-join #metacrate",
        "CALL irc-privmsg #metacrate <Alice _Resident_> hello IRC ☃",
        "CALL grid-chat channel=0 type=Normal <Bob> hello grid ☃",
        &format!("CALL teleport-lure-respond {MASTER} {SESSION} accept=true"),
        "active-transports=0 active-tasks=0 shutdown=true",
    ] {
        assert!(text.contains(expected), "missing {expected}:\n{text}");
    }
    for rejected in [
        "ignored type",
        "ignored range",
        "ignored self",
        "ignored channel",
        &format!("teleport-lure-respond {OTHER}"),
        "grid.invalid",
        "/cap/private",
    ] {
        assert!(
            !text.contains(rejected),
            "leaked/forwarded {rejected}:\n{text}"
        );
    }
    assert!(text.contains("<redacted-url>"));
}

#[test]
fn reconnect_rate_limit_duplicate_and_loop_scenarios_are_deterministic() {
    let directory = TestDir::new("resilience");
    let script = directory.write(&format!(
        "irc-connect\n\
         grid-chat\t{OTHER}\tAlice\tnormal\tfull\tone\n\
         grid-chat\t{OTHER}\tAlice\tnormal\tfull\ttwo\n\
         grid-chat\t{OTHER}\tAlice\tnormal\tfull\ttwo\n\
         irc\t#metacrate\tRelay\t<Alice> one\n\
         irc-disconnect\n\
         grid-chat\t{OTHER}\tAlice\tnormal\tfull\twhile offline\n\
         advance\t1000\n\
         irc-connect\n\
         advance\t1000\n\
         shutdown\n"
    ));
    let output = run(&[
        "--fake-script",
        path_text(&script),
        "--messages-per-second",
        "1",
        "--burst",
        "1",
    ]);
    assert!(output.status.success(), "{}", stderr(&output));
    let text = stdout(&output);
    assert_eq!(
        text.matches("CALL irc-join #metacrate").count(),
        2,
        "{text}"
    );
    assert_eq!(
        text.matches("<Alice> two").count(),
        2,
        "one log and one transport call expected:\n{text}"
    );
    assert_eq!(
        text.matches("<Alice> one").count(),
        2,
        "one log and one call expected:\n{text}"
    );
    assert!(
        !text.contains("CALL grid-chat"),
        "loop was reflected:\n{text}"
    );
    assert!(
        text.contains("CALL irc-privmsg #metacrate <Alice> while offline"),
        "{text}"
    );
    assert!(text.contains("queued=0"), "{text}");
}

#[test]
fn malformed_and_oversized_inputs_fail_without_network_access() {
    let directory = TestDir::new("invalid");
    let invalid = directory.write("irc\tmissing fields\n");
    let output = run(&["--fake-script", path_text(&invalid)]);
    assert_exit(&output, EXIT_INPUT);
    assert!(stderr(&output).contains("line 1"));

    let oversized = directory.write(&format!("grid-login\t{}\n", "x".repeat(4097)));
    let output = run(&["--fake-script", path_text(&oversized)]);
    assert_exit(&output, EXIT_INPUT);

    let output = run(&[
        "First",
        "Last",
        "secret",
        "not-a-uuid",
        "irc.invalid",
        "6667",
        "#room",
    ]);
    assert_exit(&output, EXIT_USAGE);
    assert!(!stderr(&output).contains("secret"));
}
