//! End-to-end integration tests for complete hx workflows.
//!
//! These tests verify that hx commands work correctly together
//! in realistic development scenarios.
//!
//! Run with: cargo test -p hx-cli --test e2e_workflows

#![cfg(not(windows))]

use assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
use tempfile::TempDir;

fn hx() -> Command {
    Command::cargo_bin("hx").unwrap()
}

// =============================================================================
// Workflow 1: Create and Build a Simple Project
// =============================================================================

#[test]
fn test_workflow_create_build_simple() {
    let temp = TempDir::new().unwrap();
    let project_dir = temp.path().join("simple-app");

    // Step 1: Initialize a new binary project
    hx().args(["init", "--name", "simple-app"])
        .arg(&project_dir)
        .assert()
        .success();

    // Verify project structure
    assert!(project_dir.join("hx.toml").exists());
    assert!(project_dir.join("simple-app.cabal").exists());
    assert!(project_dir.join("src/Main.hs").exists());

    // Step 2: Run doctor to verify it executes in the project context.
    // `hx doctor` exits 0 when the toolchain is healthy and 4 when required
    // tools are missing; CI runners do not all ship a Haskell toolchain, so
    // accept either rather than depending on the host being fully set up.
    hx().current_dir(&project_dir)
        .arg("doctor")
        .assert()
        .code(predicate::in_iter([0, 4]));
}

// =============================================================================
// Workflow 2: Library Project with Tests
// =============================================================================

#[test]
fn test_workflow_library_with_tests() {
    let temp = TempDir::new().unwrap();
    let project_dir = temp.path().join("my-lib");

    // Step 1: Create library project
    hx().args(["init", "--lib", "--name", "my-lib"])
        .arg(&project_dir)
        .assert()
        .success();

    // Verify library structure
    assert!(project_dir.join("src/Lib.hs").exists());

    let hx_toml = fs::read_to_string(project_dir.join("hx.toml")).unwrap();
    assert!(hx_toml.contains("kind = \"lib\""));

    // Step 2: Check config is parseable
    hx().current_dir(&project_dir)
        .args(["config", "show"])
        .assert()
        .success();
}

// =============================================================================
// Workflow 3: Project Configuration Management
// =============================================================================

#[test]
fn test_workflow_config_management() {
    let temp = TempDir::new().unwrap();
    let project_dir = temp.path().join("config-test");

    // Create project
    hx().args(["init", "--name", "config-test"])
        .arg(&project_dir)
        .assert()
        .success();

    // View config - may show global or project config
    hx().current_dir(&project_dir)
        .args(["config", "show"])
        .assert()
        .success();

    // Modify hx.toml to add custom settings
    let hx_toml_path = project_dir.join("hx.toml");
    let mut content = fs::read_to_string(&hx_toml_path).unwrap();
    content.push_str(
        r#"
[build]
ghc-options = ["-Wall", "-Wcompat"]
"#,
    );
    fs::write(&hx_toml_path, content).unwrap();

    // Verify modified config is read correctly
    hx().current_dir(&project_dir)
        .args(["config", "show"])
        .assert()
        .success();
}

// =============================================================================
// Workflow 4: Clean Build Artifacts
// =============================================================================

#[test]
fn test_workflow_clean() {
    let temp = TempDir::new().unwrap();
    let project_dir = temp.path().join("clean-test");

    // Create project
    hx().args(["init", "--name", "clean-test"])
        .arg(&project_dir)
        .assert()
        .success();

    // Create fake build artifacts
    let hx_cache = project_dir.join(".hx");
    fs::create_dir_all(&hx_cache).unwrap();
    fs::write(hx_cache.join("cache.db"), "fake cache").unwrap();

    // Clean
    hx().current_dir(&project_dir)
        .arg("clean")
        .assert()
        .success();

    // Verify artifacts are removed
    assert!(!hx_cache.exists());
}

// =============================================================================
// Workflow 5: Shell Completions Generation
// =============================================================================

