#![cfg(not(miri))]

use std::process::{Command, Output};

fn get_binary() -> String {
    env!("CARGO_BIN_EXE_grpctestify").to_string()
}

fn fixture_path(rel: &str) -> String {
    std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
        .join(rel)
        .to_string_lossy()
        .into_owned()
}

fn run_cli(args: &[&str]) -> Output {
    let binary = get_binary();
    let runner = std::env::var("CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUNNER")
        .ok()
        .or_else(|| std::env::var("CROSS_RUNNER").ok());

    let mut cmd = if let Some(runner) = runner {
        let mut parts = runner.split_whitespace();
        let program = parts.next().expect("Runner must not be empty");
        let mut command = Command::new(program);
        command.args(parts);
        command.arg(&binary);
        command
    } else {
        Command::new(&binary)
    };

    cmd.current_dir(env!("CARGO_MANIFEST_DIR"))
        .args(args)
        .output()
        .expect("Failed to execute CLI command")
}

fn parse_json_stdout(output: &Output) -> serde_json::Value {
    assert!(
        output.status.success(),
        "CLI failed with status {:?}\nstderr:\n{}\nstdout:\n{}",
        output.status.code(),
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );

    serde_json::from_slice(&output.stdout).unwrap_or_else(|e| {
        panic!(
            "Invalid JSON output: {e}\nstderr:\n{}\nstdout:\n{}",
            String::from_utf8_lossy(&output.stderr),
            String::from_utf8_lossy(&output.stdout)
        )
    })
}

fn parse_json_stdout_any_status(output: &Output) -> serde_json::Value {
    serde_json::from_slice(&output.stdout).unwrap_or_else(|e| {
        panic!(
            "Invalid JSON output: {e}\nstderr:\n{}\nstdout:\n{}",
            String::from_utf8_lossy(&output.stderr),
            String::from_utf8_lossy(&output.stdout)
        )
    })
}

fn inspect_contract_view(json: &serde_json::Value) -> serde_json::Value {
    serde_json::json!({
        "has_file": json.get("file").is_some(),
        "parse_time_is_number": json.get("parse_time_ms").and_then(|v| v.as_f64()).is_some(),
        "validation_time_is_number": json.get("validation_time_ms").and_then(|v| v.as_f64()).is_some(),
        "sections": json["ast"]["sections"],
        "diagnostics": json["diagnostics"],
        "semantic_diagnostics": json["semantic_diagnostics"],
        "optimization_hints": json["optimization_hints"],
        "inferred_rpc_mode": json["inferred_rpc_mode"],
    })
}

fn explain_contract_view(json: &serde_json::Value) -> serde_json::Value {
    serde_json::json!({
        "has_semantic_plan": json.get("semantic_plan").is_some(),
        "has_optimization_trace": json.get("optimization_trace").is_some(),
        "has_optimized_plan": json.get("optimized_plan").is_some(),
        "has_execution_plan": json.get("execution_plan").is_some(),
        "optimization_trace": json["optimization_trace"],
        "semantic_summary": json["semantic_plan"]["summary"],
        "optimized_summary": json["optimized_plan"]["summary"],
        "execution_summary": json["execution_plan"]["summary"],
        "semantic_target": json["semantic_plan"]["target"],
        "optimized_target": json["optimized_plan"]["target"],
        "execution_target": json["execution_plan"]["target"],
    })
}

#[test]
fn test_fmt_preserves_comments_and_json5_content() {
    let dir = tempfile::tempdir().expect("failed to create temp dir");
    let file = dir.path().join("comments.gctf");
    let content = r#"# file header comment
--- ADDRESS ---
localhost:50051

# endpoint comment
--- ENDPOINT ---
example.Service/Call

--- REQUEST ---
# request comment
{ "a": 1, // inline comment
  "b": 2 }

--- ASSERTS ---
# assert explanation
.a == 1
"#;
    std::fs::write(&file, content).expect("failed to write temp gctf file");

    let path = file.to_string_lossy().into_owned();
    let output = run_cli(&["fmt", "-w", &path]);
    assert!(
        output.status.success(),
        "fmt -w command failed\nstderr:\n{}\nstdout:\n{}",
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );

    let updated = std::fs::read_to_string(&file).expect("failed to read rewritten gctf file");
    assert!(updated.contains("// file header comment"));
    assert!(updated.contains("// endpoint comment"));
    assert!(updated.contains("// assert explanation"));
    assert!(updated.contains("\"a\": 1"));
    assert!(updated.contains("\"b\": 2"));
}

