import argparse
import json
import os
import shutil
import subprocess
import sys
from pathlib import Path
from typing import Tuple

from ci.paths import PROJECT_ROOT
from ci.running_process import RunningProcess

BUILD_DIR = PROJECT_ROOT / "tests" / ".build"
BUILD_DIR.mkdir(parents=True, exist_ok=True)


def clean_build_directory():
    print("Cleaning build directory...")
    shutil.rmtree(BUILD_DIR, ignore_errors=True)
    BUILD_DIR.mkdir(parents=True, exist_ok=True)
    print("Build directory cleaned.")


HERE = Path(__file__).resolve().parent

WASM_BUILD = False
USE_ZIG = False
USE_CLANG = False


def _has_system_clang_compiler() -> bool:
    CLANG = shutil.which("clang")
    CLANGPP = shutil.which("clang++")
    LLVM_AR = shutil.which("llvm-ar")
    return CLANG is not None and CLANGPP is not None and LLVM_AR is not None


def use_clang_compiler() -> Tuple[Path, Path, Path]:
    assert _has_system_clang_compiler(), "Clang system compiler not found"
    CLANG = shutil.which("clang")
    CLANGPP = shutil.which("clang++")
    LLVM_AR = shutil.which("llvm-ar")
    assert CLANG is not None, "clang compiler not found"
    assert CLANGPP is not None, "clang++ compiler not found"
    assert LLVM_AR is not None, "llvm-ar not found"
    # Set environment variables for C and C++ compilers
    os.environ["CC"] = CLANG
    os.environ["CXX"] = CLANGPP
    os.environ["AR"] = LLVM_AR

    os.environ["CXXFLAGS"] = os.environ.get("CXXFLAGS", "") + " -ferror-limit=1"
    os.environ["CFLAGS"] = os.environ.get("CFLAGS", "") + " -ferror-limit=1"

    if WASM_BUILD:
        wasm_flags = [
            "--target=wasm32",
            "-O3",
            "-flto",
            # "-nostdlib",
            # "-Wl,--no-entry",
            # "-Wl,--export-all",
            # "-Wl,--lto-O3",
            # "-Wl,-z,stack-size=8388608",  # 8 * 1024 * 1024 (8MiB)
        ]
        os.environ["CFLAGS"] = " ".join(wasm_flags)
        os.environ["CXXFLAGS"] = " ".join(wasm_flags)

    print(f"CC: {CLANG}")
    print(f"CXX: {CLANGPP}")
    print(f"AR: {LLVM_AR}")

    return Path(CLANG), Path(CLANGPP), Path(LLVM_AR)


def use_zig_compiler() -> Tuple[Path, Path, Path]:
    assert 0 == os.system(
        "uv run python -m ziglang version"
    ), "Zig-clang compiler not found"
    uv_path_str: str | None = shutil.which("uv")
    assert uv_path_str is not None, "uv not found in PATH"
    uv_path = Path(uv_path_str).resolve()
    zig_command = f'"{uv_path}" run python -m ziglang'
    # We are going to build up shell scripts that look like cc, c++, and ar. It will contain the actual build command.
    CC_PATH = BUILD_DIR / "cc"
    CXX_PATH = BUILD_DIR / "c++"
    AR_PATH = BUILD_DIR / "ar"
    if sys.platform == "win32":
        CC_PATH = CC_PATH.with_suffix(".cmd")
        CXX_PATH = CXX_PATH.with_suffix(".cmd")
        AR_PATH = AR_PATH.with_suffix(".cmd")
        CC_PATH.write_text(f"@echo off\n{zig_command} cc %* 2>&1\n")
        CXX_PATH.write_text(f"@echo off\n{zig_command} c++ %* 2>&1\n")
        AR_PATH.write_text(f"@echo off\n{zig_command} ar %* 2>&1\n")
    else:
        cc_cmd = f'#!/bin/bash\n{zig_command} cc "$@"\n'
        cxx_cmd = f'#!/bin/bash\n{zig_command} c++ "$@"\n'
        ar_cmd = f'#!/bin/bash\n{zig_command} ar "$@"\n'
        CC_PATH.write_text(cc_cmd)
        CXX_PATH.write_text(cxx_cmd)
        AR_PATH.write_text(ar_cmd)
        CC_PATH.chmod(0o755)
        CXX_PATH.chmod(0o755)
        AR_PATH.chmod(0o755)

    # if WASM_BUILD:
    #     wasm_flags = [
    #         # "--target=wasm32",
    #         # "-O3",
    #         # "-flto",
    #         # "-nostdlib",
    #         "-Wl,--no-entry",
    #         # "-Wl,--export-all",
    #         # "-Wl,--lto-O3",
    #         "-Wl,-z,stack-size=8388608",  # 8 * 1024 * 1024 (8MiB)
    #     ]
    # os.environ["CFLAGS"] = " ".join(wasm_flags)
    # os.environ["CXXFLAGS"] = " ".join(wasm_flags)

    cc, cxx = CC_PATH, CXX_PATH
    # use the system path, so on windows this looks like "C:\Program Files\Zig\zig.exe"
    cc_path: Path | str = cc.resolve()
    cxx_path: Path | str = cxx.resolve()
    if sys.platform == "win32":
        cc_path = str(cc_path).replace("/", "\\")
        cxx_path = str(cxx_path).replace("/", "\\")

    # print out the paths
    print(f"CC: {cc_path}")
    print(f"CXX: {cxx_path}")
    print(f"AR: {AR_PATH}")
    # sys.exit(1)

    # Set environment variables for C and C++ compilers
    os.environ["CC"] = str(cc_path)
    os.environ["CXX"] = str(cxx_path)
    os.environ["AR"] = str(AR_PATH)
    return CC_PATH, CXX_PATH, AR_PATH


