import sys
from pathlib import Path
from typing import Optional, Tuple

import cffi  # type: ignore

from . import hostfxr, mono, netfx

__all__ = ["ffi", "load_hostfxr", "load_mono", "load_netfx"]

ffi = cffi.FFI()  # type: ignore

for cdef in hostfxr.cdef + mono.cdef + netfx.cdef:
    ffi.cdef(cdef)


def load_hostfxr(dotnet_root: Path):
    hostfxr_name = _get_dll_name("hostfxr")
    dotnet_root = dotnet_root.absolute()

    # Find all hostfxr versions by looking for the library file in version subdirectories
    hostfxr_path = dotnet_root / "host" / "fxr"
    hostfxr_paths = hostfxr_path.glob(f"*/{hostfxr_name}")

    error_report = list()

    for hostfxr_path in reversed(sorted(hostfxr_paths, key=_path_to_version)):
        try:
            return ffi.dlopen(str(hostfxr_path))
        except Exception as err:
            error_report.append(f"Path {hostfxr_path} gave the following error:\n{err}")

    try:
        return ffi.dlopen(str(dotnet_root / hostfxr_name))
    except Exception as err:
        error_report.append(f"Path {hostfxr_path} gave the following error:\n{err}")

    raise RuntimeError(
        f"Could not find a suitable hostfxr library in {dotnet_root}. The following paths were scanned:\n\n"
        + ("\n\n".join(error_report))
    )


def load_mono(path: Optional[Path] = None):
    # Preload C++ standard library, Mono needs that and doesn't properly link against it
    if sys.platform == "linux":
        ffi.dlopen("stdc++", ffi.RTLD_GLOBAL)

    path_str = str(path) if path else None
    return ffi.dlopen(path_str, ffi.RTLD_GLOBAL)


def load_netfx():
    if sys.platform != "win32":
        raise RuntimeError(".NET Framework is only supported on Windows")

    dirname = Path(__file__).parent / "dlls"
    if sys.maxsize > 2**32:
        arch = "amd64"
    else:
        arch = "x86"

    path = dirname / arch / "ClrLoader.dll"

    return ffi.dlopen(str(path))


def _path_to_version(path: Path) -> Tuple[int, int, int]:
    name = path.parent.name
    try:
        # Handle pre-release versions like "10.0.0-rc.1" by taking only the version part
        version_part = name.split("-")[0]
        res = list(map(int, version_part.split(".")))
        return tuple(res + [0, 0, 0])[:3]
    except Exception:
        return (0, 0, 0)


def _get_dll_name(name: str) -> str:
    if sys.platform == "win32":
        return f"{name}.dll"
    elif sys.platform == "darwin":
        return f"lib{name}.dylib"
    else:
        return f"lib{name}.so"
