//! E2E tests for `aipm init` — maps directly to BDD scenarios in
//! `tests/features/manifest/workspace-init.feature`.
//!
//! These tests exercise the actual compiled binary via `assert_cmd`,
//! using `tempfile` for isolated test directories.

// Integration test crates inherit workspace lints. Relax restrictions that are
// appropriate for test code (unwrap, expect, panic are normal in tests).
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]

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

fn aipm() -> assert_cmd::Command {
    Command::cargo_bin("aipm").expect("aipm binary should be built")
}

// =========================================================================
// Scenario: Default init with no flags creates marketplace only
// =========================================================================
#[test]
fn init_default_creates_marketplace_only() {
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("my-project");

    aipm().args(["init", &dir.display().to_string()]).assert().success();

    assert!(!dir.join("aipm.toml").exists(), "aipm.toml should NOT exist");
    // Starter plugin directory and components exist, but no aipm.toml (no --manifest)
    assert!(
        dir.join(".ai/starter-aipm-plugin/skills/scaffold-plugin/SKILL.md").exists(),
        "starter plugin components should exist"
    );
    assert!(
        !dir.join(".ai/starter-aipm-plugin/aipm.toml").exists(),
        "starter aipm.toml should NOT exist without --manifest"
    );
    assert!(dir.join(".claude/settings.json").exists(), ".claude/settings.json should exist");
}

// =========================================================================
// Scenario: --workspace only
// =========================================================================
#[test]
fn init_workspace_only() {
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("ws-only");

    aipm().args(["init", "--workspace", &dir.display().to_string()]).assert().success();

    assert!(dir.join("aipm.toml").exists(), "aipm.toml should exist");
    assert!(!dir.join(".ai").exists(), ".ai/ should NOT exist");
}

// =========================================================================
// Scenario: --marketplace only
// =========================================================================
#[test]
fn init_marketplace_only() {
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("mp-only");
    std::fs::create_dir_all(&dir).ok();

    aipm().args(["init", "--marketplace", &dir.display().to_string()]).assert().success();

    // Starter plugin exists but no aipm.toml without --manifest
    assert!(
        dir.join(".ai/starter-aipm-plugin/skills/scaffold-plugin/SKILL.md").exists(),
        ".ai/starter-aipm-plugin should exist"
    );
    assert!(
        !dir.join(".ai/starter-aipm-plugin/aipm.toml").exists(),
        "starter aipm.toml should NOT exist without --manifest"
    );
    assert!(!dir.join("aipm.toml").exists(), "aipm.toml should NOT exist");
}

// =========================================================================
// Scenario: Reject if aipm.toml already exists
// =========================================================================
#[test]
fn init_rejects_existing_workspace() {
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("existing-ws");
    std::fs::create_dir_all(&dir).ok();
    std::fs::write(dir.join("aipm.toml"), "[package]\n").ok();

    aipm()
        .args(["init", "--workspace", &dir.display().to_string()])
        .assert()
        .failure()
        .stderr(predicate::str::contains("already initialized"));
}

// =========================================================================
// Scenario: Reject if .ai/ already exists
// =========================================================================
#[test]
fn init_rejects_existing_marketplace() {
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("existing-mp");
    std::fs::create_dir_all(dir.join(".ai")).ok();

    aipm()
        .args(["init", "--marketplace", &dir.display().to_string()])
        .assert()
        .failure()
        .stderr(predicate::str::contains("already exists"));
}

