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

const LOWERING_FIXTURES: &[LoweringFixture] = &[
    LoweringFixture {
        name: "add",
        source: "../examples/add.slo",
        surface_snapshot: "../tests/add.surface.lower",
        checked_snapshot: "../tests/add.checked.lower",
    },
    LoweringFixture {
        name: "top-level-test",
        source: "../tests/top-level-test.slo",
        surface_snapshot: "../tests/top-level-test.surface.lower",
        checked_snapshot: "../tests/top-level-test.checked.lower",
    },
    LoweringFixture {
        name: "type-aliases",
        source: "../tests/type-aliases.slo",
        surface_snapshot: "../tests/type-aliases.surface.lower",
        checked_snapshot: "../tests/type-aliases.checked.lower",
    },
    LoweringFixture {
        name: "comments",
        source: "../tests/comments.slo",
        surface_snapshot: "../tests/comments.surface.lower",
        checked_snapshot: "../tests/comments.checked.lower",
    },
    LoweringFixture {
        name: "formatter-stability-v1",
        source: "../tests/formatter-stability-v1.fmt",
        surface_snapshot: "../tests/formatter-stability-v1.surface.lower",
        checked_snapshot: "../tests/formatter-stability-v1.checked.lower",
    },
    LoweringFixture {
        name: "unsafe",
        source: "../tests/unsafe.slo",
        surface_snapshot: "../tests/unsafe.surface.lower",
        checked_snapshot: "../tests/unsafe.checked.lower",
    },
    LoweringFixture {
        name: "local-variables",
        source: "../examples/local-variables.slo",
        surface_snapshot: "../tests/local-variables.surface.lower",
        checked_snapshot: "../tests/local-variables.checked.lower",
    },
    LoweringFixture {
        name: "composite-locals",
        source: "../examples/composite-locals.slo",
        surface_snapshot: "../tests/composite-locals.surface.lower",
        checked_snapshot: "../tests/composite-locals.checked.lower",
    },
    LoweringFixture {
        name: "composite-struct-fields",
        source: "../examples/composite-struct-fields.slo",
        surface_snapshot: "../tests/composite-struct-fields.surface.lower",
        checked_snapshot: "../tests/composite-struct-fields.checked.lower",
    },
    LoweringFixture {
        name: "array-struct-fields",
        source: "../examples/array-struct-fields.slo",
        surface_snapshot: "../tests/array-struct-fields.surface.lower",
        checked_snapshot: "../tests/array-struct-fields.checked.lower",
    },
    LoweringFixture {
        name: "array-struct-elements",
        source: "../examples/array-struct-elements.slo",
        surface_snapshot: "../tests/array-struct-elements.surface.lower",
        checked_snapshot: "../tests/array-struct-elements.checked.lower",
    },
    LoweringFixture {
        name: "if",
        source: "../examples/if.slo",
        surface_snapshot: "../tests/if.surface.lower",
        checked_snapshot: "../tests/if.checked.lower",
    },
    LoweringFixture {
        name: "while",
        source: "../examples/while.slo",
        surface_snapshot: "../tests/while.surface.lower",
        checked_snapshot: "../tests/while.checked.lower",
    },
    LoweringFixture {
        name: "struct",
        source: "../examples/struct.slo",
        surface_snapshot: "../tests/struct.surface.lower",
        checked_snapshot: "../tests/struct.checked.lower",
    },
    LoweringFixture {
        name: "struct-value-flow",
        source: "../examples/struct-value-flow.slo",
        surface_snapshot: "../tests/struct-value-flow.surface.lower",
        checked_snapshot: "../tests/struct-value-flow.checked.lower",
    },
    LoweringFixture {
        name: "enum-struct-fields",
        source: "../examples/enum-struct-fields.slo",
        surface_snapshot: "../tests/enum-struct-fields.surface.lower",
        checked_snapshot: "../tests/enum-struct-fields.checked.lower",
    },
    LoweringFixture {
        name: "enum-payload-direct-scalars",
        source: "../examples/enum-payload-direct-scalars.slo",
        surface_snapshot: "../tests/enum-payload-direct-scalars.surface.lower",
        checked_snapshot: "../tests/enum-payload-direct-scalars.checked.lower",
    },
    LoweringFixture {
        name: "enum-payload-structs",
        source: "../examples/enum-payload-structs.slo",
        surface_snapshot: "../tests/enum-payload-structs.surface.lower",
        checked_snapshot: "../tests/enum-payload-structs.checked.lower",
    },
    LoweringFixture {
        name: "primitive-struct-fields",
        source: "../examples/primitive-struct-fields.slo",
        surface_snapshot: "../tests/primitive-struct-fields.surface.lower",
        checked_snapshot: "../tests/primitive-struct-fields.checked.lower",
    },
    LoweringFixture {
        name: "numeric-struct-fields",
        source: "../examples/numeric-struct-fields.slo",
        surface_snapshot: "../tests/numeric-struct-fields.surface.lower",
        checked_snapshot: "../tests/numeric-struct-fields.checked.lower",
    },
    LoweringFixture {
        name: "array",
        source: "../examples/array.slo",
        surface_snapshot: "../tests/array.surface.lower",
        checked_snapshot: "../tests/array.checked.lower",
    },
    LoweringFixture {
        name: "array-direct-scalars",
        source: "../examples/array-direct-scalars.slo",
        surface_snapshot: "../tests/array-direct-scalars.surface.lower",
        checked_snapshot: "../tests/array-direct-scalars.checked.lower",
    },
    LoweringFixture {
        name: "array-direct-scalars-value-flow",
        source: "../examples/array-direct-scalars-value-flow.slo",
        surface_snapshot: "../tests/array-direct-scalars-value-flow.surface.lower",
        checked_snapshot: "../tests/array-direct-scalars-value-flow.checked.lower",
    },
    LoweringFixture {
        name: "array-enum",
        source: "../examples/array-enum.slo",
        surface_snapshot: "../tests/array-enum.surface.lower",
        checked_snapshot: "../tests/array-enum.checked.lower",
    },
    LoweringFixture {
        name: "array-string",
        source: "../examples/array-string.slo",
        surface_snapshot: "../tests/array-string.surface.lower",
        checked_snapshot: "../tests/array-string.checked.lower",
    },
    LoweringFixture {
        name: "array-string-value-flow",
        source: "../examples/array-string-value-flow.slo",
        surface_snapshot: "../tests/array-string-value-flow.surface.lower",
        checked_snapshot: "../tests/array-string-value-flow.checked.lower",
    },
    LoweringFixture {
        name: "array-value-flow",
        source: "../examples/array-value-flow.slo",
        surface_snapshot: "../tests/array-value-flow.surface.lower",
        checked_snapshot: "../tests/array-value-flow.checked.lower",
    },
    LoweringFixture {
        name: "option-result",
        source: "../examples/option-result.slo",
        surface_snapshot: "../tests/option-result.surface.lower",
        checked_snapshot: "../tests/option-result.checked.lower",
    },
    LoweringFixture {
        name: "option-result-flow",
        source: "../examples/option-result-flow.slo",
        surface_snapshot: "../tests/option-result-flow.surface.lower",
        checked_snapshot: "../tests/option-result-flow.checked.lower",
    },
    LoweringFixture {
        name: "option-result-payload",
        source: "../examples/option-result-payload.slo",
        surface_snapshot: "../tests/option-result-payload.surface.lower",
        checked_snapshot: "../tests/option-result-payload.checked.lower",
    },
    LoweringFixture {
        name: "option-result-match",
        source: "../examples/option-result-match.slo",
        surface_snapshot: "../tests/option-result-match.surface.lower",
        checked_snapshot: "../tests/option-result-match.checked.lower",
    },
    LoweringFixture {
        name: "string-print",
        source: "../examples/string-print.slo",
        surface_snapshot: "../tests/string-print.surface.lower",
        checked_snapshot: "../tests/string-print.checked.lower",
    },
    LoweringFixture {
        name: "string-value-flow",
        source: "../examples/string-value-flow.slo",
        surface_snapshot: "../tests/string-value-flow.surface.lower",
        checked_snapshot: "../tests/string-value-flow.checked.lower",
    },
    LoweringFixture {
        name: "print-bool",
        source: "../examples/print-bool.slo",
        surface_snapshot: "../tests/print-bool.surface.lower",
        checked_snapshot: "../tests/print-bool.checked.lower",
    },
    LoweringFixture {
        name: "random",
        source: "../examples/random.slo",
        surface_snapshot: "../tests/random.surface.lower",
        checked_snapshot: "../tests/random.checked.lower",
    },
    LoweringFixture {
        name: "stdin-result",
        source: "../examples/stdin-result.slo",
        surface_snapshot: "../tests/stdin-result.surface.lower",
        checked_snapshot: "../tests/stdin-result.checked.lower",
    },
    LoweringFixture {
        name: "string-parse-i32-result",
        source: "../examples/string-parse-i32-result.slo",
        surface_snapshot: "../tests/string-parse-i32-result.surface.lower",
        checked_snapshot: "../tests/string-parse-i32-result.checked.lower",
    },
    LoweringFixture {
        name: "string-parse-i64-result",
        source: "../examples/string-parse-i64-result.slo",
        surface_snapshot: "../tests/string-parse-i64-result.surface.lower",
        checked_snapshot: "../tests/string-parse-i64-result.checked.lower",
    },
    LoweringFixture {
        name: "string-parse-f64-result",
        source: "../examples/string-parse-f64-result.slo",
        surface_snapshot: "../tests/string-parse-f64-result.surface.lower",
        checked_snapshot: "../tests/string-parse-f64-result.checked.lower",
    },
    LoweringFixture {
        name: "string-parse-bool-result",
        source: "../examples/string-parse-bool-result.slo",
        surface_snapshot: "../tests/string-parse-bool-result.surface.lower",
        checked_snapshot: "../tests/string-parse-bool-result.checked.lower",
    },
    LoweringFixture {
        name: "result-helpers",
        source: "../examples/result-helpers.slo",
        surface_snapshot: "../tests/result-helpers.surface.lower",
        checked_snapshot: "../tests/result-helpers.checked.lower",
    },
    LoweringFixture {
        name: "standard-runtime",
        source: "../examples/standard-runtime.slo",
        surface_snapshot: "../tests/standard-runtime.surface.lower",
        checked_snapshot: "../tests/standard-runtime.checked.lower",
    },
    LoweringFixture {
        name: "owned-string-concat",
        source: "../examples/owned-string-concat.slo",
        surface_snapshot: "../tests/owned-string-concat.surface.lower",
        checked_snapshot: "../tests/owned-string-concat.checked.lower",
    },
    LoweringFixture {
        name: "standard-io-host-env",
        source: "../tests/standard-io-host-env.slo",
        surface_snapshot: "../tests/standard-io-host-env.surface.lower",
        checked_snapshot: "../tests/standard-io-host-env.checked.lower",
    },
    LoweringFixture {
        name: "host-io-result",
        source: "../tests/host-io-result.slo",
        surface_snapshot: "../tests/host-io-result.surface.lower",
        checked_snapshot: "../tests/host-io-result.checked.lower",
    },
    LoweringFixture {
        name: "integer-to-string",
        source: "../examples/integer-to-string.slo",
        surface_snapshot: "../tests/integer-to-string.surface.lower",
        checked_snapshot: "../tests/integer-to-string.checked.lower",
    },
    LoweringFixture {
        name: "f64-to-string",
        source: "../examples/f64-to-string.slo",
        surface_snapshot: "../tests/f64-to-string.surface.lower",
        checked_snapshot: "../tests/f64-to-string.checked.lower",
    },
    LoweringFixture {
        name: "f64-to-i32-result",
        source: "../examples/f64-to-i32-result.slo",
        surface_snapshot: "../tests/f64-to-i32-result.surface.lower",
        checked_snapshot: "../tests/f64-to-i32-result.checked.lower",
    },
    LoweringFixture {
        name: "f64-to-i64-result",
        source: "../examples/f64-to-i64-result.slo",
        surface_snapshot: "../tests/f64-to-i64-result.surface.lower",
        checked_snapshot: "../tests/f64-to-i64-result.checked.lower",
    },
    LoweringFixture {
        name: "checked-i64-to-i32-conversion",
        source: "../examples/checked-i64-to-i32-conversion.slo",
        surface_snapshot: "../tests/checked-i64-to-i32-conversion.surface.lower",
        checked_snapshot: "../tests/checked-i64-to-i32-conversion.checked.lower",
    },
    LoweringFixture {
        name: "vec-i32",
        source: "../examples/vec-i32.slo",
        surface_snapshot: "../tests/vec-i32.surface.lower",
        checked_snapshot: "../tests/vec-i32.checked.lower",
    },
];

