import configparser
import os
import subprocess
import sys
import sysconfig
from importlib import metadata, util
from pathlib import Path

from SCons.Errors import UserError

Import('env')
Import('VERSION')
Import('create_uninstall_target')
Import('installing')

GUI_DIR = 'gui'
genv = env.Clone()
genv['ENV'].update(
    (key, os.environ[key])
    for key in ('SOURCE_DATE_EPOCH', 'PYTHONHASHSEED', 'PYTHONPATH',
                'DEB_PYTHON_INSTALL_LAYOUT')
    if key in os.environ
)

# Written by the old setup.py-based install; still honoured on uninstall.
# TODO: Keep it until 2.12.0
LEGACY_FILES_RECORD = os.path.join(GUI_DIR, '.files.txt')

PYTHON = os.environ.get('PYTHON', sys.executable)
PREFIX: Path = env['PREFIX']
DESTDIR = env['DESTDIR']
STAGED_PREFIX = env['staged_prefix']

GSCHEMA_DIR_SUFFIX = 'share/glib-2.0/schemas'
RESOURCE_DIR = 'shredder/resources'
GRESOURCE = RESOURCE_DIR + '/shredder.gresource'
GRESOURCE_XML = genv.File(RESOURCE_DIR + '/shredder.gresource.xml')
COMPILE_SCHEMAS = GetOption('with_compile-glib-schemas')

GSCHEMA_SOURCE = 'schemas/io.github.sahib.rmlint.Shredder.gschema.xml'
DATA_FILES = [
    (Path('$PREFIX/share/icons/hicolor/scalable/apps/shredder.svg'),
     'shredder/resources/shredder.svg'),
    (Path(f'$PREFIX/{GSCHEMA_DIR_SUFFIX}/io.github.sahib.rmlint.Shredder.gschema.xml'),
     GSCHEMA_SOURCE),
    (Path('$PREFIX/share/applications/io.github.sahib.rmlint.Shredder.desktop'),
     'shredder.desktop'),
]

purelib = sysconfig.get_path("purelib", vars={"base": PREFIX})

# XXX: Debian have '/usr' in bases and appends 'local/' to it
if DEBIAN_OK := ('deb_system' in sysconfig.get_scheme_names()
                 and PREFIX.samefile(Path('/usr/local'))
                 and purelib.startswith('/usr/local/local/lib/python')):
    purelib = purelib.replace('/usr/local/local/', '/usr/local/', 1)

def err_externally_managed():
    if DESTDIR or GetOption('allow_externally_managed') or DEBIAN_OK:
        return

    stdlib = sysconfig.get_path("stdlib")
    marker = os.path.join(stdlib, 'EXTERNALLY-MANAGED')
    if not os.path.isfile(marker):
        return

    def norm(path): return Path(os.path.normpath(path))

    if not norm(Path(sysconfig.get_path("purelib"))).is_relative_to(norm(PREFIX)):
        config = configparser.ConfigParser(interpolation=None)
        config.read(marker, encoding='utf-8')
        error = config.get(
            'externally-managed',
            'Error',
            fallback='This Python context is externally managed.',
        )
        print(f'Your distribution has something to say to you:\n {error}')
        raise UserError(f"Externally managed Python on {stdlib}. "
                        "Change PREFIX= or use --allow-externally-managed to override.")

#==============================================================================#
#                                  uninstall                                   #
#==============================================================================#

