import json
import os
import re
import sys
from pathlib import Path

from crytic_compile import CryticCompile, save_to_zip
from crytic_compile.utils.zip import load_from_zip
from deepdiff import DeepDiff
from packaging.version import parse as parse_version
from solc_select.solc_select import install_artifacts as install_solc_versions
from solc_select.solc_select import installed_versions as get_installed_solc_versions

from slither import Slither
from slither.printers.guidance.echidna import Echidna

E2E_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TEST_ROOT = os.path.join(E2E_ROOT, "solc_parsing", "test_data")


class Test:
    def __init__(
        self,
        test_file: str,
        solc_versions: list[str],
        disable_legacy: bool = False,
        solc_args: str | None = None,
    ):
        self.solc_versions = solc_versions
        self.test_file = test_file
        self.disable_legacy = disable_legacy

        versions_with_flavors: list[tuple[str, str, str]] = []
        flavors = ["compact"]
        if not self.disable_legacy:
            flavors += ["legacy"]
        for version in solc_versions:
            for flavor in flavors:
                # No legacy AST format for >0.8
                legacy_unavailable = flavor == "legacy" and parse_version(version) >= parse_version(
                    "0.8"
                )
                # No compact AST format for <0.4.12
                compact_unavailable = flavor == "compact" and parse_version(
                    version
                ) < parse_version("0.4.12")
                if legacy_unavailable or compact_unavailable:
                    continue
                versions_with_flavors.append((version, flavor, solc_args))
        self.versions_with_flavors = versions_with_flavors


def generate_output(sl: Slither) -> dict[str, dict[str, str]]:
    output = {}
    for contract in sl.contracts:
        output[contract.name] = {}

        for func_or_modifier in contract.functions + contract.modifiers:
            output[contract.name][func_or_modifier.full_name] = (
                func_or_modifier.slithir_cfg_to_dot_str(skip_expressions=True)
            )

    return output


def make_version(minor: int, patch_min: int, patch_max: int) -> list[str]:
    return [f"0.{minor}.{x}" for x in range(patch_min, patch_max + 1)]


VERSIONS_04 = make_version(4, 0, 26)
VERSIONS_05 = make_version(5, 0, 17)
VERSIONS_06 = make_version(6, 0, 12)
VERSIONS_07 = make_version(7, 0, 6)
VERSIONS_08 = make_version(8, 0, 15)

ALL_VERSIONS = VERSIONS_04 + VERSIONS_05 + VERSIONS_06 + VERSIONS_07 + VERSIONS_08