// =========================================================================
// Scenario: Help shows usage
// =========================================================================
#[test]
fn init_help_shows_usage() {
    aipm()
        .args(["init", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains("Initialize"));
}

// =========================================================================
// Scenario: No subcommand shows version
// =========================================================================
#[test]
fn no_subcommand_shows_version() {
    aipm().assert().success().stdout(predicate::str::contains("aipm"));
}

// =========================================================================
// Scenario: Claude settings generated
// =========================================================================
#[test]
fn init_claude_settings_generated() {
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("claude-gen");

    aipm().args(["init", "--marketplace", &dir.display().to_string()]).assert().success();

    let content = std::fs::read_to_string(dir.join(".claude/settings.json")).unwrap();
    assert!(content.contains("extraKnownMarketplaces"));
    assert!(content.contains(".ai"));
}

// =========================================================================
// Scenario: Starter manifest is valid TOML
// =========================================================================
#[test]
fn init_starter_manifest_valid_toml() {
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("starter-valid");

    // Use --manifest to generate starter aipm.toml
    aipm()
        .args(["init", "--marketplace", "--manifest", &dir.display().to_string()])
        .assert()
        .success();

    let content = std::fs::read_to_string(dir.join(".ai/starter-aipm-plugin/aipm.toml")).unwrap();
    assert!(content.contains("name = \"starter-aipm-plugin\""));
    assert!(content.contains("version = \"0.1.0\""));
    assert!(content.contains("type = \"composite\""));
}

// =========================================================================
// Scenario: Marketplace.json is generated with correct structure
// =========================================================================
#[test]
fn init_marketplace_json_generated() {
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("mp-json");

    aipm().args(["init", "--marketplace", &dir.display().to_string()]).assert().success();

    let content = std::fs::read_to_string(dir.join(".ai/.claude-plugin/marketplace.json")).unwrap();
    let v: serde_json::Value = serde_json::from_str(&content).unwrap();
    assert_eq!(v["name"], "local-repo-plugins");
    assert_eq!(v["owner"]["name"], "local");
    assert_eq!(v["metadata"]["description"], "Local plugins for this repository");
    assert_eq!(v["plugins"][0]["name"], "starter-aipm-plugin");
    assert_eq!(v["plugins"][0]["source"], "./starter-aipm-plugin");
}

// =========================================================================
// Scenario: Marketplace.json with --no-starter has empty plugins
// =========================================================================
#[test]
fn init_marketplace_json_no_starter_empty_plugins() {
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("mp-json-nostarter");

    aipm().args(["init", "--no-starter", &dir.display().to_string()]).assert().success();

    let content = std::fs::read_to_string(dir.join(".ai/.claude-plugin/marketplace.json")).unwrap();
    let v: serde_json::Value = serde_json::from_str(&content).unwrap();
    assert_eq!(v["name"], "local-repo-plugins");
    assert!(v["plugins"].as_array().unwrap().is_empty());
}

// =========================================================================
// Scenario: Settings.json has correct marketplace name and enabled plugins
// =========================================================================
#[test]
fn init_settings_json_marketplace_name_and_enabled_plugins() {
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("settings-check");

    aipm().args(["init", "--marketplace", &dir.display().to_string()]).assert().success();

    let content = std::fs::read_to_string(dir.join(".claude/settings.json")).unwrap();
    let v: serde_json::Value = serde_json::from_str(&content).unwrap();
    assert!(v["extraKnownMarketplaces"]["local-repo-plugins"].is_object());
    assert_eq!(v["extraKnownMarketplaces"]["local-repo-plugins"]["source"]["path"], "./.ai");
    assert_eq!(v["enabledPlugins"]["starter-aipm-plugin@local-repo-plugins"], true);
}

// =========================================================================
// Scenario: Generated workspace manifest is valid
// =========================================================================
#[test]
fn init_generated_workspace_manifest_valid() {
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("ws-valid");

    aipm().args(["init", "--workspace", &dir.display().to_string()]).assert().success();

    let content = std::fs::read_to_string(dir.join("aipm.toml")).unwrap();
    assert!(content.contains("[workspace]"));
    assert!(content.contains("members = [\".ai/*\"]"));
    assert!(content.contains("plugins_dir = \".ai\""));
}

// =========================================================================
// Scaffold script e2e tests (require Node.js >= 22.6.0)
// =========================================================================

fn has_bash() -> bool {
    std::process::Command::new("bash").arg("--version").output().is_ok_and(|o| o.status.success())
}

fn has_aipm_make_plugin() -> bool {
    // The scaffold script delegates to `aipm make plugin`; skip if subcommand is absent.
    let output = Command::cargo_bin("aipm")
        .expect("aipm binary should be built")
        .args(["make", "plugin", "--help"])
        .output();
    output.is_ok_and(|o| o.status.success())
}

fn run_scaffold(dir: &std::path::Path, plugin_name: &str) -> std::process::Output {
    let script =
        dir.join(".ai/starter-aipm-plugin/scripts/scaffold-plugin.sh").display().to_string();
    // Put the cargo-built aipm binary on PATH so the bash script can find it.
    let aipm_bin = assert_cmd::cargo::cargo_bin("aipm");
    let bin_dir = aipm_bin.parent().expect("binary should have parent dir");
    let path = match std::env::var_os("PATH") {
        Some(p) => std::env::join_paths(
            std::iter::once(bin_dir.to_path_buf()).chain(std::env::split_paths(&p)),
        )
        .unwrap_or_else(|_| bin_dir.as_os_str().to_os_string()),
        None => bin_dir.as_os_str().to_os_string(),
    };
    std::process::Command::new("bash")
        .args([&script, plugin_name])
        .env("PATH", path)
        .current_dir(dir)
        .output()
        .expect("run scaffold script")
}

#[test]
fn scaffold_script_registers_in_marketplace_json() {
    if !has_bash() || !has_aipm_make_plugin() {
        return;
    }
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("scaffold-mp");

    aipm().args(["init", "--marketplace", &dir.display().to_string()]).assert().success();

    let output = run_scaffold(&dir, "my-new-plugin");
    assert!(
        output.status.success(),
        "scaffold should succeed: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let content = std::fs::read_to_string(dir.join(".ai/.claude-plugin/marketplace.json")).unwrap();
    let v: serde_json::Value = serde_json::from_str(&content).unwrap();
    let plugins = v["plugins"].as_array().unwrap();
    assert_eq!(plugins.len(), 2, "should have starter + new plugin");
    assert_eq!(plugins[1]["name"], "my-new-plugin");
    assert_eq!(plugins[1]["source"], "./my-new-plugin");
}

#[test]
fn scaffold_script_enables_in_settings_json() {
    if !has_bash() || !has_aipm_make_plugin() {
        return;
    }
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("scaffold-settings");

    aipm().args(["init", "--marketplace", &dir.display().to_string()]).assert().success();

    let output = run_scaffold(&dir, "my-new-plugin");
    assert!(
        output.status.success(),
        "scaffold should succeed: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let content = std::fs::read_to_string(dir.join(".claude/settings.json")).unwrap();
    let v: serde_json::Value = serde_json::from_str(&content).unwrap();
    assert_eq!(v["enabledPlugins"]["my-new-plugin@local-repo-plugins"], true);
    assert_eq!(v["enabledPlugins"]["starter-aipm-plugin@local-repo-plugins"], true);
}

#[test]
fn scaffold_script_creates_plugin_directory() {
    if !has_bash() || !has_aipm_make_plugin() {
        return;
    }
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("scaffold-dir");

    aipm().args(["init", "--marketplace", &dir.display().to_string()]).assert().success();

    let output = run_scaffold(&dir, "my-new-plugin");
    assert!(
        output.status.success(),
        "scaffold should succeed: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    assert!(dir.join(".ai/my-new-plugin/.claude-plugin/plugin.json").exists());
    assert!(dir.join(".ai/my-new-plugin/skills/my-new-plugin/SKILL.md").exists());
}

#[test]
fn scaffold_script_multiple_plugins_no_duplicates() {
    if !has_bash() || !has_aipm_make_plugin() {
        return;
    }
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("scaffold-multi");

    aipm().args(["init", "--marketplace", &dir.display().to_string()]).assert().success();

    let out_a = run_scaffold(&dir, "plugin-a");
    assert!(out_a.status.success(), "plugin-a: {}", String::from_utf8_lossy(&out_a.stderr));
    let out_b = run_scaffold(&dir, "plugin-b");
    assert!(out_b.status.success(), "plugin-b: {}", String::from_utf8_lossy(&out_b.stderr));

    let mp_content =
        std::fs::read_to_string(dir.join(".ai/.claude-plugin/marketplace.json")).unwrap();
    let mp: serde_json::Value = serde_json::from_str(&mp_content).unwrap();
    let plugins = mp["plugins"].as_array().unwrap();
    assert_eq!(plugins.len(), 3, "should have starter + a + b");

    let settings_content = std::fs::read_to_string(dir.join(".claude/settings.json")).unwrap();
    let settings: serde_json::Value = serde_json::from_str(&settings_content).unwrap();
    let enabled = settings["enabledPlugins"].as_object().unwrap();
    assert_eq!(enabled.len(), 3, "should have 3 enabled plugins");
}

#[test]
fn scaffold_script_rejects_existing_plugin() {
    if !has_bash() || !has_aipm_make_plugin() {
        return;
    }
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("scaffold-dup");

    aipm().args(["init", "--marketplace", &dir.display().to_string()]).assert().success();

    let out1 = run_scaffold(&dir, "my-plugin");
    assert!(out1.status.success(), "first scaffold: {}", String::from_utf8_lossy(&out1.stderr));

    let out2 = run_scaffold(&dir, "my-plugin");
    assert!(out2.status.success(), "second scaffold should succeed (idempotent)");
    let stdout = String::from_utf8_lossy(&out2.stdout);
    assert!(stdout.contains("Already exists"), "stdout should mention 'Already exists': {stdout}");
}

// =========================================================================
// --yes / -y flag tests
// =========================================================================

#[test]
fn yes_flag_creates_default_marketplace() {
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("yes-test");

    aipm().args(["init", "-y", &dir.display().to_string()]).assert().success();

    assert!(!dir.join("aipm.toml").exists(), "aipm.toml should NOT exist (marketplace only)");
    // Starter plugin exists but no aipm.toml without --manifest
    assert!(
        dir.join(".ai/starter-aipm-plugin/skills/scaffold-plugin/SKILL.md").exists(),
        "starter plugin components should exist"
    );
    assert!(
        !dir.join(".ai/starter-aipm-plugin/aipm.toml").exists(),
        "starter aipm.toml should NOT exist without --manifest"
    );
}

#[test]
fn yes_long_form_works() {
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("yes-long");

    aipm().args(["init", "--yes", &dir.display().to_string()]).assert().success();

    assert!(dir.join(".ai").exists(), ".ai directory should exist");
}

#[test]
fn yes_flag_with_workspace_and_marketplace() {
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("yes-both");

    aipm()
        .args(["init", "-y", "--workspace", "--marketplace", &dir.display().to_string()])
        .assert()
        .success();

    assert!(dir.join("aipm.toml").exists(), "aipm.toml should exist");
    assert!(dir.join(".ai").exists(), ".ai directory should exist");
}

#[test]
fn yes_flag_marketplace_only_no_workspace() {
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("yes-mkt");

    aipm().args(["init", "-y", &dir.display().to_string()]).assert().success();

    // Default is marketplace only, no workspace manifest
    assert!(!dir.join("aipm.toml").exists(), "default -y should NOT create aipm.toml");
    assert!(dir.join(".ai").exists(), ".ai should exist");
}

// =========================================================================
// --manifest flag tests
// =========================================================================

#[test]
fn init_manifest_flag_generates_starter_toml() {
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("manifest-flag");

    aipm()
        .args(["init", "--marketplace", "--manifest", &dir.display().to_string()])
        .assert()
        .success();

    assert!(
        dir.join(".ai/starter-aipm-plugin/aipm.toml").exists(),
        "starter aipm.toml should exist with --manifest"
    );
    let content = std::fs::read_to_string(dir.join(".ai/starter-aipm-plugin/aipm.toml")).unwrap();
    assert!(content.contains("name = \"starter-aipm-plugin\""));
}

#[test]
fn init_without_manifest_flag_skips_starter_toml() {
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("no-manifest-flag");

    aipm().args(["init", "--marketplace", &dir.display().to_string()]).assert().success();

    assert!(
        dir.join(".ai/starter-aipm-plugin/skills/scaffold-plugin/SKILL.md").exists(),
        "components should exist"
    );
    assert!(
        !dir.join(".ai/starter-aipm-plugin/aipm.toml").exists(),
        "aipm.toml should NOT exist without --manifest"
    );
}
