import json
from operator import attrgetter
import os
from pathlib import Path
import platform
import subprocess
from typing import Optional
if platform.system() == "Windows":
    import winreg

from .machine_spec import MachineSpec


cached_msvs_dir = None
cached_msvc_dir = None
cached_winsdk = None


def detect_msvs_installation_dir(toolchain_prefix: Optional[Path]) -> Path:
    global cached_msvs_dir
    if cached_msvs_dir is None:
        vswhere = Path(os.environ.get("ProgramFiles(x86)", os.environ["ProgramFiles"])) \
                / "Microsoft Visual Studio" / "Installer" / "vswhere.exe"
        if not vswhere.exists():
            if toolchain_prefix is None:
                raise MissingDependencyError("unable to locate vswhere.exe")
            vswhere = toolchain_prefix / "bin" / "vswhere.exe"
        installations = json.loads(
            subprocess.run([
                               vswhere,
                               "-latest",
                               "-format", "json",
                               "-property", "installationPath"
                           ],
                           capture_output=True,
                           encoding="utf-8",
                           check=True).stdout
        )
        if len(installations) == 0:
            raise MissingDependencyError("Visual Studio is not installed")
        cached_msvs_dir = Path(installations[0]["installationPath"])
    return cached_msvs_dir


def detect_msvc_tool_dir(toolchain_prefix: Optional[Path]) -> Path:
    global cached_msvc_dir
    if cached_msvc_dir is None:
        msvs_dir = detect_msvs_installation_dir(toolchain_prefix)
        version = sorted((msvs_dir / "VC" / "Tools" / "MSVC").glob("*.*.*"),
                         key=attrgetter("name"),
                         reverse=True)[0].name
        cached_msvc_dir = msvs_dir / "VC" / "Tools" / "MSVC" / version
    return cached_msvc_dir


def detect_windows_sdk() -> tuple[Path, str]:
    global cached_winsdk
    if cached_winsdk is None:
        try:
            key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows Kits\Installed Roots")
            try:
                (install_dir, _) = winreg.QueryValueEx(key, "KitsRoot10")
                install_dir = Path(install_dir)
                version = sorted((install_dir / "Include").glob("*.*.*"),
                                 key=attrgetter("name"),
                                 reverse=True)[0].name
                cached_winsdk = (install_dir, version)
            finally:
                winreg.CloseKey(key)
        except Exception as e:
            raise MissingDependencyError("Windows 10 SDK is not installed")
    return cached_winsdk


def detect_msvs_tool_path(machine: MachineSpec,
                          build_machine: MachineSpec,
                          tool: str,
                          toolchain_prefix: Optional[Path]) -> Path:
    return detect_msvc_tool_dir(toolchain_prefix) / "bin" / f"Host{build_machine.msvc_platform}" \
            / machine.msvc_platform / tool


def detect_msvs_runtime_path(machine: MachineSpec,
                             build_machine: MachineSpec,
                             toolchain_prefix: Optional[Path]) -> list[Path]:
    msvc_platform = machine.msvc_platform
    native_msvc_platform = build_machine.msvc_platform

    msvc_dir = detect_msvc_tool_dir(toolchain_prefix)
    msvc_bindir = msvc_dir / "bin" / f"Host{native_msvc_platform}" / msvc_platform

    msvc_dll_dirs = []
    if msvc_platform != native_msvc_platform:
        msvc_dll_dirs.append(msvc_dir / "bin" / f"Host{native_msvc_platform}" / native_msvc_platform)

    (winsdk_dir, winsdk_version) = detect_windows_sdk()
    winsdk_bindir = winsdk_dir / "Bin" / winsdk_version / msvc_platform

    return [winsdk_bindir, msvc_bindir] + msvc_dll_dirs


def detect_msvs_include_path(toolchain_prefix: Optional[Path]) -> list[Path]:
    msvc_dir = detect_msvc_tool_dir(toolchain_prefix)
    vc_dir = detect_msvs_installation_dir(toolchain_prefix) / "VC"

    (winsdk_dir, winsdk_version) = detect_windows_sdk()
    winsdk_inc_dirs = [
        winsdk_dir / "Include" / winsdk_version / "um",
        winsdk_dir / "Include" / winsdk_version / "shared",
    ]

    return [
        msvc_dir / "include",
        msvc_dir / "atlmfc" / "include",
        vc_dir / "Auxiliary" / "VS" / "include",
        winsdk_dir / "Include" / winsdk_version / "ucrt",
    ] + winsdk_inc_dirs


def detect_msvs_library_path(machine: MachineSpec,
                             toolchain_prefix: Optional[Path]) -> list[Path]:
    msvc_platform = machine.msvc_platform

    msvc_dir = detect_msvc_tool_dir(toolchain_prefix)
    vc_dir = detect_msvs_installation_dir(toolchain_prefix) / "VC"

    (winsdk_dir, winsdk_version) = detect_windows_sdk()
    winsdk_lib_dir = winsdk_dir / "Lib" / winsdk_version / "um" / msvc_platform

    return [
        msvc_dir / "lib" / msvc_platform,
        msvc_dir / "atlmfc" / "lib" / msvc_platform,
        vc_dir / "Auxiliary" / "VS" / "lib" / msvc_platform,
        winsdk_dir / "Lib" / winsdk_version / "ucrt" / msvc_platform,
        winsdk_lib_dir,
    ]


class MissingDependencyError(Exception):
    pass