ALL_TESTS = [
    Test("using-for-0.4.0.sol", ["0.4.0"]),
    Test(
        "using-for-0.4.1.sol",
        make_version(4, 1, 26) + VERSIONS_05 + VERSIONS_06 + VERSIONS_07 + VERSIONS_08,
    ),
    Test(
        "top-level-import-0.4.0.sol",
        VERSIONS_04 + VERSIONS_05 + VERSIONS_06 + ["0.7.0"],
    ),
    Test("top-level-import-0.7.1.sol", make_version(7, 1, 6) + VERSIONS_08),
    Test("function-0.4.0.sol", make_version(4, 0, 15)),
    Test("function-0.4.16.sol", make_version(4, 16, 21)),
    Test("function-0.4.22.sol", ["0.4.22"]),
    Test("function-0.4.23.sol", make_version(4, 23, 26)),
    Test("function-0.5.0.sol", VERSIONS_05),
    # TODO: legacy is failing
    Test("function-0.6.0.sol", VERSIONS_06, disable_legacy=True),
    # TODO: failing
    # Test("function-0.7.0.sol", ["0.7.0"]),
    # TODO: legacy failing with 0.7
    Test("function-0.7.1.sol", make_version(7, 1, 6), disable_legacy=True),
    Test("function-0.7.1.sol", VERSIONS_08),
    Test(
        "top-level-import-bis-0.4.0.sol",
        VERSIONS_04 + VERSIONS_05 + VERSIONS_06 + ["0.7.0"],
    ),
    Test("top-level-import-bis-0.7.1.sol", make_version(7, 1, 6) + VERSIONS_08),
    # TODO: failing
    # Test(
    #     "variabledeclaration-0.4.0.sol",
    #     make_version(4, 0, 23),
    #     disable_legacy=True,
    # ),
    # # TODO: failing
    # Test(
    #     "variabledeclaration-0.4.24.sol",
    #     make_version(4, 24, 26),
    #     disable_legacy=True,
    # ),
    Test(
        "variabledeclaration-0.5.0.sol",
        VERSIONS_05 + VERSIONS_06 + VERSIONS_07 + VERSIONS_08,
        disable_legacy=True,
    ),
    Test("functioncall-0.4.0.sol", VERSIONS_04),
    Test(
        "functioncall-0.4.5.sol",
        make_version(4, 5, 9),
    ),
    Test(
        "functioncall-0.5.0.sol",
        make_version(5, 0, 2),
    ),
    Test(
        "functioncall-0.5.3.sol",
        make_version(5, 3, 17),
    ),
    Test(
        "functioncall-0.6.0.sol",
        make_version(6, 0, 1),
    ),
    Test(
        "functioncall-0.6.2.sol",
        make_version(6, 2, 7),
    ),
    Test(
        "functioncall-0.6.8.sol",
        make_version(6, 8, 12),
    ),
    Test(
        "functioncall-0.7.0.sol",
        VERSIONS_07,
    ),
    Test(
        "functioncall-0.8.0.sol",
        VERSIONS_08,
    ),
    Test(
        "break-all.sol",
        ALL_VERSIONS,
    ),
    Test(
        "top-level-nested-import-0.4.0.sol",
        VERSIONS_04 + VERSIONS_05 + VERSIONS_06 + ["0.7.0"],
    ),
    Test(
        "top-level-nested-import-0.7.1.sol",
        make_version(7, 1, 6) + VERSIONS_08,
    ),
    # + [f"variable_0.6.{ver}_legacy" for ver in range(5, 23)]
    # + [f"variable_0.6.{ver}_compact" for ver in range(5, 23)]
    # + [f"variable_0.7.{ver}_legacy" for ver in range(0, 2)]
    # + [f"variable_0.7.{ver}_compact" for ver in range(0, 2)]
    Test(
        "call_to_variable-all.sol",
        ALL_VERSIONS,
    ),
    Test("yul-0.4.0.sol", ["0.4.0"]),
    Test("yul-0.4.1.sol", make_version(4, 1, 10)),
    Test(
        "yul-0.4.11.sol",
        make_version(4, 11, 26) + VERSIONS_05 + VERSIONS_06,
    ),
    Test("yul-0.7.0.sol", make_version(7, 0, 4)),
    Test("yul-0.7.5.sol", make_version(7, 5, 6)),
    Test("yul-0.8.0.sol", ["0.8.33"]),
    Test("pragma-0.4.0.sol", VERSIONS_04),
    Test("pragma-0.5.0.sol", VERSIONS_05),
    Test("pragma-0.6.0.sol", VERSIONS_06),
    Test("pragma-0.7.0.sol", VERSIONS_07),
    Test("pragma-0.8.0.sol", VERSIONS_08),
    Test(
        "assembly-all.sol",
        ALL_VERSIONS,
    ),
    Test("struct-0.4.0.sol", VERSIONS_04 + VERSIONS_05),
    # TODO: legacy failing
    Test(
        "struct-0.6.0.sol",
        VERSIONS_06 + VERSIONS_07 + VERSIONS_08,
        disable_legacy=True,
    ),
    # TODO: currently failing
    # Test("emit-0.4.0.sol", VERSIONS_04),
    # Test(
    #     "emit-0.4.8.sol",
    #     make_version(4, 8, 20)
    # ),
    # Test(
    #     "emit-0.4.21.sol",
    #     make_version(4, 21, 26)
    # ),
    Test(
        "emit-0.5.0.sol",
        VERSIONS_05 + VERSIONS_06 + VERSIONS_07 + VERSIONS_08,
    ),
    # TODO: failing
    # Test("import-0.4.0.sol", VERSIONS_04),
    # Test(
    #     "import-0.4.3.sol",
    #     make_version(4, 3, 9) + VERSIONS_05 + VERSIONS_06 + VERSIONS_07 + VERSIONS_08,
    # ),
    Test("tupleexpression-0.4.0.sol", make_version(4, 0, 23)),
    Test("tupleexpression-0.4.24.sol", make_version(4, 24, 26) + VERSIONS_05),
    Test(
        "tupleexpression-0.5.3.sol",
        make_version(5, 3, 9) + VERSIONS_06 + VERSIONS_07 + VERSIONS_08,
    ),
    Test("literal-0.4.0.sol", make_version(4, 0, 9)),
    Test("literal-0.4.10.sol", make_version(4, 10, 26)),
    Test("literal-0.5.0.sol", VERSIONS_05),
    Test("literal-0.6.0.sol", VERSIONS_06),
    # TODO: failing
    # Test("literal-0.7.0.sol", VERSIONS_07 + VERSIONS_08),
    Test("memberaccess-0.4.0.sol", VERSIONS_04 + VERSIONS_05),
    Test(
        "memberaccess-0.5.3.sol",
        make_version(5, 3, 9),
    ),
    # TODO:  Legacy failing from 0.6
    Test(
        "memberaccess-0.5.3.sol",
        VERSIONS_06 + VERSIONS_07 + VERSIONS_08,
        disable_legacy=True,
    ),
    Test(
        "memberaccess-0.6.7.sol",
        ["0.6.7"],
        disable_legacy=True,
    ),
    Test(
        "memberaccess-0.6.8.sol",
        ["0.6.8"] + VERSIONS_07 + VERSIONS_08,
        disable_legacy=True,
    ),
    Test("throw-0.4.0.sol", VERSIONS_04),
    Test(
        "throw-0.5.0.sol",
        VERSIONS_05 + VERSIONS_06 + VERSIONS_07 + VERSIONS_08,
    ),
    Test(
        "top_level_variable2-0.4.0.sol",
        VERSIONS_04 + VERSIONS_05 + VERSIONS_06 + VERSIONS_07,
    ),
    Test("top_level_variable2-0.8.0.sol", VERSIONS_08),
    Test(
        "comment-all.sol",
        ALL_VERSIONS,
    ),
    Test("assignment-0.4.0.sol", VERSIONS_04),
    Test(
        "assignment-0.4.7.sol",
        make_version(4, 7, 9) + VERSIONS_05 + VERSIONS_06 + VERSIONS_07 + VERSIONS_08,
    ),
    Test(
        "event-all.sol",
        ALL_VERSIONS,
    ),
    # TODO: legacy not working
    Test(
        "indexrangeaccess-0.4.0.sol",
        VERSIONS_04 + VERSIONS_05 + ["0.6.0"],
    ),
    Test("indexrangeaccess-0.4.0.sol", ["0.6.0"], disable_legacy=True),
    Test(
        "indexrangeaccess-0.6.1.sol",
        make_version(6, 1, 12) + VERSIONS_07 + VERSIONS_08,
        disable_legacy=True,
    ),
    Test("variable-0.4.0.sol", VERSIONS_04),
    Test("variable-0.4.5.sol", make_version(4, 5, 13)),
    Test("variable-0.4.14.sol", make_version(4, 14, 15)),
    Test("variable-0.4.16.sol", make_version(4, 16, 26)),
    Test("variable-0.5.0.sol", VERSIONS_05 + make_version(6, 0, 4)),
    Test("variable-0.6.5.sol", make_version(6, 5, 8)),
    Test("variable-0.6.9.sol", make_version(6, 9, 12) + VERSIONS_07),
    Test("variable-0.8.0.sol", VERSIONS_08),
    Test(
        "continue-all.sol",
        ALL_VERSIONS,
    ),
    Test(
        "if-all.sol",
        ALL_VERSIONS,
    ),
    Test(
        "modifier-all.sol",
        VERSIONS_04 + VERSIONS_05 + VERSIONS_06,
    ),
    Test(
        "modifier-0.7.0.sol",
        VERSIONS_07 + VERSIONS_08,
    ),
    Test("library_implicit_conversion-0.4.0.sol", VERSIONS_04),
    Test(
        "library_implicit_conversion-0.5.0.sol",
        VERSIONS_05 + VERSIONS_06 + VERSIONS_07 + VERSIONS_08,
    ),
    Test("units_and_global_variables-0.4.0.sol", VERSIONS_04),
    Test("units_and_global_variables-0.5.0.sol", make_version(5, 0, 3)),
    Test("units_and_global_variables-0.5.4.sol", make_version(5, 4, 17)),
    Test("units_and_global_variables-0.6.0.sol", VERSIONS_06),
    Test("units_and_global_variables-0.7.0.sol", VERSIONS_07),
    Test("units_and_global_variables-0.8.0.sol", VERSIONS_08),
    Test("units_and_global_variables-0.8.4.sol", make_version(8, 4, 6)),
    Test("units_and_global_variables-0.8.7.sol", make_version(8, 7, 9)),
    Test("global_variables-0.8.18.sol", make_version(8, 18, 18)),
    Test(
        "push-all.sol",
        ALL_VERSIONS,
    ),
    Test(
        "indexaccess-all.sol",
        ALL_VERSIONS,
    ),
    Test("minmax-0.4.0.sol", VERSIONS_04 + VERSIONS_05 + VERSIONS_06),
    Test(
        "minmax-0.6.8.sol",
        make_version(6, 8, 9) + VERSIONS_07 + VERSIONS_08,
    ),
    Test(
        "minmax-0.8.8.sol",
        make_version(8, 8, 15),
    ),
    Test("dowhile-0.4.0.sol", VERSIONS_04),
    Test(
        "dowhile-0.4.5.sol",
        make_version(4, 5, 9) + VERSIONS_05 + VERSIONS_06 + VERSIONS_07 + VERSIONS_08,
    ),
    Test(
        "custom_error-0.4.0.sol",
        ALL_VERSIONS,
    ),
    Test("custom_error-0.8.4.sol", make_version(8, 4, 15)),
    Test("custom-error-selector.sol", make_version(8, 4, 15)),
    Test(
        "top-level-0.4.0.sol",
        VERSIONS_04 + VERSIONS_05 + VERSIONS_06 + ["0.7.0"],
    ),
    Test("top-level-0.7.1.sol", make_version(7, 1, 3)),
    Test("top-level-0.7.4.sol", make_version(7, 4, 6) + VERSIONS_08),
    Test("contract-0.4.0.sol", make_version(4, 0, 21)),
    Test("contract-0.4.22.sol", make_version(4, 22, 26) + VERSIONS_05),
    Test("contract-0.6.0.sol", VERSIONS_06 + VERSIONS_07 + VERSIONS_08),
    Test(
        "import_interface_with_struct_from_top_level-0.4.0.sol",
        VERSIONS_04 + VERSIONS_05 + VERSIONS_06 + make_version(7, 0, 5),
    ),
    Test(
        "import_interface_with_struct_from_top_level-0.7.6.sol",
        ["0.7.6"] + VERSIONS_08,
    ),
    Test("scope-0.4.0.sol", VERSIONS_04),
    Test(
        "scope-0.5.0.sol",
        VERSIONS_05 + VERSIONS_06 + VERSIONS_07 + VERSIONS_08,
    ),
    Test(
        "conditional-all.sol",
        ALL_VERSIONS,
    ),
    Test(
        "for-all.sol",
        ALL_VERSIONS,
    ),
    Test("trycatch-0.4.0.sol", VERSIONS_04 + VERSIONS_05),
    # TODO: legacy failing
    Test(
        "trycatch-0.6.0.sol",
        VERSIONS_06 + VERSIONS_07 + VERSIONS_08,
        disable_legacy=True,
    ),
    Test(
        "unchecked-0.4.0.sol",
        VERSIONS_04 + VERSIONS_05 + VERSIONS_06 + VERSIONS_07,
    ),
    Test("unchecked-0.8.0.sol", VERSIONS_08),
    Test(
        "return-all.sol",
        ALL_VERSIONS,
    ),
    Test("binaryoperation-0.4.0.sol", VERSIONS_04),
    Test(
        "binaryoperation-0.4.7.sol",
        make_version(4, 7, 9) + VERSIONS_05 + VERSIONS_06 + VERSIONS_07 + make_version(8, 0, 12),
    ),
    Test("newexpression-0.4.0.sol", VERSIONS_04),
    Test(
        "newexpression-0.5.0.sol",
        VERSIONS_05 + VERSIONS_06 + VERSIONS_07 + VERSIONS_08,
    ),
    Test(
        "enum-0.4.0.sol",
        VERSIONS_04 + VERSIONS_05 + VERSIONS_06 + VERSIONS_07,
    ),
    Test("enum-0.8.0.sol", VERSIONS_08),
    Test(
        "top_level_variable-0.4.0.sol",
        VERSIONS_04 + VERSIONS_05 + VERSIONS_06 + VERSIONS_07,
    ),
    Test("top_level_variable-0.8.0.sol", VERSIONS_08),
    Test("unaryexpression-0.4.0.sol", VERSIONS_04),
    Test(
        "unaryexpression-0.5.0.sol",
        VERSIONS_05 + VERSIONS_06 + VERSIONS_07 + VERSIONS_08,
    ),
    Test(
        "while-all.sol",
        ALL_VERSIONS,
    ),
    Test(
        "complex_imports/import_free/Caller.sol",
        ["0.8.2"],
    ),
    Test("custom_error_with_state_variable.sol", make_version(8, 4, 12)),
    Test("complex_imports/import_aliases/test.sol", VERSIONS_08),
    # 0.8.9 crashes on our testcase
    Test(
        "user_defined_value_type/user_defined_types-0.8.8.sol", ["0.8.8"] + make_version(8, 10, 15)
    ),
    Test("user_defined_value_type/argument-0.8.8.sol", ["0.8.8"] + make_version(8, 10, 15)),
    Test("user_defined_value_type/calldata-0.8.8.sol", ["0.8.8"] + make_version(8, 10, 15)),
    Test("user_defined_value_type/constant-0.8.8.sol", ["0.8.8"] + make_version(8, 10, 15)),
    Test("user_defined_value_type/erc20-0.8.8.sol", ["0.8.8"] + make_version(8, 10, 15)),
    Test("user_defined_value_type/in_parenthesis-0.8.8.sol", ["0.8.8"] + make_version(8, 10, 15)),
    Test("user_defined_value_type/top-level-0.8.8.sol", ["0.8.8"] + make_version(8, 10, 15)),
    Test("user_defined_value_type/using-for-0.8.8.sol", ["0.8.8"] + make_version(8, 10, 15)),
    Test("user_defined_value_type/abi-decode-fixed-array.sol", ["0.8.8"] + make_version(8, 10, 15)),
    Test("bytes_call.sol", ["0.8.12"]),
    Test("modifier_identifier_path.sol", VERSIONS_08),
    Test("free_functions/libraries_from_free.sol", ["0.8.12"]),
    Test("free_functions/new_operator.sol", ["0.8.12"]),
    Test("free_functions/library_constant_function_collision.sol", ["0.8.12"]),
    Test("ternary-with-max.sol", ["0.8.15"]),
    Test("using-for-1-0.8.0.sol", ["0.8.15"]),
    Test("using-for-2-0.8.0.sol", ["0.8.15"]),
    Test("using-for-3-0.8.0.sol", ["0.8.15"]),
    Test("using-for-4-0.8.0.sol", ["0.8.15"]),
    Test("using-for-in-library-0.8.0.sol", ["0.8.15"]),
    Test("using-for-alias-contract-0.8.0.sol", ["0.8.15"]),
    Test("using-for-alias-top-level-0.8.0.sol", ["0.8.15"]),
    Test("using-for-functions-list-1-0.8.0.sol", ["0.8.15"]),
    Test("using-for-functions-list-2-0.8.0.sol", ["0.8.15"]),
    Test("using-for-functions-list-3-0.8.0.sol", ["0.8.15"]),
    Test("using-for-functions-list-4-0.8.0.sol", ["0.8.15"]),
    Test("using-for-global-0.8.0.sol", ["0.8.15"]),
    Test("using-for-this-contract.sol", ["0.8.15"]),
    Test("library_event-0.8.16.sol", ["0.8.16"]),
    Test("top-level-struct-0.8.0.sol", ["0.8.0"]),
    Test("yul-top-level-0.8.0.sol", ["0.8.0"]),
    Test("complex_imports/import_aliases_issue_1319/test.sol", ["0.5.12"]),
    Test("yul-state-constant-access.sol", ["0.8.16"]),
    Test("negate-unary-element.sol", ["0.8.16"]),
    Test(
        "assembly-functions.sol",
        ["0.6.9", "0.7.6", "0.8.16"],
    ),
    Test("user_defined_operators-0.8.19.sol", ["0.8.19"]),
    Test("aliasing/main.sol", ["0.8.19"]),
    Test("aliasing/alias-unit-NewContract.sol", ["0.8.19"]),
    Test("aliasing/alias-symbol-NewContract.sol", ["0.8.19"]),
    Test("type-aliases.sol", ["0.8.19"]),
    Test("enum-max-min.sol", ["0.8.19"]),
    Test("event-top-level.sol", ["0.8.22"]),
    Test("solidity-0.8.24.sol", ["0.8.24"], solc_args="--evm-version cancun"),
    Test("scope/inherited_function_scope.sol", ["0.8.24"]),
    Test("using_for_global_user_defined_operator_1.sol", ["0.8.24"]),
    Test("require-error.sol", ["0.8.27"]),
    Test("yul-solady.sol", ["0.8.27"]),
]
# create the output folder if needed
try:
    os.mkdir("test_artifacts")