#[test]
fn test_fmt_write_rewrites_json_inside_sections() {
    let dir = tempfile::tempdir().expect("failed to create temp dir");
    let file = dir.path().join("rewrite-json.gctf");
    let content = r#"--- ENDPOINT ---
example.v1.Greeter/SayHello

--- REQUEST ---
{"name":"World","meta":{"id":1}}

--- RESPONSE partial=true ---
{"message":"Hello","ok":true}
"#;
    std::fs::write(&file, content).expect("failed to write temp gctf file");

    let path = file.to_string_lossy().into_owned();
    let output = run_cli(&["fmt", "-w", &path]);
    assert!(
        output.status.success(),
        "fmt -w command failed\nstderr:\n{}\nstdout:\n{}",
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );

    let updated =
        std::fs::read_to_string(&file).expect("failed to read rewritten gctf file content");
    assert!(updated.contains("\"name\": \"World\""));
    assert!(updated.contains("\"meta\": {"));
    assert!(updated.contains("\"id\": 1"));
    assert!(updated.contains("--- RESPONSE partial"));
    assert!(updated.contains("\"ok\": true"));
}

#[test]
fn test_fmt_write_preserves_inline_json_comments() {
    let dir = tempfile::tempdir().expect("failed to create temp dir");
    let file = dir.path().join("preserve-json-comments.gctf");
    let content = r#"--- ENDPOINT ---
scalar.FileService/UploadFile

--- REQUEST ---
{
  "content": "aGVsbG8="  # "hello" in Base64
}

--- RESPONSE ---
{
  "checksum": "5d41402abc4b2a76b9719d911017c592"  # MD5 hash of "hello"
}
"#;
    std::fs::write(&file, content).expect("failed to write temp gctf file");

    let path = file.to_string_lossy().into_owned();
    let output = run_cli(&["fmt", "-w", &path]);
    assert!(
        output.status.success(),
        "fmt -w command failed\nstderr:\n{}\nstdout:\n{}",
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );

    let updated =
        std::fs::read_to_string(&file).expect("failed to read rewritten gctf file content");
    assert!(updated.contains("// \"hello\" in Base64"));
    assert!(updated.contains("// MD5 hash of \"hello\""));
}

#[test]
fn test_fmt_write_preserves_blank_line_between_sections() {
    let dir = tempfile::tempdir().expect("failed to create temp dir");
    let file = dir.path().join("preserve-section-spacing.gctf");
    let content = r#"--- ENDPOINT ---
weather.WeatherService/GetCurrentForecast

--- REQUEST ---
{}

--- RESPONSE ---
{"condition":"Sunny","date":{"day":5,"month":10,"year":2023},"temperatureC":22.5}
"#;
    std::fs::write(&file, content).expect("failed to write temp gctf file");

    let path = file.to_string_lossy().into_owned();
    let output = run_cli(&["fmt", "-w", &path]);
    assert!(
        output.status.success(),
        "fmt -w command failed\nstderr:\n{}\nstdout:\n{}",
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );

    let updated =
        std::fs::read_to_string(&file).expect("failed to read rewritten gctf file content");
    assert!(updated.contains("--- REQUEST ---\n{}\n\n--- RESPONSE ---"));
    assert!(updated.contains("\"temperatureC\": 22.5"));
}

