use std::fs;
use std::io::Write;
use std::process::{Command, Stdio};
use tempfile::NamedTempFile;

const CLI_PATH: &str = env!("CARGO_BIN_EXE_json-sort");

#[test]
fn test_check_flag_reports_unsorted() {
    let file = NamedTempFile::new().unwrap();
    let file_path = file.path();
    let unsorted = "{\"b\":1,\"a\":2}";
    fs::write(file_path, unsorted).unwrap();

    let output = Command::new(CLI_PATH)
        .arg("--check")
        .arg(file_path)
        .output()
        .expect("failed to execute process");

    // Should exit with code 1 and print the file as not properly sorted
    assert!(!output.status.success());
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(stderr.contains("is not properly sorted"));

    // File should not be modified
    let after = fs::read_to_string(file_path).unwrap();
    assert_eq!(after, unsorted);
}

#[test]
fn test_fix_updates_file_and_exits_successfully() {
    let file = NamedTempFile::new().unwrap();
    let file_path = file.path();
    fs::write(file_path, "{\"b\":1,\"a\":2}").unwrap();

    let output = Command::new(CLI_PATH)
        .arg("--fix")
        .arg(file_path)
        .output()
        .expect("failed to execute process");

    assert!(output.status.success());
    let after = fs::read_to_string(file_path).unwrap();
    assert_eq!(after, "{\"a\":2,\"b\":1}");
}

#[test]
fn test_missing_input_exits_with_error() {
    let temp_dir = tempfile::tempdir().unwrap();
    let file_path = temp_dir.path().join("missing.json");

    let output = Command::new(CLI_PATH)
        .arg(&file_path)
        .output()
        .expect("failed to execute process");

    assert!(!output.status.success());
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(stderr.contains("No files matched input"));
}

#[test]
fn test_invalid_glob_exits_with_error() {
    let output = Command::new(CLI_PATH)
        .arg("[")
        .output()
        .expect("failed to execute process");

    assert!(!output.status.success());
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(stderr.contains("Invalid glob pattern"));
}

#[test]
fn test_duplicate_inputs_are_processed_once() {
    let file = NamedTempFile::new().unwrap();
    let file_path = file.path();
    fs::write(file_path, "{\"b\":1,\"a\":2}").unwrap();

    let output = Command::new(CLI_PATH)
        .arg("--check")
        .arg(file_path)
        .arg(file_path)
        .output()
        .expect("failed to execute process");

    assert!(!output.status.success());
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert_eq!(stderr.matches("is not properly sorted").count(), 1);
}

#[test]
fn test_check_accepts_sorted_file_with_trailing_newline() {
    let file = NamedTempFile::new().unwrap();
    let file_path = file.path();
    fs::write(file_path, "{\n  \"a\": 2,\n  \"b\": 1\n}\n").unwrap();

    let output = Command::new(CLI_PATH)
        .arg("--check")
        .arg(file_path)
        .output()
        .expect("failed to execute process");

    assert!(output.status.success());
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(stderr.is_empty());
}

#[test]
fn test_fix_preserves_trailing_newline_at_eof() {
    let file = NamedTempFile::new().unwrap();
    let file_path = file.path();
    fs::write(file_path, "{\"b\":1,\"a\":2}\n").unwrap();

    let output = Command::new(CLI_PATH)
        .arg("--fix")
        .arg(file_path)
        .output()
        .expect("failed to execute process");

    assert!(output.status.success());
    let after = fs::read_to_string(file_path).unwrap();
    assert_eq!(after, "{\"a\":2,\"b\":1}\n");
}

#[test]
fn test_version_prints_package_version() {
    let output = Command::new(CLI_PATH)
        .arg("--version")
        .output()
        .expect("failed to execute process");

    assert!(output.status.success());
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert_eq!(
        stdout.trim(),
        concat!("json-sort ", env!("CARGO_PKG_VERSION"))
    );
}

#[test]
fn test_help_includes_package_description() {
    let output = Command::new(CLI_PATH)
        .arg("--help")
        .output()
        .expect("failed to execute process");

    assert!(output.status.success());
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains(env!("CARGO_PKG_DESCRIPTION")));
}

#[test]
fn test_stdin_preserves_missing_trailing_newline() {
    let mut child = Command::new(CLI_PATH)
        .arg("-")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .expect("failed to execute process");

    child
        .stdin
        .as_mut()
        .unwrap()
        .write_all(br#"{"b":1,"a":2}"#)
        .unwrap();

    let output = child.wait_with_output().unwrap();

    assert!(output.status.success());
    assert_eq!(output.stdout, br#"{"a":2,"b":1}"#);
}