except OSError:
    pass


def pytest_generate_tests(metafunc):
    test_cases = []
    for test_item in ALL_TESTS:
        for version, flavor, _ in test_item.versions_with_flavors:
            test_cases.append((test_item.test_file, version, flavor))
    metafunc.parametrize("test_file, version, flavor", test_cases)


class TestASTParsing:
    def test_parsing(self, test_file, version, flavor):
        actual = os.path.join(TEST_ROOT, "compile", f"{test_file}-{version}-{flavor}.zip")
        expected = os.path.join(TEST_ROOT, "expected", f"{test_file}-{version}-{flavor}.json")

        cc = load_from_zip(actual)[0]

        # Validate that the AST is in the expected format
        for compiled in cc.compilation_units.values():
            for source in compiled.source_units.values():
                if source.ast:
                    if flavor == "compact":
                        assert "nodeType" in source.ast, "AST is not compact"
                    else:
                        assert "nodeType" not in source.ast, "AST is not legacy"

        sl = Slither(
            cc,
            solc_force_legacy_json=flavor == "legacy",
            disallow_partial=True,
            skip_analyze=True,
        )

        actual = generate_output(sl)

        assert os.path.isfile(expected), f"Expected file {expected} does not exist"
        with open(expected, encoding="utf8") as f:
            expected = json.load(f)

        diff = DeepDiff(expected, actual, ignore_order=True, verbose_level=2, view="tree")
        if diff:
            for change in diff.get("values_changed", []):
                path_list = re.findall(r"\['(.*?)'\]", change.path())
                path = "_".join(path_list)
                with open(
                    f"test_artifacts/{test_file}_{path}_expected.dot",
                    "w",
                    encoding="utf8",
                ) as f:
                    f.write(change.t1)
                with open(
                    f"test_artifacts/{test_file}_{version}_{flavor}_{path}_actual.dot",
                    "w",
                    encoding="utf8",
                ) as f:
                    f.write(change.t2)

        assert not diff, diff.pretty()

        sl = Slither(cc, solc_force_legacy_json=flavor == "legacy", disallow_partial=True)
        sl.register_printer(Echidna)
        sl.run_printers()


