#!/usr/bin/env python
# License: GPLv3 Copyright: 2025, un_pogaz <un.pogaz@gmail.com>

import os
from contextlib import suppress
from typing import NamedTuple

from setup import Command

HOOK_TEMPLATE = '''\
#!/usr/bin/env -S calibre-debug -e -- --
# File generated by calibre "setup.py git_hooks"

import os
import runpy
import sys
base = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.argv[0] = os.path.basename({file!r})
runpy.run_path(os.path.join(base, 'setup', {file!r}), run_name='__main__')
'''


class Hook(NamedTuple):
    name: str
    file: str
    default: bool = True


HOOKS = {
    h.name: h
    for h in (
        Hook('post-checkout', 'git_post_checkout_hook.py'),
        Hook('post-rewrite', 'git_post_rewrite_hook.py'),
        Hook('pre-commit', 'git_pre_commit_hook.py'),
        # disable by default, because except Kovid, nobody can run this hook
        Hook('commit-msg', 'git_commit_msg_hook.py', False),
    )
}

DEFAULT = ','.join(sorted(h.name for h in HOOKS.values() if h.default))
AVAILABLES = ', '.join(sorted(h for h in HOOKS))


class GitHooks(Command):
    description = 'Install/uninstall git hooks'

    def add_options(self, parser):
        parser.add_option(
            '-n', '--name', default=DEFAULT, help=f'Name(s) of the hook to install, separated by commas. Default: "{DEFAULT}". Hooks available: {AVAILABLES}'
        )
        parser.add_option('-u', '--uninstall', default=False, action='store_true', help='Uninstall the selected hooks')
        parser.add_option('-f', '--force', default=False, action='store_true', help='Force the operations on the hooks')

    def run(self, opts):
        self.force = opts.force
        self.names = []

        invalides = []
        for candidate in sorted(c.strip().lower() for c in opts.name.split(',')):
            if not candidate:
                continue
            if candidate not in HOOKS:
                invalides.append(candidate)
            else:
                self.names.append(candidate)

        if invalides:
            self.info('Info: The following hook names are not recognized:', ', '.join(invalides))
        if not self.names:
            self.info('No supported hook names recognized.')
            return

        if opts.uninstall:
            self.uninstall()
        else:
            self.install()

    def _parse_template(self, hook_name):
        base_hooks = os.path.join(os.path.dirname(self.SRC), '.git', 'hooks')
        hook = HOOKS[hook_name]
        path = self.j(base_hooks, hook.name)
        script = HOOK_TEMPLATE.format(file=hook.file)
        return path, script

    def install(self):
        self.info('Installing the hooks:', ', '.join(self.names))
        for candidate in self.names:
            path, script = self._parse_template(candidate)

            if self.e(path):
                with open(path, 'rb') as f:
                    previous = f.read().decode('utf-8')
                msg = f'{candidate}: a non-calibre hook is installed.'
                if previous == script:
                    self.info(f'{candidate}: installed.')
                    continue
                elif self.force:
                    self.info(msg, 'Force installation.')
                else:
                    self.info(msg, 'Skip installation.')
                    continue

            self.info(f'{candidate}: installed.')
            with suppress(OSError):
                os.remove(path)  # remove if symlink
            with open(path, 'wb') as f:
                f.write(script.encode('utf-8'))
            try:
                os.chmod(path, 0o744, follow_symlinks=False)
            except NotImplementedError:  # old python on windows
                os.chmod(path, 0o744)

    def uninstall(self):
        self.info('Uninstalling the hooks:', ', '.join(self.names))
        for candidate in self.names:
            path, script = self._parse_template(candidate)

            if not self.e(path):
                self.info(f'{candidate}: no hook to unistall.')
                continue

            with open(path, 'rb') as f:
                previous = f.read().decode('utf-8')
            msg = f'{candidate}: a non-calibre hook is installed.'
            if previous == script:
                self.info(f'{candidate}: unistalled.')
            elif self.force:
                self.info(msg, 'Force unistallation.')
            else:
                self.info(msg, 'Skip unistallation.')
                continue

            os.remove(path)