#[test]
fn test_fmt_write_inserts_blank_line_between_adjacent_sections() {
    let dir = tempfile::tempdir().expect("failed to create temp dir");
    let file = dir.path().join("insert-section-spacing.gctf");
    let content = r#"--- ENDPOINT ---
weather.WeatherService/GetCurrentForecast
--- REQUEST ---
{}
--- RESPONSE ---
{"condition":"Sunny"}
"#;
    std::fs::write(&file, content).expect("failed to write temp gctf file");

    let path = file.to_string_lossy().into_owned();
    let output = run_cli(&["fmt", "-w", &path]);
    assert!(
        output.status.success(),
        "fmt -w command failed\nstderr:\n{}\nstdout:\n{}",
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );

    let updated =
        std::fs::read_to_string(&file).expect("failed to read rewritten gctf file content");
    assert!(updated.contains("GetCurrentForecast\n\n--- REQUEST ---"));
    assert!(updated.contains("--- REQUEST ---\n{}\n\n--- RESPONSE ---"));
}

#[test]
fn test_fmt_write_preserves_response_trailing_hash_comment() {
    let dir = tempfile::tempdir().expect("failed to create temp dir");
    let file = dir.path().join("response-trailing-hash-comment.gctf");
    let content = r#"--- ENDPOINT ---
scalar.FileService/UploadFile

--- REQUEST ---
{
    "content": "aGVsbG8="  # "hello" in Base64
}

--- RESPONSE ---
{
    "checksum": "5d41402abc4b2a76b9719d911017c592"}  # MD5 hash of "hello"
"#;
    std::fs::write(&file, content).expect("failed to write temp gctf file");

    let path = file.to_string_lossy().into_owned();
    let output = run_cli(&["fmt", "-w", &path]);
    assert!(
        output.status.success(),
        "fmt -w command failed\nstderr:\n{}\nstdout:\n{}",
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );

    let updated =
        std::fs::read_to_string(&file).expect("failed to read rewritten gctf file content");
    assert!(updated.contains("// \"hello\" in Base64"));
    assert!(updated.contains("// MD5 hash of \"hello\""));
    assert!(updated.contains("--- RESPONSE ---"));
}

#[test]
fn test_fmt_write_preserves_inline_block_comment_in_json() {
    let dir = tempfile::tempdir().expect("failed to create temp dir");
    let file = dir.path().join("preserve-inline-block-comment.gctf");
    let content = r#"--- ENDPOINT ---
weather.WeatherService/GetCurrentForecast

--- REQUEST ---
{}

--- RESPONSE ---
{"condition":"Sunny","date":{"day":5 /** five */,"month":10,"year":2023},"temperatureC":22.5}
"#;
    std::fs::write(&file, content).expect("failed to write temp gctf file");

    let path = file.to_string_lossy().into_owned();
    let output = run_cli(&["fmt", "-w", &path]);
    assert!(
        output.status.success(),
        "fmt -w command failed\nstderr:\n{}\nstdout:\n{}",
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );

    let updated =
        std::fs::read_to_string(&file).expect("failed to read rewritten gctf file content");
    assert!(updated.contains("/** five */"));
    assert!(updated.contains("\"day\": 5 /** five */"));
    assert!(updated.contains("\"temperatureC\": 22.5"));
}

#[test]
fn test_fmt_write_formats_json5_to_canonical_json() {
    let dir = tempfile::tempdir().expect("failed to create temp dir");
    let file = dir.path().join("json5-canonical.gctf");
    let content = r#"--- ENDPOINT ---
example.v1.Greeter/SayHello

--- REQUEST ---
{
  name: 'World',
  meta: { id: 1, },
}

--- RESPONSE ---
{ message: 'Hello World' }
"#;
    std::fs::write(&file, content).expect("failed to write temp gctf file");

    let path = file.to_string_lossy().into_owned();
    let output = run_cli(&["fmt", "-w", &path]);
    assert!(
        output.status.success(),
        "fmt -w command failed\nstderr:\n{}\nstdout:\n{}",
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );

    let updated = std::fs::read_to_string(&file).expect("failed to read rewritten gctf file");
    assert!(updated.contains("\"name\": \"World\""));
    assert!(updated.contains("\"meta\": {"));
    assert!(updated.contains("\"id\": 1"));
    assert!(updated.contains("\"message\": \"Hello World\""));
    assert!(!updated.contains("'World'"));
    assert!(!updated.contains("name:"));
}

