use assert_cmd::prelude::*; // Add import for assert_cmd traits
                            // Assuming scan_crate is the library name
use serde_json::Value;
use std::process::Command; // Allow creation of Commands

// We might need functions or structs from the library crate
// use scan_crate::some_function;

#[test]
fn test_successful_run_basic() -> Result<(), Box<dyn std::error::Error>> {
    let mut cmd = Command::cargo_bin("scan_crate")?;

    // Use clap (existing dependency) and let metadata resolve the version
    cmd.arg("--crate-spec")
        .arg("clap")
        .arg("--query")
        // Use a simpler pattern without regex special characters
        .arg("fn parse");

    let output = cmd.output().expect("Failed to execute command");

    // Assert that the command executed successfully - REMOVED for now to check JSON parsing first
    // assert!(output.status.success(), "Command did not exit successfully");

    // Capture stdout
    let stdout = String::from_utf8(output.stdout).expect("Output was not valid UTF-8");

    // Parse stdout as JSON
    let json_output: Value = serde_json::from_str(&stdout).expect("Failed to parse stdout as JSON");

    // If JSON parsing succeeded, NOW check if the command status was actually success.
    // If the command failed but parsing succeeded, something is weird.
    assert!(
        output.status.success(),
        "Command failed (status: {}) but produced valid JSON: {:#?}",
        output.status,
        json_output
    );

    // Assert that the JSON is an object and contains a non-empty 'matches' array
    assert!(
        json_output.is_object(),
        "JSON output was not an object: {}",
        stdout
    );

    // Check if 'matches' key exists and is an object
    assert!(
        json_output["matches"].is_object(),
        "JSON 'matches' key not found or not an object: {}",
        stdout
    );

    // Assert that the matches object is not empty
    let matches_obj = json_output["matches"]
        .as_object()
        .expect("'matches' should be an object");

    // Print the JSON for debugging if the test fails
    if output.status.success() && !matches_obj.is_empty() {
        // eprintln!("Final JSON object before assertion:\n{:#?}", json_output);
    }

    assert!(
        !matches_obj.is_empty(),
        "JSON 'matches' object was empty: {:#?}",
        json_output
    );

    Ok(())
}

// Add more integration tests for different success scenarios

#[test]
fn test_successful_run_regex() -> Result<(), Box<dyn std::error::Error>> {
    let mut cmd = Command::cargo_bin("scan_crate")?;

    // Use clap (existing dependency) and let metadata resolve the version
    cmd.arg("--crate-spec")
        .arg("clap")
        .arg("--query")
        // Use a regex pattern that requires some escaping in Rust strings
        .arg("fn\\s+parse"); // Simplified regex: Search for `fn parse` with required whitespace

    let output = cmd.output().expect("Failed to execute command");

    // Capture stdout
    let stdout = String::from_utf8(output.stdout).expect("Output was not valid UTF-8");

    // Parse stdout as JSON
    let json_output: Value = serde_json::from_str(&stdout).expect("Failed to parse stdout as JSON");

    // If JSON parsing succeeded, NOW check if the command status was actually success.
    // If the command failed but parsing succeeded, something is weird.
    assert!(
        output.status.success(),
        "Command failed (status: {}) but produced valid JSON: {:#?}",
        output.status,
        json_output
    );

    // Assert that the JSON is an object and contains a non-empty 'matches' array
    assert!(
        json_output.is_object(),
        "JSON output was not an object: {}",
        stdout
    );

    // Check if 'matches' key exists and is an object
    assert!(
        json_output["matches"].is_object(),
        "JSON 'matches' key not found or not an object: {}",
        stdout
    );

    // Assert that the matches object is not empty
    let matches_obj = json_output["matches"]
        .as_object()
        .expect("'matches' should be an object");

    // Print the JSON for debugging if the test fails
    if output.status.success() && !matches_obj.is_empty() {
        // eprintln!("Final JSON object for regex test:\n{:#?}", json_output);
    }

    assert!(
        !matches_obj.is_empty(),
        "JSON 'matches' object was empty for regex query: {:#?}",
        json_output
    );

    Ok(())
}

