#!/usr/bin/env python3

"""
    bootstrap CLI
    ~~~~~~~~~~~~~

    Just call this file, and the magic happens ;)
"""

import hashlib
import os
import shlex
import signal
import subprocess
import sys
import venv
from pathlib import Path


def print_no_pip_error():
    print('Error: Pip not available!')
    print('Hint: "apt-get install python3-venv"\n')


try:
    from ensurepip import version
except ModuleNotFoundError as err:
    print(err)
    print('-' * 100)
    print_no_pip_error()
    raise
else:
    if not version():
        print_no_pip_error()
        sys.exit(-1)


assert sys.version_info >= (3, 11), f'Python version {sys.version_info} is too old!'


if sys.platform == 'win32':  # wtf
    # Files under Windows, e.g.: .../.venv/Scripts/python.exe
    BIN_NAME = 'Scripts'
    FILE_EXT = '.exe'
else:
    # Files under Linux/Mac and all other than Windows, e.g.: .../.venv/bin/python3
    BIN_NAME = 'bin'
    FILE_EXT = ''

BASE_PATH = Path(__file__).parent
VENV_PATH = BASE_PATH / '.venv'
BIN_PATH = VENV_PATH / BIN_NAME
PYTHON_PATH = BIN_PATH / f'python3{FILE_EXT}'
PIP_PATH = BIN_PATH / f'pip{FILE_EXT}'
UV_PATH = BIN_PATH / f'uv{FILE_EXT}'

DEP_LOCK_PATH = BASE_PATH / 'uv.lock'
DEP_HASH_PATH = VENV_PATH / '.dep_hash'

# script file defined in pyproject.toml as [console_scripts]
# (Under Windows: ".exe" not added!)
PROJECT_SHELL_SCRIPT = BIN_PATH / 'inventory_project'


def get_dep_hash():
    """Get SHA512 hash from lock file content."""
    return hashlib.sha512(DEP_LOCK_PATH.read_bytes()).hexdigest()


def store_dep_hash():
    """Generate .venv/.dep_hash"""
    DEP_HASH_PATH.write_text(get_dep_hash())


def venv_up2date():
    """Is existing .venv is up-to-date?"""
    if DEP_HASH_PATH.is_file():
        return DEP_HASH_PATH.read_text() == get_dep_hash()
    return False


def verbose_check_call(*popen_args):
    print(f'\n+ {shlex.join(str(arg) for arg in popen_args)}\n')
    return subprocess.check_call(popen_args)


def noop_sigint_handler(signal_num, frame):
    """
    Don't exist cmd2 shell on "Interrupt from keyboard"
    e.g.: User stops the dev. server by CONTROL-C
    """


def main(argv):
    assert DEP_LOCK_PATH.is_file(), f'File not found: "{DEP_LOCK_PATH}" !'

    # Create virtual env in ".venv/":
    if not PYTHON_PATH.is_file():
        print(f'Create virtual env here: {VENV_PATH.absolute()}')
        builder = venv.EnvBuilder(symlinks=True, upgrade=True, with_pip=True)
        builder.create(env_dir=VENV_PATH)

    # Set environment variable for uv to use '.venv-app' as project environment:
    os.environ['UV_PROJECT_ENVIRONMENT'] = str(VENV_PATH.absolute())

    if not PROJECT_SHELL_SCRIPT.is_file() or not venv_up2date():
        # Update pip
        verbose_check_call(PYTHON_PATH, '-m', 'pip', 'install', '-U', 'pip')

        # Install uv
        verbose_check_call(PYTHON_PATH, '-m', 'pip', 'install', '-U', 'uv')

        # install requirements
        verbose_check_call(UV_PATH, 'sync', '--frozen')

        # install project
        verbose_check_call(PIP_PATH, 'install', '--no-deps', '-e', '.')

        # Activate git pre-commit hooks:
        verbose_check_call(PYTHON_PATH, '-m', 'pre_commit', 'install')

        store_dep_hash()

    signal.signal(signal.SIGINT, noop_sigint_handler)  # ignore "Interrupt from keyboard" signals

    # Call our entry point CLI:
    try:
        verbose_check_call(PROJECT_SHELL_SCRIPT, *argv[1:])
    except subprocess.CalledProcessError as err:
        sys.exit(err.returncode)
    except KeyboardInterrupt:
        print('Bye!')
        sys.exit(130)


if __name__ == '__main__':
    main(sys.argv)