#[test]
fn test_workflow_completions_all_shells() {
    // Bash completions - just verify it succeeds and produces output
    hx().args(["completions", "generate", "bash"])
        .assert()
        .success();

    // Zsh completions
    hx().args(["completions", "generate", "zsh"])
        .assert()
        .success();

    // Fish completions
    hx().args(["completions", "generate", "fish"])
        .assert()
        .success();
}

// =============================================================================
// Workflow 6: Project with Custom Toolchain
// =============================================================================

#[test]
fn test_workflow_custom_toolchain() {
    let temp = TempDir::new().unwrap();
    let project_dir = temp.path().join("toolchain-test");

    // Create project
    hx().args(["init", "--name", "toolchain-test"])
        .arg(&project_dir)
        .assert()
        .success();

    // Modify hx.toml to specify a GHC version
    let hx_toml_path = project_dir.join("hx.toml");
    let content = r#"
[project]
name = "toolchain-test"
version = "0.1.0"
kind = "bin"

[toolchain]
ghc = "9.8.2"
"#;
    fs::write(&hx_toml_path, content).unwrap();

    // `hx config show` reports the *global* configuration, not the project
    // manifest, so verify the custom toolchain by reading hx.toml back —
    // matching the pattern used by the other workflow tests. (Asserting
    // against global config made this test pass or fail depending on whatever
    // global config happened to exist on the machine, which is why it failed
    // in CI but passed locally.)
    let written = fs::read_to_string(&hx_toml_path).unwrap();
    assert!(
        written.contains("[toolchain]") && written.contains("9.8.2"),
        "project hx.toml should retain the custom GHC version, got:\n{written}"
    );
}

// =============================================================================
// Workflow 7: Multiple Projects in Workspace
// =============================================================================

#[test]
fn test_workflow_multiple_projects() {
    let temp = TempDir::new().unwrap();

    // Create multiple projects in same temp directory
    let app1 = temp.path().join("app1");
    let app2 = temp.path().join("app2");
    let lib1 = temp.path().join("lib1");

    // Initialize all projects
    hx().args(["init", "--name", "app1"])
        .arg(&app1)
        .assert()
        .success();

    hx().args(["init", "--name", "app2"])
        .arg(&app2)
        .assert()
        .success();

    hx().args(["init", "--lib", "--name", "lib1"])
        .arg(&lib1)
        .assert()
        .success();

    // Verify each has correct structure
    assert!(app1.join("src/Main.hs").exists());
    assert!(app2.join("src/Main.hs").exists());
    assert!(lib1.join("src/Lib.hs").exists());

    // Each should run doctor independently (exit 0 healthy / 4 toolchain missing)
    hx().current_dir(&app1)
        .arg("doctor")
        .assert()
        .code(predicate::in_iter([0, 4]));

    hx().current_dir(&lib1)
        .arg("doctor")
        .assert()
        .code(predicate::in_iter([0, 4]));
}

// =============================================================================
// Workflow 8: Help System Navigation
// =============================================================================

#[test]
fn test_workflow_help_navigation() {
    // Main help
    hx().arg("--help")
        .assert()
        .success()
        .stdout(predicate::str::contains("Commands:"));

    // All major subcommands have help
    let subcommands = [
        "init",
        "build",
        "test",
        "run",
        "bench",
        "clean",
        "lock",
        "doctor",
        "toolchain",
        "fmt",
        "lint",
        "watch",
        "repl",
        "config",
        "stackage",
        "server",
        "coverage",
    ];

    for cmd in subcommands {
        hx().args([cmd, "--help"])
            .assert()
            .success()
            .stdout(predicate::str::contains(cmd).or(predicate::str::contains("Usage")));
    }
}

// =============================================================================
// Workflow 9: Error Handling
// =============================================================================

#[test]
fn test_workflow_error_handling() {
    let temp = TempDir::new().unwrap();

    // Running build outside a project should fail gracefully
    hx().current_dir(temp.path())
        .arg("build")
        .assert()
        .failure()
        .stderr(predicate::str::contains("hx.toml").or(predicate::str::contains("project")));

    // Invalid command should fail
    hx().arg("not-a-command").assert().failure();
}

// =============================================================================
// Workflow 10: Verbose and Quiet Modes
// =============================================================================

