import glob
import os
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path


def patch_cx_freeze():
    import cx_Freeze.dist
    from dev_tools.build.freeze.cx.freezer import Freezer

    # override the already imported, builtin Freezer class in the global dict of the using module
    cx_Freeze.dist.Freezer = Freezer

    import cx_Freeze.hooks

    def _load_wx(finder, module):
        module.update_distribution("wxPython")
        if not sys.platform.startswith("win") and not sys.platform.startswith("darwin"):  # assume gtk3 binding
            # We need to include some extra files and libs for GTK3:
            #   - "libpixbufloader-svg.so" from gdk-pixbuf-2 for GTK3 to be able to load default icons
            #   - actual icons?
            pixbuf_version = None
            for lib_dir in ["/lib", "/usr/lib", "/lib64", "/usr/lib64"]:
                lib_dir = Path(lib_dir) / "gdk-pixbuf-2.0"
                if lib_dir.is_dir():
                    pixbuf_version = [t.name for t in lib_dir.iterdir()]
                    if len(pixbuf_version) != 1:
                        raise Exception(f"There must be a single gdk-pixbuf-2 installed: {pixbuf_version}")
                    pixbuf_version = pixbuf_version[0]
                    break
            if not pixbuf_version:
                raise Exception("The gdk-pixbuf-2 was not found")
            loaders_dir = lib_dir / pixbuf_version / "loaders"
            if not loaders_dir.is_dir():
                raise Exception(f"The gdk-pixbuf-2 loaders dir is missing: {loaders_dir}")
            loaders = [t.name for t in loaders_dir.iterdir()]
            svg_loader = "libpixbufloader-svg.so"
            if svg_loader not in loaders:
                raise Exception(f"The gdk-pixbuf-2 {svg_loader} is missing from: {loaders_dir}")
            # NOTE: We omit the pixbuf version to have a constant path.
            finder.IncludeFiles(loaders_dir / svg_loader, Path("lib", "gdk-pixbuf-2.0", "loaders", svg_loader))
            for loader_cache_gen in ["gdk-pixbuf-query-loaders", "gdk-pixbuf-query-loaders-64"]:
                loader_cache_gen = shutil.which(loader_cache_gen)
                if loader_cache_gen:
                    break
            if not loader_cache_gen:
                raise Exception("pixbuf loaders cache generator not found")
            loaders_cache = subprocess.check_output([loader_cache_gen], encoding="utf-8")
            # Strip not copied loaders and replace the path to be relative.
            loaders_cache = [line.strip() for line in loaders_cache.replace("\r\n", "\n").split("\n")]
            while loaders_cache:  # Find the beginning of the svg section.
                if loaders_cache.pop(0).find(svg_loader) >= 0:
                    break
            relative_loaders_cache = [f'"{svg_loader}"']  # svg lib name, no absolute path
            while loaders_cache:  # Copy until the end of the svg section.
                relative_loaders_cache.append(loaders_cache.pop(0))
                if not relative_loaders_cache[-1]:
                    break
            # NOTE: This temp file can't be cleaned up here.. and we have no framework to clean it up later either.
            fd, temp_path = tempfile.mkstemp(text=True)
            with os.fdopen(fd, "w") as f:
                f.write("\n".join(relative_loaders_cache))
            finder.IncludeFiles(temp_path, Path("lib", "gdk-pixbuf-2.0", "loaders.cache"))
            # Grab the schemas for glib.
            glib_schemas_path = "/usr/share/glib-2.0/schemas/gschemas.compiled"
            if not os.path.isfile(glib_schemas_path):
                raise Exception("compiled glib schemas not found")
            finder.IncludeFiles(glib_schemas_path, Path("lib", "glib-schemas", "gschemas.compiled"))

    def _load_passlib(finder, module):
        finder.IncludePackage("passlib")
        # used modules are lazily loaded, only copy what we use
        finder.IncludePackage("passlib.handlers.pbkdf2")

    _load_zmq_base = cx_Freeze.hooks.load_zmq

    def _load_zmq(finder, module):
        _load_zmq_base(finder, module)
        zmq_root = os.path.abspath(os.path.join(os.path.dirname(module.file), os.pardir))
        dists = glob.glob(os.path.join(zmq_root, "pyzmq-*.dist-info"))
        assert len(dists) == 1
        finder.ZipIncludeFiles(dists[0], os.path.basename(dists[0]))

    def _load_orjson(finder, module):
        for pkg in ["dataclasses", "enum", "json", "numpy", "uuid", "zoneinfo"]:
            finder.IncludePackage(pkg)

    # Monkey-patching the freezes hooks as they seems to be out-of-date. This works fine here, the hooks are only
    # imported by cx_freeze dynamically during the freeze procedure.
    cx_Freeze.hooks.load_wx = _load_wx
    cx_Freeze.hooks.load_passlib = _load_passlib
    cx_Freeze.hooks.load_zmq = _load_zmq
    cx_Freeze.hooks.load_orjson = _load_orjson