if 'uninstall' in COMMAND_LINE_TARGETS:
    data_uninstalls = [create_uninstall_target(env, dest)
                       for dest, _ in DATA_FILES]

    def delete_file(path: Path, parent_dirs):
        try:
            path.unlink()
            if path.parent.name != 'bin':  # don't delete /usr/local/bin
                parent_dirs.add(path.parent)
        except FileNotFoundError:
            return
        except OSError as err:
            print(f"Unable to delete {path}: {err}", file=sys.stderr)

    def prune_empty_dirs(parent_dirs):
        for directory in sorted(parent_dirs, key=lambda p: len(p.parts), reverse=True):
            try:
                directory.rmdir()
            except OSError:
                pass  # possibly not empty, keep it

    def uninstall_wheel():
        err_externally_managed()

        parent_dirs = set()
        for dist in metadata.distributions(path=[purelib]):
            if dist.metadata['Name'] != 'shredder':
                continue

            for file in dist.files or ():
                path = dist.locate_file(file)
                if file.suffix == '.py':
                    for optimise in ('', 1, 2):
                        pyc = Path(util.cache_from_source(path, optimization=optimise))
                        delete_file(pyc, parent_dirs)
                delete_file(path, parent_dirs)
        prune_empty_dirs(parent_dirs)

    def uninstall_legacy_record():
        """Delete files recorded by the old setup.py-based install."""
        if not os.path.exists(LEGACY_FILES_RECORD):
            return

        parent_dirs = set()
        with open(LEGACY_FILES_RECORD) as handle:
            for line in handle:
                if line.strip():
                    delete_file(Path(line.strip()), parent_dirs)

        prune_empty_dirs(parent_dirs)
        os.remove(LEGACY_FILES_RECORD)

    def uninstall_gui(target, source, env):
        uninstall_wheel()
        uninstall_legacy_record()

        if not COMPILE_SCHEMAS:
            return

        # recompile glib schemas after deleting ours
        print('Recompiling glib schemas…')
        gschema_dir = str(PREFIX / GSCHEMA_DIR_SUFFIX)
        if os.path.isdir(gschema_dir):
            subprocess.call(['glib-compile-schemas', gschema_dir])
        else:
            print('the schema dir is missing; skipped.')

    gui_uninstall = env.Command(
        'shredder.uninstall', '',
        Action(uninstall_gui, 'Uninstalling shredder…')
    )
    env.Depends(gui_uninstall, data_uninstalls)
    env.Alias('uninstall', gui_uninstall)
    Return()

#==============================================================================#
#                                    wheel                                     #
#==============================================================================#

def scan_gresource(node, env, _path):
    if node != GRESOURCE_XML:
        return []
    resource_deps = subprocess.check_output(
        ['glib-compile-resources',
            '--sourcedir', node.dir.get_abspath(),
            '--generate-dependencies', node.get_abspath()],
            text=True,
    )
    return [env.File(path) for path in resource_deps.split()]

sources = ([node for node in Glob('shredder/*.py') if node.name != '_version.py']
           + Glob('shredder/views/*.py'))
wheel = genv.Command(
    f'dist/shredder-{VERSION.pep440}-py3-none-any.whl',
    ['pyproject.toml', 'setup.py', '../.version', genv.Value('$gitrev')]
    + sources + [GRESOURCE_XML],
    f'cd {GUI_DIR} && {PYTHON} -m build --no-isolation --wheel --outdir dist .',
    source_scanner=Scanner(function=scan_gresource),
)

env.Clean(wheel, [
    GRESOURCE,
    'shredder/_version.py',
    'build',
    'shredder.egg-info',
])
env.Default(wheel)

#==============================================================================#
#                                   install                                    #
#==============================================================================#

if installing:
    err_externally_managed()

    install_cmd = f'{PYTHON} -m installer'
    if DESTDIR:
        install_cmd += f' --destdir "{DESTDIR}"'
    # DEBIAN_OK implies PREFIX=/usr/local
    # and Debian installs to /usr/local without prefix
    if not DEBIAN_OK:
        install_cmd += f' --prefix "{PREFIX}"'
    install_cmd += ' $SOURCE'
    wheel_install = genv.Command('shredder.install', wheel[0], install_cmd)

    data_installs = [
        env.InstallAs(str(DESTDIR / dest), src)
        for dest, src in DATA_FILES
    ]
    env.Alias(['install', 'install-gui'], [wheel_install] + data_installs)

    if COMPILE_SCHEMAS and not DESTDIR:
        compile_schemas = env.Command(
            'shredder.gschemas', GSCHEMA_SOURCE,
            f'glib-compile-schemas "{PREFIX / GSCHEMA_DIR_SUFFIX}"'
        )
        env.Alias(['install', 'install-gui'], compile_schemas)
    else:
        print('==> Not compiling glib schemas (--without-compile-glib-schemas or DESTDIR=).')
        print('==> You may need to run this after installing:')
        print(f'==>     glib-compile-schemas {PREFIX / GSCHEMA_DIR_SUFFIX}')
