import os
import shutil
import glob
import re
import subprocess
import warnings

import setuptools
import setuptools.command.build_py
import debian.changelog


def fopen(path, mode="r", **kwargs):
    return open(path, mode, encoding="UTF-8", **kwargs)


# Get version from debian/changelog
with open("./debian/changelog", "rb") as F:
    MINI_BUILDD_VERSION = str(debian.changelog.Changelog(file=F).version)

# setuptools would always complain about our snapshot/testing Debian version string (PEP 440).
# Not relevant for us as this is used as Debian package only.
# Mildy annoying when hunting real errors, though.
warnings.filterwarnings("ignore", module="setuptools.dist")


def which(*names):
    for name in names:
        path = shutil.which(name)
        if path is not None:
            return path
    return None


# .. attention:: **compat** (``py-argcomplete < 2.0.0-1``): Cope with executable name change
PY_ARGCOMPLETE = which("register-python-argcomplete", "register-python-argcomplete3")
print(f"I: Found p-r-argcomplete executable: {PY_ARGCOMPLETE}")


class Scripts():
    _INSTALL_PATHS = {1: "usr/bin/", 8: "usr/sbin/"}

    def __init__(self):
        # Auto-generate Debian install files.
        self.debian_install = {
            1: fopen("debian/mini-buildd-utils.install", "w"),
            8: fopen("debian/mini-buildd.install", "w"),
        }
        # dh_bash-completion format is very limited. The only way to automate this is to write the resp. configs here.
        self.debian_bash_completion = {
            1: fopen("debian/mini-buildd-utils.bash-completion", "w"),
            8: fopen("debian/mini-buildd.bash-completion", "w"),
        }

        for i in 1, 8:
            self.debian_install[i].write("# Generated by setup.py\n")

        # Static install contents for mini-buildd
        self.debian_install[8].write(("share/package-templates/ /usr/share/mini-buildd/\n"
                                      "etc/schroot/\n"
                                      "etc/sudoers.d/\n"))

    def setup(self, prog_path, section):
        prog = os.path.basename(prog_path)
        man_page_file = f"{prog_path}.{section}"

        # Hack to get the DESCRIPTION from script for '--name' argument. See: https://lintian.debian.org/tags/manpage-has-useless-whatis-entry.html
        with fopen(f"{prog_path}") as prog_file:
            name = re.search(r'DESCRIPTION.*=.*"(.+?)"', prog_file.read()).group(1)

        print(f"I: Generating {man_page_file}...")
        subprocess.run([prog_path, "--help"], check=True, stdout=subprocess.DEVNULL)  # Pre-test to get 'error debug' from prog itself to stderr (help2man call below will not show them).
        subprocess.check_call(["help2man", "--no-info", "--name", name, "--version-string", MINI_BUILDD_VERSION, "--section", str(section), "--output", man_page_file, prog_path])

        # bash completion (python only)
        with fopen(f"{prog_path}") as prog_file:
            if re.match(r"#!/usr/bin/python", prog_file.readline()):
                bash_completion_file = f"src/{prog}.bash-completion"
                print(f"I: Generating {bash_completion_file}...")
                os.makedirs(os.path.dirname(bash_completion_file), exist_ok=True)
                with fopen(bash_completion_file, "w") as bash_completion:
                    bash_completion.write(f'eval "$({PY_ARGCOMPLETE} --shell=bash "{prog}")"\n')
                self.debian_bash_completion[section].write(f"{bash_completion_file} {prog}\n")

        self.debian_install[section].write(f"{prog_path} {self._INSTALL_PATHS[section]}\n")


class BuildPy(setuptools.command.build_py.build_py):
    def run(self):
        version_file = "src/mini_buildd/version.py"
        print(f"I: Generating {version_file}...")
        with fopen(version_file, "w") as init_py:
            init_py.write(f"""\
__version__ = "{MINI_BUILDD_VERSION}"
""")

        scripts = Scripts()
        scripts.setup("src/mini-buildd", 8)

        scripts.setup("src/mini-buildd-self-signed-certificate", 8)
        scripts.setup("src/mini-buildd-backup", 8)
        scripts.setup("src/mini-buildd-debootstrap-uname-2.6", 8)
        scripts.setup("src/mini-buildd-ssh-uploader-command", 8)
        scripts.setup("src/mini-buildd-ssh-client-command", 8)
        scripts.setup("src/mini-buildd-debug-build", 8)
        scripts.setup("src/mini-buildd-schroot-cleanup", 8)
        scripts.setup("src/mini-buildd-reject-cleanup", 8)
        scripts.setup("src/mini-buildd-import-08x", 8)

        scripts.setup("src/mini-buildd-api", 1)
        scripts.setup("src/mini-buildd-events", 1)
        scripts.setup("src/mini-buildd-dput", 1)
        scripts.setup("src/mini-buildd-internals", 1)
        scripts.setup("src/mini-buildd-bootstrap-apt", 1)
        scripts.setup("src/mini-buildd-super-portext", 1)

        super().run()

        # Auto-minifying some files. minify breaks "html" django templates, so skipped.
        for extension in ["css", "svg", "js"]:
            for f in glob.glob(f"{self.build_lib}/**/*.{extension}", recursive=True):
                print(f"I: Minifying {f}...")
                minified = f + ".minified"
                subprocess.check_call(["minify", "--output", minified, f])
                os.replace(minified, f)


def package_data_files(*patterns):
    """Little helper to collect file lists for package_data."""
    package_path = "src/mini_buildd/"
    for pattern in patterns:
        for f in glob.glob(f"{package_path}{pattern}", recursive=True):  # python 3.10's "root_dir" would help, but we need to support >=3.6.
            yield f[len(package_path):]


setuptools.setup(
    cmdclass={"build_py": BuildPy},
    name="mini-buildd",
    version=MINI_BUILDD_VERSION.replace("+", ".").replace("~", "+"),  # Taint Debian version to make setuptools not warn
    description="Mini Debian build daemon",
    author="Stephan Sürken",
    author_email="absurd@debian.org",
    package_dir={"": "src"},
    packages=setuptools.find_packages("src"),                         # All traditional (dir has "__init__.py") packages: ['mini_buildd', 'mini_buildd.models']
    package_data={
        "mini_buildd": list(package_data_files("**/*.css", "**/*.js", "**/*.svg", "**/*.html", "templatetags/**/*.py")),
    })
