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

const EXIT_USAGE: i32 = 2;
const EXIT_INPUT: i32 = 3;
static TEMP_ID: AtomicU64 = AtomicU64::new(0);

struct TestDir(PathBuf);

impl TestDir {
    fn new() -> Self {
        let id = TEMP_ID.fetch_add(1, Ordering::Relaxed);
        let path = std::env::temp_dir().join(format!(
            "metacrate-live-grid-smoke-{}-{id}",
            std::process::id()
        ));
        fs::create_dir(&path).expect("create smoke 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 text(path: &Path) -> &str {
    path.to_str().expect("UTF-8 test path")
}

fn run(args: &[&str]) -> Output {
    Command::new(env!("CARGO_BIN_EXE_live-grid-smoke"))
        .args(args)
        .env_remove("GRID_USER")
        .env_remove("GRID_PASSWORD")
        .env_remove("GRID_LOGIN_URL")
        .stdin(Stdio::null())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .output()
        .expect("run live-grid-smoke")
}

#[test]
fn help_documents_credentials_side_effect_gates_and_unsupported_dangerous_actions() {
    let output = run(&["--help"]);
    assert!(output.status.success());
    let help = String::from_utf8(output.stdout).unwrap();
    for marker in [
        "GRID_USER",
        "workspace .env",
        "OpenSim/compatible-grid LLSD login endpoint",
        "--fake",
        "--audit-only",
        "--evidence",
        "--allow-live-login",
        "--allow-public-chat",
        "--allow-agent-movement",
        "--allow-reversible-inventory",
        "never spends L$",
        "permanently deletes",
        "targets another user",
    ] {
        assert!(help.contains(marker), "help omitted {marker}:\n{help}");
    }
    assert_eq!(run(&[]).status.code(), Some(EXIT_USAGE));
}

#[test]
fn fake_smoke_records_every_sanitized_stage_without_skips_or_credentials() {
    let directory = TestDir::new();
    let evidence = directory.path("evidence.jsonl");
    let output = run(&["--fake", "--evidence", text(&evidence)]);
    let stdout = String::from_utf8(output.stdout).unwrap();
    let stderr = String::from_utf8(output.stderr).unwrap();
    assert!(
        output.status.success(),
        "stdout:\n{stdout}\nstderr:\n{stderr}"
    );
    assert!(stderr.is_empty(), "{stderr}");
    let contents = fs::read_to_string(evidence).unwrap();
    assert!(!contents.contains("://"));
    assert!(!contents.to_ascii_lowercase().contains("password"));
    assert!(!contents.to_ascii_lowercase().contains("token"));
    let records: Vec<Value> = contents
        .lines()
        .map(|line| serde_json::from_str(line).unwrap())
        .collect();
    assert_eq!(records.len(), 9);
    let stages: Vec<_> = records
        .iter()
        .map(|record| record["stage"].as_str().unwrap())
        .collect();
    assert_eq!(
        stages,
        [
            "completeness",
            "login",
            "capabilities-simulator",
            "im-chat",
            "movement-teleport",
            "inventory-folder",
            "object-properties",
            "asset-texture",
            "logout",
        ]
    );
    assert!(records.iter().all(|record| record["status"] == "ok"));
    assert!(records.iter().all(|record| record["schema"] == 1));
    assert!(
        records
            .iter()
            .all(|record| record["recorded_unix_seconds"].as_u64().is_some())
    );
    assert!(
        records
            .iter()
            .all(|record| record["program_version"].as_str().is_some())
    );
    assert!(
        records
            .iter()
            .all(|record| record["rust_commit"].as_str().is_some())
    );
    assert_eq!(records[0]["metrics"]["programs"], 9);
    assert_eq!(records[0]["metrics"]["pending_programs"], 0);
    assert_eq!(records[0]["metrics"]["pending_commands"], 0);
    assert_eq!(records[8]["metrics"]["active_tasks"], 0);
}

#[test]
fn audit_proves_all_programs_and_commands_without_credentials_or_evidence() {
    let output = run(&["--audit-only"]);
    let stdout = String::from_utf8(output.stdout).unwrap();
    assert!(
        output.status.success(),
        "{}",
        String::from_utf8_lossy(&output.stderr)
    );
    assert!(stdout.contains("programs=9"), "{stdout}");
    assert!(stdout.contains("pending-programs=0"), "{stdout}");
    assert!(stdout.contains("pending-commands=0"), "{stdout}");
}

#[test]
fn live_mode_requires_every_literal_confirmation_before_loading_credentials() {
    let directory = TestDir::new();
    let evidence = directory.path("evidence.jsonl");
    let output = run(&[
        "--evidence",
        text(&evidence),
        "--allow-live-login",
        "--confirm-live-login",
        "LOGIN",
    ]);
    assert_eq!(output.status.code(), Some(EXIT_USAGE));
    let stderr = String::from_utf8(output.stderr).unwrap();
    assert!(stderr.contains("--allow-public-chat"), "{stderr}");
    assert!(!evidence.exists());
}

#[test]
fn evidence_is_create_new_and_never_overwrites_a_previous_audit() {
    let directory = TestDir::new();
    let evidence = directory.path("evidence.jsonl");
    fs::write(&evidence, "preserve-me\n").unwrap();
    let output = run(&["--fake", "--evidence", text(&evidence)]);
    assert_eq!(output.status.code(), Some(EXIT_INPUT));
    assert_eq!(fs::read_to_string(evidence).unwrap(), "preserve-me\n");
}