#[test]
fn test_fmt_write_formats_response_jsonlines() {
    let dir = tempfile::tempdir().expect("failed to create temp dir");
    let file = dir.path().join("jsonlines-response.gctf");
    let content = r#"--- ENDPOINT ---
track.StreamService/Read

--- REQUEST ---
{"id":"abc"}

--- RESPONSE ---
{"seq":1,"ok":true}
{"seq":2,"ok":true}
"#;
    std::fs::write(&file, content).expect("failed to write temp gctf file");

    let path = file.to_string_lossy().into_owned();
    let output = run_cli(&["fmt", "-w", &path]);
    assert!(
        output.status.success(),
        "fmt -w command failed\nstderr:\n{}\nstdout:\n{}",
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );

    let updated = std::fs::read_to_string(&file).expect("failed to read rewritten gctf file");
    assert!(updated.contains("\"seq\": 1"));
    assert!(updated.contains("\"seq\": 2"));
    assert!(updated.contains("\"ok\": true"));
}

#[test]
fn test_fmt_write_formats_jsonc_and_preserves_line_comments() {
    let dir = tempfile::tempdir().expect("failed to create temp dir");
    let file = dir.path().join("jsonc-comments.gctf");
    let content = r#"--- ENDPOINT ---
weather.WeatherService/GetCurrentForecast

--- REQUEST ---
{}

--- RESPONSE ---
{
  // weather payload
  "condition":"Sunny",
  "temperatureC":22.5
}
"#;
    std::fs::write(&file, content).expect("failed to write temp gctf file");

    let path = file.to_string_lossy().into_owned();
    let output = run_cli(&["fmt", "-w", &path]);
    assert!(
        output.status.success(),
        "fmt -w command failed\nstderr:\n{}\nstdout:\n{}",
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );

    let updated = std::fs::read_to_string(&file).expect("failed to read rewritten gctf file");
    assert!(updated.contains("// weather payload"));
    assert!(updated.contains("\"condition\": \"Sunny\""));
    assert!(updated.contains("\"temperatureC\": 22.5"));
}

#[test]
fn test_fmt_write_preserves_hash_inside_string() {
    let dir = tempfile::tempdir().expect("failed to create temp dir");
    let file = dir.path().join("hash-in-string.gctf");
    let content = r#"--- ENDPOINT ---
weather.WeatherService/GetCurrentForecast

--- REQUEST ---
{}

--- RESPONSE ---
{"station":"MS#00006","temperatureC":22.5}
"#;
    std::fs::write(&file, content).expect("failed to write temp gctf file");

    let path = file.to_string_lossy().into_owned();
    let output = run_cli(&["fmt", "-w", &path]);
    assert!(
        output.status.success(),
        "fmt -w command failed\nstderr:\n{}\nstdout:\n{}",
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );

    let updated = std::fs::read_to_string(&file).expect("failed to read rewritten gctf file");
    assert!(updated.contains("\"station\": \"MS#00006\""));
    assert!(updated.contains("\"temperatureC\": 22.5"));
}

#[test]
fn test_fmt_write_preserves_multiline_block_comment_in_json() {
    let dir = tempfile::tempdir().expect("failed to create temp dir");
    let file = dir.path().join("multiline-block-comment.gctf");
    let content = r#"--- ENDPOINT ---
weather.WeatherService/GetCurrentForecast

--- REQUEST ---
{}

--- RESPONSE ---
{
  "date": {
    "day": 5,
    /* this is
       a multiline
       block comment */
    "month": 10,
    "year": 2023
  }
}
"#;
    std::fs::write(&file, content).expect("failed to write temp gctf file");

    let path = file.to_string_lossy().into_owned();
    let output = run_cli(&["fmt", "-w", &path]);
    assert!(
        output.status.success(),
        "fmt -w command failed\nstderr:\n{}\nstdout:\n{}",
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );

    let updated = std::fs::read_to_string(&file).expect("failed to read rewritten gctf file");
    assert!(updated.contains("/* this is"));
    assert!(updated.contains("a multiline"));
    assert!(updated.contains("block comment */"));
    assert!(updated.contains("\"month\": 10"));
}

