use assert_cmd::Command;
use predicates::prelude::*;

#[test]
fn help_lists_codex_style_commands() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.arg("--help")
        .assert()
        .success()
        .stdout(predicate::str::contains("Usage: sunox [OPTIONS] [PROMPT]"))
        .stdout(predicate::str::contains("create"))
        .stdout(predicate::str::contains("download"))
        .stdout(predicate::str::contains("add"))
        .stdout(predicate::str::contains("clip"))
        .stdout(predicate::str::contains("login"))
        .stdout(predicate::str::contains("logout"))
        .stdout(predicate::str::contains("doctor"))
        .stdout(predicate::str::contains("-c, --config <key=value>"))
        .stdout(predicate::str::contains("generate").not());
}

#[test]
fn create_help_accepts_prompt_argument() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["create", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains(
            "Usage: sunox create [OPTIONS] [PROMPT]",
        ))
        .stdout(predicate::str::contains("--title"))
        .stdout(predicate::str::contains("--tags"))
        .stdout(predicate::str::contains("--captcha"));
}

#[test]
fn clip_help_groups_clip_subcommands() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["clip", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains("Manage clips"))
        .stdout(predicate::str::contains("list"))
        .stdout(predicate::str::contains("status"))
        .stdout(predicate::str::contains("download"))
        .stdout(predicate::str::contains("upload"))
        .stdout(predicate::str::contains("timed-lyrics"));
}

#[test]
fn clip_status_reuses_existing_validation() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["clip", "status", "--json"])
        .assert()
        .failure()
        .stderr(predicate::str::contains("\"code\": \"config_error\""))
        .stderr(predicate::str::contains("no clip IDs provided"));
}

#[test]
fn login_logout_and_doctor_help_are_available() {
    for command in ["login", "logout", "doctor"] {
        let mut cmd = Command::cargo_bin("sunox").expect("binary");

        cmd.args([command, "--help"])
            .assert()
            .success()
            .stdout(predicate::str::contains("Usage:"));
    }
}

#[test]
fn help_lists_playlist_command() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.arg("--help")
        .assert()
        .success()
        .stdout(predicate::str::contains("playlist"))
        .stdout(predicate::str::contains("Manage playlists"));
}

#[test]
fn help_lists_clip_management_commands() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["clip", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains("restore"))
        .stdout(predicate::str::contains("like"))
        .stdout(predicate::str::contains("dislike"));
}

#[test]
fn help_lists_upload_command() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["clip", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains("upload"))
        .stdout(predicate::str::contains("Upload a local audio file"));
}

#[test]
fn upload_help_lists_workflow_flags() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["clip", "upload", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains("<FILE>"))
        .stdout(predicate::str::contains("--upload-type"))
        .stdout(predicate::str::contains("--stem-mix"))
        .stdout(predicate::str::contains("--title"))
        .stdout(predicate::str::contains("--lyrics-file"))
        .stdout(predicate::str::contains("--timeout"));
}

#[test]
fn playlist_help_lists_management_subcommands() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["playlist", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains("list"))
        .stdout(predicate::str::contains("info"))
        .stdout(predicate::str::contains("create"))
        .stdout(predicate::str::contains("set"))
        .stdout(predicate::str::contains("add"))
        .stdout(predicate::str::contains("remove"))
        .stdout(predicate::str::contains("publish"))
        .stdout(predicate::str::contains("reorder"))
        .stdout(predicate::str::contains("restore"))
        .stdout(predicate::str::contains("save"))
        .stdout(predicate::str::contains("unsave"))
        .stdout(predicate::str::contains("like"))
        .stdout(predicate::str::contains("dislike"))
        .stdout(predicate::str::contains("delete"));
}

#[test]
fn playlist_set_help_lists_image_url() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["playlist", "set", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains("--image-url"));
}

#[test]
fn persona_help_lists_management_subcommands() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["persona", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains("list"))
        .stdout(predicate::str::contains("info"))
        .stdout(predicate::str::contains("clips"))
        .stdout(predicate::str::contains("create"))
        .stdout(predicate::str::contains("set"))
        .stdout(predicate::str::contains("processed-clip"))
        .stdout(predicate::str::contains("publish"))
        .stdout(predicate::str::contains("unpublish"))
        .stdout(predicate::str::contains("love"))
        .stdout(predicate::str::contains("unlove"))
        .stdout(predicate::str::contains("toggle-love"))
        .stdout(predicate::str::contains("delete"))
        .stdout(predicate::str::contains("restore"))
        .stdout(predicate::str::contains("purge"));
}

