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

const CLIENT: &str = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
const AVATAR: &str = "11111111-2222-3333-4444-555555555555";
const BOT: &str = "22222222-3333-4444-5555-666666666666";
const GROUP: &str = "33333333-4444-5555-6666-777777777777";
const MEMBER: &str = "44444444-5555-6666-7777-888888888888";
const ROLE: &str = "55555555-6666-7777-8888-999999999999";
const CLASSIFIED: &str = "66666666-7777-8888-9999-aaaaaaaaaaaa";
const LAND: &str = "77777777-8888-9999-aaaa-bbbbbbbbbbbb";
const SIM: &str = "88888888-9999-aaaa-bbbb-cccccccccccc";
const OBJECT: &str = "99999999-aaaa-bbbb-cccc-dddddddddddd";
const DANCE1: &str = "b68a3d7c-de9e-fc87-eec8-543d787e5b0d";

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-test-client-services-{}-{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("session.tsv");
        fs::write(&path, contents).expect("write 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 test path")
}
fn run(args: &[&str]) -> Output {
    Command::new(env!("CARGO_BIN_EXE_test-client"))
        .args(args)
        .stdin(Stdio::null())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .output()
        .expect("run test-client")
}

#[test]
#[allow(clippy::too_many_lines)]
fn full_offline_session_exercises_every_remaining_service_and_voice_command() {
    let directory = TestDir::new();
    let script = directory.write(&format!(
        "!client\t{CLIENT}\tAlice\tBot\n\
         !service-avatar\t{CLIENT}\t{AVATAR}\tActive Resident\tBuilders\t128\t129\t25\t10\ttrue\n\
         !service-avatar\t{CLIENT}\t{BOT}\tQuiet Bot\tNone\t130\t131\t25\t11\tfalse\n\
         !service-friend\t{CLIENT}\t{AVATAR}\tActive Resident\ttrue\t4294967298000\t128\t129\t25\n\
         !service-group\t{CLIENT}\t{GROUP}\tBuilders Guild\n\
         !service-group-result\t{CLIENT}\t{GROUP}\tBuilders Guild\t42\n\
         !service-group-member\t{CLIENT}\t{GROUP}\t{MEMBER}\n\
         !service-group-role\t{CLIENT}\t{GROUP}\t{ROLE}\tOfficer\tArchitect\n\
         !service-person\t{CLIENT}\t{AVATAR}\tActive\tResident\ttrue\n\
         !service-classified\t{CLIENT}\t{CLASSIFIED}\tBuilder Services\t25\n\
         !service-event\t{CLIENT}\t7\tBuilding Class\tToday\n\
         !service-event-info\t{CLIENT}\t7\tBuilding Class\tTest Region\t1000\t2000\t25\n\
         !service-land\t{CLIENT}\t{LAND}\tBuilder Parcel\t1024\t500\n\
         !service-place\t{CLIENT}\tBuilder Plaza\tTest Region\t128\t128\t25\n\
         !service-sim\t{CLIENT}\tTest Region\t{SIM}\t4294967298000\t0.98\n\
         !service-utilization\t{CLIENT}\ttrue\n\
         !service-utilization-row\t{CLIENT}\tpacket\tAgentUpdate\t2\t3\t2048\t4096\n\
         !service-utilization-row\t{CLIENT}\tcapability\tEventQueueGet\t1\t4\t512\t8192\n\
         who\n\
         bots\n\
         friends\n\
         mapfriend {AVATAR}\n\
         key2name {AVATAR}\n\
         key2name {GROUP}\n\
         searchclassifieds Builder\n\
         searchevents Building\n\
         searchgroups Builders\n\
         searchland mainland 1000 512\n\
         searchpeople Active\n\
         searchplaces Builder\n\
         showevent 7\n\
         groups\n\
         groupmembers Builders Guild\n\
         grouproles Builders Guild\n\
         play list\n\
         play show\n\
         dilation\n\
         netstats\n\
         regioninfo\n\
         stats\n\
         uptime\n\
         cloneprofile {AVATAR} --confirm\n\
         sendgeneric test-method alpha beta --confirm\n\
         play DANCE1 --confirm\n\
         touch {OBJECT} --confirm\n\
         activategroup Builders Guild --confirm\n\
         invitegroup {AVATAR} {GROUP} {ROLE} --confirm\n\
         joingroup Builders Guild --confirm\n\
         leavegroup Builders Guild --confirm\n\
         voiceparcel\n\
         voiceaccount\n\
         quit\n"
    ));
    let output = run(&[
        "--allow-live-mutations",
        "--allow-spending",
        "--fake-script",
        path_text(&script),
    ]);
    let stdout = String::from_utf8(output.stdout).expect("UTF-8 stdout");
    let stderr = String::from_utf8(output.stderr).expect("UTF-8 stderr");
    assert!(
        output.status.success(),
        "stdout:\n{stdout}\nstderr:\n{stderr}"
    );
    assert!(stderr.is_empty(), "{stderr}");
    for expected in [
        "Active Resident (Group: Builders",
        "Quiet Bot (Group: None",
        "Is Probably a bot",
        "has 1 friends:",
        "Found Friend 11111111-2222-3333-4444-555555555555",
        "Avatar: Active Resident",
        "Group: Builders Guild",
        "returned 1 classified ads",
        "matched 1 Events",
        "matched 1 Groups",
        "1 results",
        "matched 1 People",
        "returned 1 results",
        "<redacted-url> Region/232/208/0",
        "got 1 groups",
        "MemberCount 1",
        "RoleCount 1",
        "AFRAID",
        DANCE1,
        "Dilation is 0.98",
        "Capabilities Totals",
        "Packet Totals",
        "UUID: 88888888-9999-aaaa-bbbb-cccccccccccc",
        "Packets in the queue:",
        "I am Alice Bot, Up Since:",
        "Synchronized our profile",
        "Sent generic message with method test-method",
        "Touched object",
        "Active group is now Builders Guild",
        "invited",
        "Joined the group Builders Guild",
        "has left the group Builders Guild",
        "Parcel voice info: region-name-present=true, parcel-local-id=42, channel=<redacted>",
        "Voice account provisioned: credentials=<redacted>",
        "CALL profile-clone",
        "CALL generic-message",
        "CALL animation-start",
        "CALL object-touch",
        "CALL group-activate",
        "CALL group-invite",
        "CALL group-join",
        "CALL group-leave",
        "CALL voice-parcel-info",
        "CALL voice-provision-account credentials=<redacted>",
    ] {
        assert!(stdout.contains(expected), "missing {expected:?}:\n{stdout}");
    }
}