#[test]
fn promoted_fixtures_print_expected_surface_ast() {
    for fixture in LOWERING_FIXTURES {
        assert_lowering_fixture(fixture, LoweringMode::Surface);
    }
}

#[test]
fn promoted_fixtures_print_expected_checked_ast() {
    for fixture in LOWERING_FIXTURES {
        assert_lowering_fixture(fixture, LoweringMode::Checked);
    }
}

struct LoweringFixture {
    name: &'static str,
    source: &'static str,
    surface_snapshot: &'static str,
    checked_snapshot: &'static str,
}

#[derive(Clone, Copy)]
enum LoweringMode {
    Surface,
    Checked,
}

impl LoweringMode {
    fn flag(self) -> &'static str {
        match self {
            LoweringMode::Surface => "--inspect-lowering=surface",
            LoweringMode::Checked => "--inspect-lowering=checked",
        }
    }

    fn snapshot(self, fixture: &LoweringFixture) -> &'static str {
        match self {
            LoweringMode::Surface => fixture.surface_snapshot,
            LoweringMode::Checked => fixture.checked_snapshot,
        }
    }
}

fn assert_lowering_fixture(fixture: &LoweringFixture, mode: LoweringMode) {
    let expected =
        fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join(mode.snapshot(fixture)))
            .expect("read lowering inspector fixture");
    let output = run_lowering_inspector(mode.flag(), fixture.source);

    assert_success_output(output, expected, fixture.name, mode.flag());
}

fn run_lowering_inspector(mode: &str, fixture: &str) -> std::process::Output {
    let compiler = env!("CARGO_BIN_EXE_glagol");
    let fixture = Path::new(fixture);

    Command::new(compiler)
        .arg(mode)
        .arg(fixture)
        .current_dir(Path::new(env!("CARGO_MANIFEST_DIR")))
        .output()
        .unwrap_or_else(|err| panic!("run glagol {} on fixture: {}", mode, err))
}

fn assert_success_output(output: std::process::Output, expected: String, name: &str, mode: &str) {
    assert!(
        output.status.success(),
        "lowering inspector failed for `{}` `{}`\nstdout:\n{}\nstderr:\n{}",
        name,
        mode,
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr),
    );

    let actual = String::from_utf8(output.stdout).expect("lowering inspector output is UTF-8");
    assert_eq!(
        actual, expected,
        "lowering inspector fixture drifted for `{}` `{}`",
        name, mode
    );
    assert!(
        output.stderr.is_empty(),
        "lowering inspector wrote stderr for `{}` `{}`:\n{}",
        name,
        mode,
        String::from_utf8_lossy(&output.stderr),
    );
}