#[test]
fn create_rejects_removed_wait_flag() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args([
        "create",
        "--title",
        "Test",
        "--tags",
        "pop",
        "--lyrics",
        "[Verse]\nHello",
        "--wait",
        "--no-captcha",
    ])
    .assert()
    .failure()
    .stderr(predicate::str::contains("unexpected argument '--wait'"));
}

#[test]
fn create_rejects_removed_download_flag() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args([
        "create",
        "--title",
        "Test",
        "--tags",
        "pop",
        "--lyrics",
        "[Verse]\nHello",
        "--download",
        "./out",
        "--no-captcha",
    ])
    .assert()
    .failure()
    .stderr(predicate::str::contains("unexpected argument '--download'"));
}

#[test]
fn create_help_lists_optional_captcha_flag() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["create", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains("--captcha"))
        .stdout(predicate::str::contains("[default: v5.5]").not())
        .stdout(predicate::str::contains("--variation").not());
}

#[test]
fn top_level_help_omits_removed_generate_command() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["--help"]).assert().success().stdout(
        predicate::str::contains("Generate music with custom lyrics, tags, and controls").not(),
    );
}

#[test]
fn cover_help_does_not_hardcode_generation_model_default() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["clip", "cover", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains("--model"))
        .stdout(predicate::str::contains("[default: v5.5]").not());
}

#[test]
fn speed_help_exposes_live_adjust_speed_contract() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["clip", "speed", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains("--multiplier"))
        .stdout(predicate::str::contains("--no-keep-pitch"))
        .stdout(predicate::str::contains("--title"));
}

#[test]
fn install_skill_prints_current_generation_guidance() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["install-skill", "--print"])
        .assert()
        .success()
        .stdout(predicate::str::contains("token=null"))
        .stdout(predicate::str::contains("--captcha"))
        .stdout(predicate::str::contains("sunox create --title"))
        .stdout(predicate::str::contains("sunox clip upload <file>"))
        .stdout(predicate::str::contains("sunox clip speed <clip_id>"));
}

#[test]
fn install_skill_defaults_to_codex_skill_directory() {
    let test_home = std::env::temp_dir().join(format!(
        "sunox-cli-install-skill-codex-default-test-{}",
        std::process::id()
    ));
    let _ = std::fs::remove_dir_all(&test_home);
    std::fs::create_dir_all(&test_home).expect("test home");

    let mut cmd = Command::cargo_bin("sunox").expect("binary");
    cmd.env("HOME", &test_home)
        .args(["install-skill", "--json"])
        .assert()
        .success()
        .stdout(predicate::str::contains("\"target\": \"codex\""))
        .stdout(predicate::str::contains(".codex/skills/sunox/SKILL.md"));

    let installed = test_home.join(".codex/skills/sunox/SKILL.md");
    let skill = std::fs::read_to_string(installed).expect("installed skill");
    assert!(skill.contains("sunox agent-info"));
    assert!(skill.contains("sunox clip wait"));
}

#[test]
fn install_skill_accepts_explicit_codex_target() {
    let test_home = std::env::temp_dir().join(format!(
        "sunox-cli-install-skill-codex-explicit-test-{}",
        std::process::id()
    ));
    let _ = std::fs::remove_dir_all(&test_home);
    std::fs::create_dir_all(&test_home).expect("test home");

    let mut cmd = Command::cargo_bin("sunox").expect("binary");
    cmd.env("HOME", &test_home)
        .args(["install-skill", "--target", "codex", "--json"])
        .assert()
        .success()
        .stdout(predicate::str::contains("\"target\": \"codex\""))
        .stdout(predicate::str::contains(".codex/skills/sunox/SKILL.md"));
}

#[test]
fn set_rejects_empty_update_before_auth() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["clip", "set", "clip-a", "--json"])
        .assert()
        .failure()
        .stderr(predicate::str::contains("\"code\": \"config_error\""))
        .stderr(predicate::str::contains(
            "provide at least one metadata field",
        ));
}

#[test]
fn status_rejects_empty_ids_before_auth() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["clip", "status", "--json"])
        .assert()
        .failure()
        .stderr(predicate::str::contains("\"code\": \"config_error\""))
        .stderr(predicate::str::contains("no clip IDs provided"));
}

#[test]
fn download_rejects_empty_ids_before_auth() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["clip", "download", "--json"])
        .assert()
        .failure()
        .stderr(predicate::str::contains("\"code\": \"config_error\""))
        .stderr(predicate::str::contains("no clip IDs provided"));
}