def _generate_test(test_item: Test, skip_existing=False):
    flavors = ["compact"]
    if not test_item.disable_legacy:
        flavors += ["legacy"]
    for version, flavor, _ in test_item.versions_with_flavors:
        test_file = os.path.join(
            TEST_ROOT, "compile", f"{test_item.test_file}-{version}-{flavor}.zip"
        )
        expected_file = os.path.join(
            TEST_ROOT, "expected", f"{test_item.test_file}-{version}-{flavor}.json"
        )

        if skip_existing:
            if os.path.isfile(expected_file):
                continue

        try:
            cc = load_from_zip(test_file)[0]
            sl = Slither(
                cc,
                solc_force_legacy_json=flavor == "legacy",
                disallow_partial=True,
                skip_analyze=True,
            )

        except Exception as e:
            print(e)
            print(test_item)
            print(f"{expected_file} failed")
            continue

        actual = generate_output(sl)
        print(f"Generate {expected_file}")

        Path(expected_file).parents[0].mkdir(parents=True, exist_ok=True)

        with open(expected_file, "w", encoding="utf8") as f:
            json.dump(actual, f, indent="  ")


def set_solc(version: str):
    env = dict(os.environ)
    env["SOLC_VERSION"] = version
    os.environ.clear()
    os.environ.update(env)