#[test]
fn test_fmt_check_mode_reports_each_file_needing_format() {
    let dir = tempfile::tempdir().expect("failed to create temp dir");
    let file1 = dir.path().join("needs-format-1.gctf");
    let file2 = dir.path().join("needs-format-2.gctf");
    let content = r#"--- ENDPOINT ---
example.v1.Greeter/SayHello

--- REQUEST ---
{"name":"World"}

--- RESPONSE ---
{"message":"Hello World"}
"#;
    std::fs::write(&file1, content).expect("failed to write temp gctf file 1");
    std::fs::write(&file2, content).expect("failed to write temp gctf file 2");

    let path1 = file1.to_string_lossy().into_owned();
    let path2 = file2.to_string_lossy().into_owned();
    let output = run_cli(&["fmt", &path1, &path2]);

    assert!(
        !output.status.success(),
        "fmt check mode must fail when formatting is needed\nstderr:\n{}\nstdout:\n{}",
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("needs-format-1.gctf:1: [FORMAT_NEEDED]"));
    assert!(stdout.contains("needs-format-2.gctf:1: [FORMAT_NEEDED]"));
}

#[test]
fn test_fmt_check_mode_ignores_crlf_only_diff() {
    let dir = tempfile::tempdir().expect("failed to create temp dir");
    let file = dir.path().join("crlf-formatted.gctf");
    let content = "--- ENDPOINT ---\r\nexample.v1.Greeter/SayHello\r\n\r\n--- REQUEST ---\r\n{\r\n  \"name\": \"World\"\r\n}\r\n\r\n--- RESPONSE ---\r\n{\r\n  \"message\": \"Hello World!\"\r\n}\r\n";
    std::fs::write(&file, content).expect("failed to write temp gctf file");

    let path = file.to_string_lossy().into_owned();
    let output = run_cli(&["fmt", &path]);

    assert!(
        output.status.success(),
        "fmt check mode should ignore CRLF-only diff\nstderr:\n{}\nstdout:\n{}",
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );
}

#[test]
fn test_fmt_applies_optimizer_by_default() {
    let dir = tempfile::tempdir().expect("failed to create temp dir");
    let file = dir.path().join("fmt-opt-default.gctf");
    let content = r#"--- ENDPOINT ---
example.v1.Greeter/SayHello

--- REQUEST ---
{"name": "World"}

--- RESPONSE ---
{"message": "Hello World"}

--- ASSERTS ---
@has_header("x-request-id") == true
"#;
    std::fs::write(&file, content).expect("failed to write temp gctf file");

    let path = file.to_string_lossy().into_owned();
    let output = run_cli(&["fmt", "-w", &path]);
    assert!(
        output.status.success(),
        "fmt -w command failed\nstderr:\n{}\nstdout:\n{}",
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );

    let updated = std::fs::read_to_string(&file).expect("failed to read rewritten gctf file");
    assert!(updated.contains("@has_header(\"x-request-id\")"));
    // Optimizer should simplify `@has_header(...) == true` to just `@has_header(...)`
    assert!(!updated.contains("== true"));
}

#[test]
fn test_fmt_rejects_removed_optimize_flag() {
    let file = fixture_path("tests/data/gctf/valid_simple.gctf");
    let output = run_cli(&["fmt", "-o", &file]);

    assert!(
        !output.status.success(),
        "fmt -o must fail after optimize flag removal\nstderr:\n{}\nstdout:\n{}",
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );

    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        stderr.contains("unexpected argument '-o'")
            || stderr.contains("unexpected argument '--optimize'"),
        "expected clap error about removed -o flag, got:\n{}",
        stderr
    );
}