#[test]
fn test_workflow_output_modes() {
    let temp = TempDir::new().unwrap();
    let project_dir = temp.path().join("output-test");

    // Create project
    hx().args(["init", "--name", "output-test"])
        .arg(&project_dir)
        .assert()
        .success();

    // Verbose mode (doctor exits 0 healthy / 4 when toolchain is missing)
    hx().current_dir(&project_dir)
        .args(["--verbose", "doctor"])
        .assert()
        .code(predicate::in_iter([0, 4]));

    // Quiet mode
    hx().current_dir(&project_dir)
        .args(["--quiet", "doctor"])
        .assert()
        .code(predicate::in_iter([0, 4]));
}

// =============================================================================
// Workflow 11: Lockfile Operations
// =============================================================================

#[test]
fn test_workflow_lockfile_help() {
    let temp = TempDir::new().unwrap();
    let project_dir = temp.path().join("lock-test");

    // Create project
    hx().args(["init", "--name", "lock-test"])
        .arg(&project_dir)
        .assert()
        .success();

    // Lock help should work
    hx().current_dir(&project_dir)
        .args(["lock", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains("--snapshot"));
}

// =============================================================================
// Workflow 12: Stackage Integration
// =============================================================================

#[test]
fn test_workflow_stackage_commands() {
    // Stackage list help
    hx().args(["stackage", "list", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains("--lts"))
        .stdout(predicate::str::contains("--nightly"));

    // Stackage info help
    hx().args(["stackage", "info", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains("snapshot"));

    // Stackage set help
    hx().args(["stackage", "set", "--help"]).assert().success();
}

// =============================================================================
// Workflow 13: Cross-Compilation Support
// =============================================================================

#[test]
fn test_workflow_cross_compilation_flags() {
    // Build should support --target
    hx().args(["build", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains("--target"));

    // Test should support --target
    hx().args(["test", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains("--target"));

    // Run should support --target
    hx().args(["run", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains("--target"));
}

// =============================================================================
// Workflow 14: Server/LSP Commands
// =============================================================================

#[test]
fn test_workflow_server_commands() {
    // Server subcommands
    hx().args(["server", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains("start"))
        .stdout(predicate::str::contains("stop"))
        .stdout(predicate::str::contains("status"))
        .stdout(predicate::str::contains("restart"));
}

// =============================================================================
// Workflow 15: Coverage Commands
// =============================================================================

#[test]
fn test_workflow_coverage_commands() {
    hx().args(["coverage", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains("--html"))
        .stdout(predicate::str::contains("--json"))
        .stdout(predicate::str::contains("--threshold"));
}

// =============================================================================
// Workflow 16: Import a multi-package Stack project
// =============================================================================

#[test]
fn test_workflow_import_stack_multipackage() {
    let temp = TempDir::new().unwrap();
    let dir = temp.path();

    // A multi-package stack.yaml with an explicit compiler and an extra-dep
    // carrying an inline comment (exercises the issue #2 parsing fixes).
    fs::write(
        dir.join("stack.yaml"),
        "resolver: nightly-2026-03-23\n\
         compiler: ghc-9.8.2\n\
         packages:\n\
         - app\n\
         - lib\n\
         extra-deps:\n\
         - some-dep-1.2.3  # pin to avoid a regression\n",
    )
    .unwrap();

    hx().current_dir(dir)
        .args(["import", "--from", "stack"])
        .assert()
        .success();

    // hx.toml uses the explicit compiler version, and the inline comment is
    // stripped from the dependency version.
    let hx_toml = fs::read_to_string(dir.join("hx.toml")).unwrap();
    assert!(hx_toml.contains("ghc = \"9.8.2\""), "got:\n{hx_toml}");
    assert!(hx_toml.contains("some-dep = \"1.2.3\""), "got:\n{hx_toml}");

    // cabal.project is generated so hx recognizes the workspace members.
    let cabal_project = fs::read_to_string(dir.join("cabal.project")).unwrap();
    assert!(cabal_project.contains("./app"), "got:\n{cabal_project}");
    assert!(cabal_project.contains("./lib"), "got:\n{cabal_project}");
}