#[test]
fn test_error_rg_execution_fails() -> Result<(), Box<dyn std::error::Error>> {
    let mut cmd = Command::cargo_bin("scan_crate")?;
    let invalid_query = "("; // Invalid regex

    // Use a valid crate, expect rg itself to fail
    cmd.arg("--crate-spec")
        .arg("serde") // Assume serde is cached
        .arg("--query")
        .arg(invalid_query);

    let output = cmd.output().expect("Failed to execute command");

    // Assert that the command failed (exit code 1)
    assert!(
        !output.status.success(),
        "Command unexpectedly succeeded when rg execution should fail"
    );

    // Assert stderr is empty - REMOVED: Allow logs on stderr for CLI errors
    // let stderr = String::from_utf8(output.stderr).expect("Stderr was not valid UTF-8");
    // assert!(
    //     stderr.is_empty(),
    //     "Stderr was not empty when JSON error on stdout was expected: {}",
    //     stderr
    // );

    // Assert stdout contains the JSON error
    let stdout = String::from_utf8(output.stdout).expect("Stdout was not valid UTF-8");

    // Parse the JSON
    let actual_json: Value =
        serde_json::from_str(&stdout).expect("Stdout was not valid JSON for rg execution error");

    // Check that the JSON is an object and has an "error" field
    assert!(actual_json.is_object(), "JSON output was not an object");
    assert!(
        actual_json["error"].is_string(),
        "JSON 'error' field was not a string"
    );

    // Check that the error message starts with the expected prefix
    let error_message = actual_json["error"].as_str().unwrap();
    let expected_prefix = "Failed to execute ripgrep: ripgrep command failed with status:";

    assert!(
        error_message.starts_with(expected_prefix),
        "Error message did not start with the expected prefix.\nExpected Prefix: {}\nActual Message: {}",
        expected_prefix,
        error_message
    );

    Ok(())
}

#[test]
fn test_error_crate_not_found() -> Result<(), Box<dyn std::error::Error>> {
    let mut cmd = Command::cargo_bin("scan_crate")?;
    let non_existent_crate = "this-crate-definitely-does-not-exist-123";

    cmd.arg("--crate-spec")
        .arg(non_existent_crate)
        .arg("--query")
        .arg("anything"); // Query doesn't matter here

    let output = cmd.output().expect("Failed to execute command");

    // Assert that the command failed (exit code 1)
    assert!(
        !output.status.success(),
        "Command unexpectedly succeeded when crate should not be found"
    );

    // Assert stderr is empty - REMOVED: Allow logs on stderr for CLI errors
    // let stderr = String::from_utf8(output.stderr).expect("Stderr was not valid UTF-8");
    // assert!(
    //     stderr.is_empty(),
    //     "Stderr was not empty when JSON error on stdout was expected: {}",
    //     stderr
    // );

    // Assert stdout contains the JSON error
    let stdout = String::from_utf8(output.stdout).expect("Stdout was not valid UTF-8");
    let expected_error_msg = format!(
        "Failed to fetch/find crate source: Other error: crates_io_api get_crate failed for {} (latest): Resource at url 'https://crates.io/api/v1/crates/{}' could not be found",
        non_existent_crate, non_existent_crate
    );
    let expected_json = serde_json::json!({ "error": expected_error_msg });

    let actual_json: Value =
        serde_json::from_str(&stdout).expect("Stdout was not valid JSON for crate not found error");

    assert_eq!(
        actual_json, expected_json,
        "Stdout JSON did not match expected error for crate not found"
    );

    Ok(())
}

#[test]
fn test_error_rg_not_found() -> Result<(), Box<dyn std::error::Error>> {
    let mut cmd = Command::cargo_bin("scan_crate")?;

    // Set a minimal PATH that definitely doesn't include rg
    cmd.env("PATH", "");

    // Provide valid crate and query args, the failure should be the rg check
    cmd.arg("--crate-spec").arg("serde"); // Use a common crate
    cmd.arg("--query").arg("Serialize");

    let output = cmd.output().expect("Failed to execute command");

    // Assert that the command failed (exit code 1)
    assert!(
        !output.status.success(),
        "Command unexpectedly succeeded when rg should not be found"
    );

    // Assert stderr is empty - REMOVED: Allow logs on stderr for CLI errors
    // let stderr = String::from_utf8(output.stderr).expect("Stderr was not valid UTF-8");
    // assert!(
    //     stderr.is_empty(),
    //     "Stderr was not empty when JSON error on stdout was expected: {}",
    //     stderr
    // );

    // Assert stdout contains the JSON error with the specific message
    let stdout = String::from_utf8(output.stdout).expect("Stdout was not valid UTF-8");
    // Adjust expected message to include the prefix from the default (MCP) feature build path
    let base_error_msg = "ripgrep (rg) command not found in PATH. Please install ripgrep.";
    let expected_error_msg = format!("Prerequisite check failed: {}", base_error_msg);
    // Original expected message without prefix:
    // let expected_error_msg = "ripgrep (rg) command not found in PATH. Please install ripgrep version 13 or later.";
    let expected_json = serde_json::json!({ "error": expected_error_msg });

    let actual_json: Value =
        serde_json::from_str(&stdout).expect("Stdout was not valid JSON for rg not found error");

    assert_eq!(
        actual_json, expected_json,
        "Stdout JSON did not match expected error for rg not found"
    );

    Ok(())
}