#[test]
fn test_run_subcommand_uses_dry_run_flag() {
    let dir = tempfile::tempdir().expect("failed to create temp dir");
    let file = dir.path().join("dry-run.gctf");
    let content = r#"--- ENDPOINT ---
example.v1.Greeter/SayHello

--- REQUEST ---
{"name": "World"}

--- RESPONSE ---
{"message": "Hello World"}
"#;
    std::fs::write(&file, content).expect("failed to write temp gctf file");

    let path = file.to_string_lossy().into_owned();
    let output = run_cli(&["run", &path, "--dry-run"]);

    assert!(
        output.status.success(),
        "run --dry-run command failed\nstderr:\n{}\nstdout:\n{}",
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(
        stdout.contains("Dry-Run Preview"),
        "expected dry-run preview output, got:\n{}",
        stdout
    );
}

#[test]
fn test_explain_shows_options_section_details() {
    let dir = tempfile::tempdir().expect("failed to create temp dir");
    let file = dir.path().join("explain-options.gctf");
    let content = r#"--- ENDPOINT ---
example.v1.Greeter/SayHello

--- OPTIONS ---
timeout: 5
retry: 2

--- REQUEST ---
{"name": "World"}

--- RESPONSE ---
{"message": "Hello World"}
"#;
    std::fs::write(&file, content).expect("failed to write temp gctf file");

    let path = file.to_string_lossy().into_owned();
    let output = run_cli(&["explain", &path]);

    assert!(
        output.status.success(),
        "explain command failed\nstderr:\n{}\nstdout:\n{}",
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("Step 2: OPTIONS"));
    assert!(stdout.contains("Runtime overrides"));
    assert!(stdout.contains("timeout: 5"));
    assert!(stdout.contains("retry: 2"));
}

#[test]
fn test_inspect_shows_options_overrides_in_flow() {
    let dir = tempfile::tempdir().expect("failed to create temp dir");
    let file = dir.path().join("inspect-options.gctf");
    let content = r#"--- ENDPOINT ---
example.v1.Greeter/SayHello

--- OPTIONS ---
timeout: 5
retry: 2

--- REQUEST ---
{"name": "World"}

--- RESPONSE ---
{"message": "Hello World"}
"#;
    std::fs::write(&file, content).expect("failed to write temp gctf file");

    let path = file.to_string_lossy().into_owned();
    let output = run_cli(&["inspect", &path]);

    assert!(
        output.status.success(),
        "inspect command failed\nstderr:\n{}\nstdout:\n{}",
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("OPTIONS Overrides:"));
    assert!(stdout.contains("- retry: 2"));
    assert!(stdout.contains("- timeout: 5"));
}

#[test]
fn test_check_valid_file_json_output() {
    let file = fixture_path("tests/data/gctf/valid_simple.gctf");
    let output = run_cli(&["check", &file, "--format", "json"]);
    let json = parse_json_stdout(&output);

    assert!(json.get("diagnostics").is_some());
    assert!(json.get("summary").is_some());
    assert_eq!(json["summary"]["total_files"], 1);
}

#[test]
fn test_check_missing_file_json_output() {
    let file = fixture_path("tests/data/gctf/nonexistent.gctf");
    let output = run_cli(&["check", &file, "--format", "json"]);
    assert!(!output.status.success(), "missing file should fail check");
    let json = parse_json_stdout_any_status(&output);

    assert!(!json["diagnostics"].as_array().unwrap().is_empty());
    assert_eq!(json["diagnostics"][0]["code"], "FILE_NOT_FOUND");
}

#[test]
fn test_inspect_valid_file_json_output() {
    let file = fixture_path("tests/data/gctf/valid_simple.gctf");
    let output = run_cli(&["inspect", &file, "--format", "json"]);
    let json = parse_json_stdout(&output);

    assert!(json.get("file").is_some());
    assert!(json.get("ast").is_some());
    assert!(json.get("diagnostics").is_some());
    assert!(json.get("semantic_diagnostics").is_some());
    assert!(json.get("optimization_hints").is_some());
    assert!(json.get("inferred_rpc_mode").is_some());
}

#[test]
fn test_explain_valid_file_json_output() {
    let file = fixture_path("tests/data/gctf/valid_simple.gctf");
    let output = run_cli(&["explain", &file, "--format", "json"]);
    let json = parse_json_stdout(&output);

    assert!(json.get("semantic_plan").is_some());
    assert!(json.get("optimization_trace").is_some());
    assert!(json.get("optimized_plan").is_some());
    assert!(json.get("execution_plan").is_some());
}

#[test]
fn test_inspect_json_golden_contract() {
    let file = fixture_path("tests/data/gctf/valid_simple.gctf");
    let output = run_cli(&["inspect", &file, "--format", "json"]);
    let json = parse_json_stdout(&output);

    let actual = inspect_contract_view(&json);
    let expected = serde_json::json!({
        "has_file": true,
        "parse_time_is_number": true,
        "validation_time_is_number": true,
        "sections": [
            {
                "section_type": "ENDPOINT",
                "start_line": 0,
                "end_line": 2,
                "content_kind": "single"
            },
            {
                "section_type": "REQUEST",
                "start_line": 3,
                "end_line": 7,
                "content_kind": "json"
            },
            {
                "section_type": "RESPONSE",
                "start_line": 8,
                "end_line": 11,
                "content_kind": "json"
            }
        ],
        "diagnostics": [],
        "semantic_diagnostics": [],
        "optimization_hints": [],
        "inferred_rpc_mode": "Unary"
    });

    assert_eq!(actual, expected);
}

#[test]
fn test_explain_json_golden_contract() {
    let file = fixture_path("tests/data/gctf/valid_simple.gctf");
    let output = run_cli(&["explain", &file, "--format", "json"]);
    let json = parse_json_stdout(&output);

    let actual = explain_contract_view(&json);
    let expected = serde_json::json!({
        "has_semantic_plan": true,
        "has_optimization_trace": true,
        "has_optimized_plan": true,
        "has_execution_plan": true,
        "optimization_trace": [],
        "semantic_summary": {
            "total_requests": 1,
            "total_responses": 1,
            "total_errors": 0,
            "error_expected": false,
            "assertion_blocks": 0,
            "variable_extractions": 0,
            "rpc_mode_name": "Unary"
        },
        "optimized_summary": {
            "total_requests": 1,
            "total_responses": 1,
            "total_errors": 0,
            "error_expected": false,
            "assertion_blocks": 0,
            "variable_extractions": 0,
            "rpc_mode_name": "Unary"
        },
        "execution_summary": {
            "total_requests": 1,
            "total_responses": 1,
            "total_errors": 0,
            "error_expected": false,
            "assertion_blocks": 0,
            "variable_extractions": 0,
            "rpc_mode_name": "Unary"
        },
        "semantic_target": {
            "endpoint": "example.v1.Greeter/SayHello",
            "package": "example.v1",
            "service": "Greeter",
            "method": "SayHello"
        },
        "optimized_target": {
            "endpoint": "example.v1.Greeter/SayHello",
            "package": "example.v1",
            "service": "Greeter",
            "method": "SayHello"
        },
        "execution_target": {
            "endpoint": "example.v1.Greeter/SayHello",
            "package": "example.v1",
            "service": "Greeter",
            "method": "SayHello"
        }
    });

    assert_eq!(actual, expected);
}

#[test]
fn test_inssect_sections_have_required_fields() {
    let file = fixture_path("tests/data/gctf/valid_simple.gctf");
    let output = run_cli(&["inspect", &file, "--format", "json"]);
    let json = parse_json_stdout(&output);

    let sections = json["ast"]["sections"]
        .as_array()
        .expect("sections should be array");
    for section in sections {
        assert!(section.get("section_type").is_some());
        assert!(section.get("start_line").is_some());
        assert!(section.get("end_line").is_some());
        assert!(section.get("content_kind").is_some());
    }
}

#[test]
fn test_fmt_check_mode_ok_output() {
    let file = fixture_path("tests/data/gctf/valid_simple.gctf");
    let output = run_cli(&["fmt", &file]);
    assert!(
        output.status.success(),
        "fmt command failed\nstderr:\n{}\nstdout:\n{}",
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("... OK"));
}

#[test]
fn test_fmt_check_mode_fails_when_format_needed() {
    let dir = tempfile::tempdir().expect("failed to create temp dir");
    let file = dir.path().join("needs-format.gctf");
    let content = r#"--- ENDPOINT ---
example.v1.Greeter/SayHello

--- REQUEST ---
{"name":"World"}

--- RESPONSE ---
{"message":"Hello World"}
"#;
    std::fs::write(&file, content).expect("failed to write temp gctf file");

    let path = file.to_string_lossy().into_owned();
    let output = run_cli(&["fmt", &path]);

    assert!(
        !output.status.success(),
        "fmt check mode must fail when formatting is needed\nstderr:\n{}\nstdout:\n{}",
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("[FORMAT_NEEDED]"));
}

#[test]
fn test_fmt_write_then_check_is_idempotent() {
    let dir = tempfile::tempdir().expect("failed to create temp dir");
    let file = dir.path().join("idempotent.gctf");
    let content = r#"--- ENDPOINT ---
example.v1.Greeter/SayHello

--- REQUEST ---
{"name": "World"}

--- RESPONSE ---
{"message": "Hello World"}
"#;
    std::fs::write(&file, content).expect("failed to write temp gctf file");
    let path = file.to_string_lossy().into_owned();

    let write_output = run_cli(&["fmt", "-w", &path]);
    assert!(write_output.status.success());

    let output1 = run_cli(&["fmt", &path]);
    let output2 = run_cli(&["fmt", &path]);

    let stdout1 = String::from_utf8_lossy(&output1.stdout);
    let stdout2 = String::from_utf8_lossy(&output2.stdout);

    assert_eq!(stdout1, stdout2, "Formatter should be idempotent");
}

#[test]
fn test_fmt_fails_when_check_fails() {
    let dir = tempfile::tempdir().expect("failed to create temp dir");
    let file = dir.path().join("invalid-plugin.gctf");
    let content = r#"--- ENDPOINT ---
example.v1.Greeter/SayHello

--- REQUEST ---
{"name": "World"}

--- RESPONSE ---
{"message": "Hello World"}

--- ASSERTS ---
@regexp(.message, /World/) == true
"#;
    std::fs::write(&file, content).expect("failed to write temp gctf file");

    let path = file.to_string_lossy().into_owned();
    let output = run_cli(&["fmt", &path]);

    assert!(
        !output.status.success(),
        "fmt must fail when check fails\nstderr:\n{}\nstdout:\n{}",
        String::from_utf8_lossy(&output.stderr),
        String::from_utf8_lossy(&output.stdout)
    );
}

#[test]
fn test_list_json_output() {
    let dir = fixture_path("tests/data/gctf");
    let output = run_cli(&["list", &dir, "--format", "json"]);
    let json = parse_json_stdout(&output);

    assert!(json.get("tests").is_some());
    let tests = json["tests"].as_array().expect("tests should be array");
    assert!(!tests.is_empty());

    for test in tests {
        assert!(test.get("id").is_some());
        assert!(test.get("label").is_some());
        assert!(test.get("uri").is_some());
    }
}

#[test]
fn test_list_with_range() {
    let dir = fixture_path("tests/data/gctf");
    let output = run_cli(&["list", &dir, "--format", "json", "--with-range"]);
    let json = parse_json_stdout(&output);

    let tests = json["tests"].as_array().expect("tests should be array");
    for test in tests {
        if test.get("range").is_some() {
            let range = &test["range"];
            assert!(range.get("start").is_some());
            assert!(range.get("end").is_some());
        }
    }
}
