use std::path::Path;
use std::process::Command;

use super::{fabro_dev, output_text, write_file};

#[expect(
    clippy::disallowed_methods,
    reason = "integration tests intentionally shell out to git in temporary fixture repositories"
)]
fn git(root: &Path, args: &[&str]) {
    let output = Command::new("git")
        .args(args)
        .current_dir(root)
        .output()
        .expect("git should run");
    assert!(
        output.status.success(),
        "git {:?} failed\nstdout:\n{}\nstderr:\n{}",
        args,
        output_text(&output.stdout),
        output_text(&output.stderr)
    );
}

fn release_fixture() -> tempfile::TempDir {
    let fixture = tempfile::tempdir().expect("creating fixture");
    write_file(
        fixture.path(),
        "Cargo.toml",
        r#"[workspace]
members = []

[workspace.package]
version = "0.1.0"
"#,
    );
    git(fixture.path(), &["init"]);
    git(fixture.path(), &["config", "user.name", "Release Test"]);
    git(fixture.path(), &[
        "config",
        "user.email",
        "release-test@example.com",
    ]);
    git(fixture.path(), &["add", "Cargo.toml"]);
    git(fixture.path(), &["commit", "-m", "initial"]);
    fixture
}

#[test]
fn help_lists_release_flags() {
    let output = fabro_dev()
        .args(["release", "--help"])
        .assert()
        .success()
        .get_output()
        .clone();
    let stdout = output_text(&output.stdout);

    for flag in ["--dry-run", "--skip-tests", "--release-date", "--nightly"] {
        assert!(
            stdout.contains(flag),
            "release help should list {flag}:\n{stdout}"
        );
    }

    assert!(
        !stdout.contains("[PRERELEASE_LABEL]"),
        "release help should not list a prerelease positional:\n{stdout}"
    );
}

#[test]
fn dry_run_computes_stable_version_from_date() {
    let fixture = release_fixture();

    let output = fabro_dev()
        .args([
            "release",
            "--root",
            fixture
                .path()
                .to_str()
                .expect("fixture path should be utf-8"),
            "--release-date",
            "2026-01-01",
            "--dry-run",
        ])
        .assert()
        .success()
        .get_output()
        .clone();
    let stdout = output_text(&output.stdout);

    assert!(
        stdout.contains("Releasing 0.100.0 (tag v0.100.0)"),
        "dry-run should compute base version from date:\n{stdout}"
    );
    assert!(
        stdout.contains("cargo --locked dev spa refresh"),
        "dry-run should print one SPA refresh command:\n{stdout}"
    );
    assert!(
        !stdout.contains("git diff --exit-code -- lib/apps/fabro-spa/assets"),
        "dry-run should not print a separate SPA asset diff command:\n{stdout}"
    );
    assert!(
        stdout.contains("git tag -a v0.100.0 -m v0.100.0"),
        "dry-run should print release tag command:\n{stdout}"
    );
    assert!(
        stdout.contains(
            "unset GH_TOKEN GITHUB_TOKEN && SEGMENT_WRITE_KEY=fake-for-local-smoke cargo nextest run --locked"
        ),
        "dry-run should show release tests without inherited GitHub tokens:\n{stdout}"
    );
}

#[test]
fn dry_run_increments_existing_prerelease_number() {
    let fixture = release_fixture();
    git(fixture.path(), &["tag", "v0.100.0-nightly.0"]);

    let output = fabro_dev()
        .args([
            "release",
            "--root",
            fixture
                .path()
                .to_str()
                .expect("fixture path should be utf-8"),
            "--release-date",
            "2026-01-01",
            "--dry-run",
            "--nightly",
        ])
        .assert()
        .success()
        .get_output()
        .clone();
    let stdout = output_text(&output.stdout);

    assert!(
        stdout.contains("Releasing 0.100.0-nightly.1 (tag v0.100.0-nightly.1)"),
        "dry-run should increment existing nightly tag:\n{stdout}"
    );
}

#[test]
fn positional_nightly_fails_with_clap_error() {
    let output = fabro_dev()
        .args(["release", "nightly"])
        .assert()
        .failure()
        .code(2)
        .get_output()
        .clone();
    let stderr = output_text(&output.stderr);

    assert!(
        stderr.contains("unexpected argument 'nightly'"),
        "nightly positional should be rejected by clap:\n{stderr}"
    );
}

#[test]
fn dry_run_reports_skip_tests_without_running_release_tests() {
    let fixture = release_fixture();

    let output = fabro_dev()
        .args([
            "release",
            "--root",
            fixture
                .path()
                .to_str()
                .expect("fixture path should be utf-8"),
            "--release-date",
            "2026-01-01",
            "--dry-run",
            "--skip-tests",
        ])
        .assert()
        .success()
        .get_output()
        .clone();
    let stdout = output_text(&output.stdout);

    assert!(
        stdout.contains("--skip-tests set, would skip release-mode test smoke"),
        "dry-run should report skip-tests behavior:\n{stdout}"
    );
    assert!(
        !stdout.contains("cargo nextest run"),
        "dry-run with skip-tests should not print release test command:\n{stdout}"
    );
}

#[test]
fn dirty_worktree_errors_unless_dry_run() {
    let fixture = release_fixture();
    write_file(fixture.path(), "dirty.txt", "dirty\n");

    let output = fabro_dev()
        .args([
            "release",
            "--root",
            fixture
                .path()
                .to_str()
                .expect("fixture path should be utf-8"),
            "--release-date",
            "2026-01-01",
            "--skip-tests",
        ])
        .assert()
        .failure()
        .code(1)
        .get_output()
        .clone();
    let stderr = output_text(&output.stderr);

    assert!(
        stderr.contains("working tree is dirty"),
        "dirty non-dry-run release should fail before mutating:\n{stderr}"
    );
}