def run_command(command: str, cwd: Path | None = None) -> None:
    process = RunningProcess(command, cwd=cwd)
    process.wait()
    if process.returncode != 0:
        print(f"{Path(__file__).name}: Error executing command: {command}")
        sys.exit(1)


def compile_fastled(specific_test: str | None = None) -> None:
    if USE_ZIG:
        print("USING ZIG COMPILER")
        rtn = subprocess.run(
            "python -m ziglang version", shell=True, capture_output=True
        ).returncode
        zig_is_installed = rtn == 0
        assert (
            zig_is_installed
        ), 'Zig compiler not when using "python -m ziglang version" command'
        use_zig_compiler()
    elif USE_CLANG:
        print("USING CLANG COMPILER")
        use_clang_compiler()

    cmake_configure_command_list: list[str] = [
        "cmake",
        "-S",
        str(PROJECT_ROOT / "tests"),
        "-B",
        str(BUILD_DIR),
        "-G",
        "Ninja",
        "-DCMAKE_VERBOSE_MAKEFILE=ON",
        "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
    ]

    if WASM_BUILD:
        cmake_configure_command_list.extend(
            [
                "-DCMAKE_C_COMPILER_TARGET=wasm32-wasi",
                "-DCMAKE_CXX_COMPILER_TARGET=wasm32-wasi",
                "-DCMAKE_C_COMPILER_WORKS=TRUE",
                "-DCMAKE_CXX_COMPILER_WORKS=TRUE",
                "-DCMAKE_SYSTEM_NAME=Generic",
                "-DCMAKE_CROSSCOMPILING=TRUE",
                "-DCMAKE_EXE_LINKER_FLAGS=-Wl,--no-entry -Wl,--export-all -Wl,--lto-O3 -Wl,-z,stack-size=8388608",
            ]
        )

    cmake_configure_command = subprocess.list2cmdline(cmake_configure_command_list)
    run_command(cmake_configure_command, cwd=BUILD_DIR)

    # Build the project
    if specific_test:
        cmake_build_command = f"cmake --build {BUILD_DIR} --target test_{specific_test}"
    else:
        cmake_build_command = f"cmake --build {BUILD_DIR}"
    run_command(cmake_build_command)

    print("FastLED library compiled successfully.")


def parse_arguments():
    parser = argparse.ArgumentParser(
        description="Compile FastLED library with different compiler options."
    )
    parser.add_argument("--use-zig", action="store_true", help="Use Zig compiler")
    parser.add_argument("--use-clang", action="store_true", help="Use Clang compiler")
    parser.add_argument("--wasm", action="store_true", help="Build for WebAssembly")
    parser.add_argument(
        "--clean",
        action="store_true",
        help="Clean the build directory before compiling",
    )
    parser.add_argument(
        "--test",
        help="Specific test to compile (without test_ prefix)",
    )
    return parser.parse_args()


def get_build_info(args: argparse.Namespace) -> dict[str, str | dict[str, str]]:
    return {
        "USE_ZIG": str(USE_ZIG),
        "USE_CLANG": str(USE_CLANG),
        "WASM_BUILD": str(WASM_BUILD),
        "CC": os.environ.get("CC", ""),
        "CXX": os.environ.get("CXX", ""),
        "AR": os.environ.get("AR", ""),
        "CFLAGS": os.environ.get("CFLAGS", ""),
        "CXXFLAGS": os.environ.get("CXXFLAGS", ""),
        "ARGS": {
            "use_zig": str(args.use_zig),
            "use_clang": str(args.use_clang),
            "wasm": str(args.wasm),
        },
    }


def should_clean_build(build_info: dict[str, str | dict[str, str]]) -> bool:
    build_info_file = BUILD_DIR / "build_info.json"
    if not build_info_file.exists():
        return True

    with open(build_info_file, "r") as f:
        old_build_info = json.load(f)

    return old_build_info != build_info


def update_build_info(build_info: dict[str, str | dict[str, str]]):
    build_info_file = BUILD_DIR / "build_info.json"
    with open(build_info_file, "w") as f:
        json.dump(build_info, f, indent=2)


def main() -> None:
    global USE_ZIG, USE_CLANG, WASM_BUILD

    args = parse_arguments()
    USE_ZIG = args.use_zig  # use Zig's clang compiler
    USE_CLANG = args.use_clang  # Use pure Clang for WASM builds
    WASM_BUILD = args.wasm

    using_gcc = not USE_ZIG and not USE_CLANG and not WASM_BUILD
    if using_gcc:
        if not shutil.which("g++"):
            print(
                "gcc compiler not found in PATH, falling back zig's built in clang compiler"
            )
            USE_ZIG = True
            USE_CLANG = False

    if USE_CLANG:
        if not _has_system_clang_compiler():
            print(
                "Clang compiler not found in PATH, falling back to Zig-clang compiler"
            )
            USE_ZIG = True
            USE_CLANG = False

    os.chdir(str(HERE))
    print(f"Current directory: {Path('.').absolute()}")

    build_info = get_build_info(args)
    if args.clean or should_clean_build(build_info):
        clean_build_directory()

    compile_fastled(args.test)
    update_build_info(build_info)
    print("FastLED library compiled successfully.")


if __name__ == "__main__":
    main()
