//! Integration tests that exercise the scythe CLI binary via std::process::Command.

use std::path::{Path, PathBuf};
use std::process::Command;

fn scythe_bin() -> Command {
    Command::new(env!("CARGO_BIN_EXE_scythe"))
}

/// Returns the workspace root (two levels up from crate manifest dir).
fn workspace_root() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap()
        .parent()
        .unwrap()
        .to_path_buf()
}

fn schema_dir(relative: &str) -> PathBuf {
    workspace_root().join("tests/schemas").join(relative)
}

// ---------------------------------------------------------------------------
// Help
// ---------------------------------------------------------------------------

#[test]
fn test_help_exits_zero() {
    let output = scythe_bin()
        .arg("--help")
        .output()
        .expect("failed to run scythe --help");

    assert!(output.status.success(), "scythe --help should exit 0");
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(
        stdout.contains("SQL-to-code") || stdout.contains("scythe"),
        "help output should mention scythe"
    );
}

#[test]
fn test_version_exits_zero() {
    let output = scythe_bin()
        .arg("--version")
        .output()
        .expect("failed to run scythe --version");

    assert!(output.status.success(), "scythe --version should exit 0");
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(
        stdout.contains("scythe"),
        "version output should contain 'scythe'"
    );
}

// ---------------------------------------------------------------------------
// Check command
// ---------------------------------------------------------------------------

#[test]
fn test_check_basemind_exits_zero() {
    let dir = schema_dir("simple/basemind");
    let output = scythe_bin()
        .args(["check", "--config", "scythe.toml"])
        .current_dir(&dir)
        .output()
        .expect("failed to run scythe check");

    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        output.status.success(),
        "scythe check on basemind should exit 0.\nstderr: {}",
        stderr
    );
    assert!(
        stderr.contains("Check passed")
            || stderr.contains("valid")
            || stderr.contains("All queries valid"),
        "check should report success.\nstderr: {}",
        stderr
    );
}

#[test]
fn test_check_pagila_exits_zero() {
    let dir = schema_dir("medium/pagila");
    let output = scythe_bin()
        .args(["check", "--config", "scythe.toml"])
        .current_dir(&dir)
        .output()
        .expect("failed to run scythe check");

    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        output.status.success(),
        "scythe check on pagila should exit 0.\nstderr: {}",
        stderr
    );
}

// ---------------------------------------------------------------------------
// Generate command
// ---------------------------------------------------------------------------

#[test]
fn test_generate_writes_file() {
    let dir = schema_dir("simple/basemind");
    let temp = tempfile::TempDir::new().unwrap();
    let output_dir = temp.path().join("generated");

    // Create a config that writes to our temp directory
    let config_content = format!(
        r#"[scythe]
version = "1"

[[sql]]
name = "test"
engine = "postgresql"
schema = ["schema.sql"]
queries = ["queries/*.sql"]
output = "{}"
"#,
        output_dir.display()
    );

    let config_path = temp.path().join("scythe.toml");
    std::fs::write(&config_path, &config_content).unwrap();

    let output = scythe_bin()
        .args(["generate", "--config", config_path.to_str().unwrap()])
        .current_dir(&dir)
        .output()
        .expect("failed to run scythe generate");

    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        output.status.success(),
        "scythe generate should exit 0.\nstderr: {}",
        stderr
    );

    // Verify output file was created
    let generated_file = output_dir.join("queries.rs");
    assert!(
        generated_file.exists(),
        "should create queries.rs at {}",
        generated_file.display()
    );

    let content = std::fs::read_to_string(&generated_file).unwrap();
    assert!(
        content.contains("Auto-generated by scythe"),
        "generated file should have scythe header"
    );
    assert!(
        content.len() > 100,
        "generated file should have substantial content, got {} bytes",
        content.len()
    );
}

#[test]
fn test_generate_pagila_writes_file() {
    let dir = schema_dir("medium/pagila");
    let temp = tempfile::TempDir::new().unwrap();
    let output_dir = temp.path().join("generated");

    // Use only the query files that don't trigger known limitations
    // (e.g. analytics.sql uses ANY($1) which produces an unresolvable param type)
    let config_content = format!(
        r#"[scythe]
version = "1"

[[sql]]
name = "pagila"
engine = "postgresql"
schema = ["schema.sql"]
queries = ["queries/customers.sql", "queries/rentals.sql"]
output = "{}"
"#,
        output_dir.display()
    );

    let config_path = temp.path().join("scythe.toml");
    std::fs::write(&config_path, &config_content).unwrap();

    let output = scythe_bin()
        .args(["generate", "--config", config_path.to_str().unwrap()])
        .current_dir(&dir)
        .output()
        .expect("failed to run scythe generate");

    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        output.status.success(),
        "scythe generate on pagila should exit 0.\nstderr: {}",
        stderr
    );

    let generated_file = output_dir.join("queries.rs");
    assert!(
        generated_file.exists(),
        "should create queries.rs for pagila"
    );

    let content = std::fs::read_to_string(&generated_file).unwrap();
    // Pagila has many queries, should produce substantial output
    assert!(
        content.len() > 500,
        "pagila should generate substantial code, got {} bytes",
        content.len()
    );
}

// ---------------------------------------------------------------------------
// Error cases
// ---------------------------------------------------------------------------

#[test]
fn test_missing_config_exits_nonzero() {
    let output = scythe_bin()
        .args(["check", "--config", "nonexistent.toml"])
        .output()
        .expect("failed to run scythe check");

    assert!(
        !output.status.success(),
        "scythe check with missing config should fail"
    );
}

#[test]
fn test_no_subcommand_exits_nonzero() {
    let output = scythe_bin().output().expect("failed to run scythe");

    assert!(
        !output.status.success(),
        "scythe with no subcommand should fail"
    );
}