#[test]
fn top_level_download_reuses_clip_download_validation() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["download", "--json"])
        .assert()
        .failure()
        .stderr(predicate::str::contains("\"code\": \"config_error\""))
        .stderr(predicate::str::contains("no clip IDs provided"));
}

#[test]
fn top_level_download_help_is_user_facing() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["download", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains(
            "Usage: sunox download [OPTIONS] [IDS]...",
        ))
        .stdout(predicate::str::contains("--output"))
        .stdout(predicate::str::contains("--video"));
}

#[test]
fn top_level_add_requires_clip_ids_before_auth() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["add", "--to", "playlist-a", "--json"])
        .assert()
        .failure()
        .stderr(predicate::str::contains("\"code\": \"config_error\""))
        .stderr(predicate::str::contains("no clip IDs provided"));
}

#[test]
fn top_level_add_help_uses_playlist_language() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["add", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains(
            "Usage: sunox add [OPTIONS] --to <PLAYLIST_ID> [CLIP_IDS]...",
        ))
        .stdout(predicate::str::contains("--to <PLAYLIST_ID>"));
}

#[test]
fn config_set_persists_in_isolated_home() {
    let test_home =
        std::env::temp_dir().join(format!("sunox-cli-config-test-{}", std::process::id()));
    let _ = std::fs::remove_dir_all(&test_home);
    std::fs::create_dir_all(&test_home).expect("test home");

    let mut cmd = Command::cargo_bin("sunox").expect("binary");
    cmd.env("HOME", &test_home)
        .args(["config", "set", "output_dir", "./songs", "--json"])
        .assert()
        .success()
        .stdout(predicate::str::contains("\"output_dir\": \"./songs\""));

    let mut show = Command::cargo_bin("sunox").expect("binary");
    show.env("HOME", &test_home)
        .args(["config", "show", "--json"])
        .assert()
        .success()
        .stdout(predicate::str::contains("\"status\": \"success\""))
        .stdout(predicate::str::contains("\"data\""))
        .stdout(predicate::str::contains("\"output_dir\": \"./songs\""));
}

#[test]
fn config_show_json_uses_success_envelope() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["config", "show", "--json"])
        .assert()
        .success()
        .stdout(predicate::str::contains("\"status\": \"success\""))
        .stdout(predicate::str::contains("\"data\""))
        .stdout(predicate::str::contains("\"default_model\""));
}

#[test]
fn config_show_applies_suno_env_overrides() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.env("SUNO_OUTPUT_DIR", "/tmp/suno-output")
        .env("SUNO_POLL_TIMEOUT_SECS", "777")
        .args(["config", "show", "--json"])
        .assert()
        .success()
        .stdout(predicate::str::contains(
            "\"output_dir\": \"/tmp/suno-output\"",
        ))
        .stdout(predicate::str::contains("\"poll_timeout_secs\": 777"));
}

#[test]
fn config_set_normalizes_default_model_version() {
    let test_home = std::env::temp_dir().join(format!(
        "sunox-cli-model-config-test-{}",
        std::process::id()
    ));
    let _ = std::fs::remove_dir_all(&test_home);
    std::fs::create_dir_all(&test_home).expect("test home");

    let mut cmd = Command::cargo_bin("sunox").expect("binary");
    cmd.env("HOME", &test_home)
        .args(["config", "set", "default_model", "v5.5", "--json"])
        .assert()
        .success()
        .stdout(predicate::str::contains(
            "\"default_model\": \"chirp-fenix\"",
        ));
}

#[test]
fn global_config_override_applies_without_persisting() {
    let test_home = std::env::temp_dir().join(format!(
        "sunox-cli-global-config-test-{}",
        std::process::id()
    ));
    let _ = std::fs::remove_dir_all(&test_home);
    std::fs::create_dir_all(&test_home).expect("test home");

    let mut cmd = Command::cargo_bin("sunox").expect("binary");
    cmd.env("HOME", &test_home)
        .args(["-c", "default_model=v5", "config", "show", "--json"])
        .assert()
        .success()
        .stdout(predicate::str::contains(
            "\"default_model\": \"chirp-crow\"",
        ));

    let mut show = Command::cargo_bin("sunox").expect("binary");
    show.env("HOME", &test_home)
        .args(["config", "show", "--json"])
        .assert()
        .success()
        .stdout(predicate::str::contains(
            "\"default_model\": \"chirp-fenix\"",
        ));
}