#[test]
fn social_mutations_require_startup_and_per_command_confirmation() {
    let directory = TestDir::new();
    let script = directory.write(&format!(
        "!client\t{CLIENT}\tAlice\tBot\nplay {DANCE1} --confirm\nquit\n"
    ));
    let output = run(&["--fake-script", path_text(&script)]);
    let stdout = String::from_utf8(output.stdout).expect("UTF-8 stdout");
    assert!(output.status.success());
    assert!(stdout.contains("Live mutations are disabled"), "{stdout}");
    assert!(!stdout.contains("CALL animation-start"), "{stdout}");

    let script = directory.write(&format!(
        "!client\t{CLIENT}\tAlice\tBot\ntouch {OBJECT}\nquit\n"
    ));
    let output = run(&[
        "--allow-live-mutations",
        "--fake-script",
        path_text(&script),
    ]);
    let stdout = String::from_utf8(output.stdout).expect("UTF-8 stdout");
    assert!(output.status.success());
    assert!(stdout.contains("append --confirm"), "{stdout}");
    assert!(!stdout.contains("CALL object-touch"), "{stdout}");

    let script = directory.write(&format!(
        "!client\t{CLIENT}\tAlice\tBot\njoingroup uuid {GROUP} --confirm\nquit\n"
    ));
    let output = run(&[
        "--allow-live-mutations",
        "--fake-script",
        path_text(&script),
    ]);
    let stdout = String::from_utf8(output.stdout).expect("UTF-8 stdout");
    assert!(output.status.success());
    assert!(stdout.contains("restart with --allow-spending"), "{stdout}");
    assert!(!stdout.contains("CALL group-join"), "{stdout}");
}

#[test]
fn command_inventory_has_no_pending_source_commands() {
    assert!(libremetaverse_programs::test_client::pending_test_client_commands().is_empty());
}