def _generate_compile(test_item: Test, skip_existing=False):
    for version, flavor, solc_args in test_item.versions_with_flavors:
        test_file = os.path.join(TEST_ROOT, test_item.test_file)
        expected_file = os.path.join(
            TEST_ROOT, "compile", f"{test_item.test_file}-{version}-{flavor}.zip"
        )

        if skip_existing:
            if os.path.isfile(expected_file):
                continue

        set_solc(version)
        print(f"Compiled to {expected_file}")
        cc = CryticCompile(
            test_file, solc_force_legacy_json=flavor == "legacy", solc_args=solc_args
        )

        Path(expected_file).parents[0].mkdir(parents=True, exist_ok=True)

        save_to_zip([cc], expected_file)


if __name__ == "__main__":
    required_solcs = set()
    for test in ALL_TESTS:
        required_solcs |= set(test.solc_versions)
    installed_solcs = set(get_installed_solc_versions())
    missing_solcs = list(required_solcs - installed_solcs)
    if missing_solcs:
        install_solc_versions(missing_solcs)

    if len(sys.argv) != 2 or sys.argv[1] not in ["--generate", "--overwrite", "--compile"]:
        print(
            "To generate the missing json artifacts run\n\tpython tests/test_ast_parsing.py --generate"
        )
        print(
            "To re-generate all the json artifacts run\n\tpython tests/test_ast_parsing.py --overwrite"
        )
        print("To compile json artifacts run\n\tpython tests/test_ast_parsing.py --compile")
        print("\tThis will overwrite the previous json files.")
    elif sys.argv[1] == "--generate":
        for next_test in ALL_TESTS:
            _generate_test(next_test, skip_existing=True)
    elif sys.argv[1] == "--overwrite":
        for next_test in ALL_TESTS:
            _generate_test(next_test)
    elif sys.argv[1] == "--compile":
        for next_test in ALL_TESTS:
            _generate_compile(next_test, skip_existing=True)