#[test]
fn config_check_json_reports_missing_auth_structurally() {
    let test_home = std::env::temp_dir().join(format!(
        "sunox-cli-config-check-test-{}",
        std::process::id()
    ));
    let _ = std::fs::remove_dir_all(&test_home);
    std::fs::create_dir_all(&test_home).expect("test home");

    let mut cmd = Command::cargo_bin("sunox").expect("binary");
    cmd.env("HOME", &test_home)
        .args(["config", "check", "--json"])
        .assert()
        .success()
        .stdout(predicate::str::contains("\"status\": \"success\""))
        .stdout(predicate::str::contains("\"auth\""))
        .stdout(predicate::str::contains("\"ok\": false"))
        .stdout(predicate::str::contains("\"code\": \"auth_missing\""));
}

#[test]
fn agent_info_reports_submit_wait_download_workflow() {
    let mut cmd = Command::cargo_bin("sunox").expect("binary");

    cmd.args(["agent-info", "--json"])
        .assert()
        .success()
        .stdout(predicate::str::contains("\"clip wait\""))
        .stdout(predicate::str::contains("\"workflow\""))
        .stdout(predicate::str::contains("\"human_commands\""))
        .stdout(predicate::str::contains("\"machine_commands\""))
        .stdout(predicate::str::contains("sunox download <clip_id>"))
        .stdout(predicate::str::contains(
            "sunox add <clip_id> --to <playlist_id>",
        ))
        .stdout(predicate::str::contains(
            "submit generation or description and return clip payload",
        ))
        .stdout(predicate::str::contains(
            "poll clip ids until complete or error",
        ))
        .stdout(predicate::str::contains("download completed media"))
        .stdout(predicate::str::contains("\"v3.5\""))
        .stdout(predicate::str::contains("chirp-v3-5"))
        .stdout(predicate::str::contains("\"playlist\""))
        .stdout(predicate::str::contains("\"playlist_create\""))
        .stdout(predicate::str::contains("\"playlist_set_visibility\""))
        .stdout(predicate::str::contains("\"playlist_reorder_tracks\""))
        .stdout(predicate::str::contains("\"playlist_save\""))
        .stdout(predicate::str::contains("\"playlist_unsave\""))
        .stdout(predicate::str::contains("\"playlist_like\""))
        .stdout(predicate::str::contains("\"playlist_dislike\""))
        .stdout(predicate::str::contains("\"persona_create\""))
        .stdout(predicate::str::contains("\"persona_clips\""))
        .stdout(predicate::str::contains("\"persona_set_visibility\""))
        .stdout(predicate::str::contains("\"persona_set_metadata\""))
        .stdout(predicate::str::contains("\"persona_processed_clip\""))
        .stdout(predicate::str::contains("\"persona_love\""))
        .stdout(predicate::str::contains("\"persona_unlove\""))
        .stdout(predicate::str::contains("\"persona_toggle_love\""))
        .stdout(predicate::str::contains("\"clip_restore\""))
        .stdout(predicate::str::contains("\"clip_like\""))
        .stdout(predicate::str::contains("\"clip_dislike\""))
        .stdout(predicate::str::contains("\"clip_speed\""))
        .stdout(predicate::str::contains("\"persona_list\""))
        .stdout(predicate::str::contains("token=null"))
        .stdout(predicate::str::contains("--captcha"))
        .stdout(predicate::str::contains("\"audio_upload\""))
        .stdout(predicate::str::contains("\"persona_delete\""))
        .stdout(predicate::str::contains("\"persona_restore\""))
        .stdout(predicate::str::contains("\"persona_purge\""))
        .stdout(predicate::str::contains("\"unsupported_surfaces\""))
        .stdout(predicate::str::contains("\"image_upload\""))
        .stdout(predicate::str::contains("\"update_feedback_state\""))
        .stdout(predicate::str::contains("\"not_implemented\"").not())
        .stdout(predicate::str::contains("deprecated").not())
        .stdout(predicate::str::contains("\"config\""))
        .stdout(predicate::str::contains("\"agent_targets\""))
        .stdout(predicate::str::contains("\"codex\""))
        .stdout(predicate::str::contains("~/.codex/skills/sunox/SKILL.md"))
        .stdout(predicate::str::contains(
            "sunox install-skill --target codex",
        ))
        .stdout(predicate::str::contains("clip speed"))
        .stdout(predicate::str::contains("config.toml"));
}
