#!/usr/bin/python3
import os
import pwd
import subprocess
import sys


def show_git_difference():
    git_difference = subprocess.run(
            ['git', 'diff', '-U0'],
            capture_output=True
        ).stdout.decode().strip()
    if git_difference:
        print(git_difference)


def try_to_run_command(command, shell=False):
    try:
        cmd = subprocess.run(command, shell=shell)
        if cmd.returncode == 0:
            print(f'Выполнено: {command}')
        else:
            print(f'Возникла ошибка. Вывод команды: {cmd.stdout.decode()}')
    except Exception as e:
        print(f'Команда: {command}, во время выполнения возникло исключение: {e}')


def install_packages_for_building():
    """
    Установка всех пакетов, необходимых для локальной сборки
    """
    try_to_run_command(
        [
            'sudo', 'dnf', 'in', '-y', 'git-core', 'rpmdevtools', 'rpm-mk-build-deps', 'abf-console-client',
            'basesystem-build'
        ]
    )


def replace_current_with_remote():
    try:
        cmd = subprocess.run('git reset --hard && git pull', shell=True, capture_output=True)
        if cmd.returncode == 0:
            print('Состояние обновлено из удалённого репозитория.')
        else:
            print(f'Не удалось обновить состояние из удалённого репозитория. Вывод: {cmd.stdout.decode()}')
    except Exception as e:
        print(f'Во время выполнения команды возникла ошибка: {e}')


def install_spec_build_requires(spec_file='', remove_rpm=True):
    """
    Установка зависимостей spec-файла.
    :param remove_rpm: если True, удалить rpm-файл для установки зависимостей после установки
    :param spec_file: спек
    :return: -
    """
    if not spec_file:
        print('Укажите spec-файл.')
        return
    else:
        try_to_run_command(f'mbd {spec_file} && sudo dnf in --disablerepo "*i686*" -y *build-deps*.rpm'
                           f'{" && rm -f *build-deps*.rpm" if remove_rpm else ""}',
                           shell=True)


def remove_spec_build_requires(spec_file=''):
    """
    Удаление зависимостей spec-файла
    :param spec_file: спек
    :return: -
    """
    if not spec_file:
        print('Укажите spec-файл.')
        return
    name = subprocess.run(
        ["rpmspec", "-q", "--srpm", "--qf", "%{name}", spec_file], capture_output=True
    ).stdout.decode().strip()

    try_to_run_command(['sudo', 'dnf', 'rm', '-y', f'{name}-build-deps'])


def build_package(spec_file='', install_required=False, no_delete=False):
    """
    Сборка пакета
    :param no_delete: если False, то *.src.rpm и всё указанное в .abf.yml не удаляется после сборки
    :param spec_file: спек для сборки
    :param install_required: если True, то автоматическая установка пакета после сборки
    :return: -
    """
    if not spec_file:
        print('Укажите spec-файл.')
        return
    install_rpm_command = (' && find . -maxdepth 1 -name "*.rpm" ! -name "*build-deps*" ! -name "*.src.rpm" '
                           '-exec sudo dnf in -y {} \;') if install_required else ''

    command_to_run = (f'spectool --define "_sourcedir $PWD" -g {spec_file} && rm -f .abf.yml && abf put -n && abf rpmbuild'
                      f'{install_rpm_command}')
    try_to_run_command(command_to_run, shell=True)
    if not no_delete:
        delete_garbage_command = 'rm -rf BUILD BUILDROOT *.src.rpm '
        try:
            yml_contents = open(os.path.join(os.getcwd(), '.abf.yml'), 'r').read()
            for file in os.listdir(os.getcwd()):
                if file in yml_contents:
                    delete_garbage_command += f'{file} '
        except FileNotFoundError:
            pass
        try_to_run_command(delete_garbage_command, shell=True)


def version_up(spec_file='', specified_version=''):
    """
    Обновляет версию в spec-файле со сбросом релиза во все единицы
    :param spec_file: спек
    :param specified_version: если укзаана, то версия заменяется на указанную, если нет, повышается на 1
    :return: -
    """
    if not spec_file:
        print('Укажите spec-файл.')
        return
    version_updated = False
    try:
        with open(spec_file, 'r') as inp:
            lines = inp.readlines()
        for i in range(len(lines)):
            line = lines[i]
            if line.startswith('Version'):
                # чтобы полностью сохранить структуру строки
                line = line.strip()
                # Если версия не указана, просто повысить её на 1
                if not specified_version:
                    indx = -1
                    while line[indx].isdigit():
                        indx -= 1
                    if indx != -1:
                        # Повышается число после последней точки
                        number = int(line[indx + 1:]) + 1
                        line = line[:indx + 1] + str(number) + '\n'
                        lines[i] = line
                        print(f'Версия повышена.')
                        version_updated = True
                        break
                    else:
                        print('Строка с версией имеет некорректный формат.')
                        break
                # Если версия указана, нужно заменить ту, которая есть, на указанную
                else:
                    indx = 0
                    while indx < len(line) and not line[indx].isdigit():
                        indx += 1
                    if indx < len(line):
                        line = line[:indx] + specified_version + '\n'
                        lines[i] = line
                        print(f'Версия повышена.')
                        version_updated = True
                        break
                    else:
                        print('Строка с версией имеет некорректный формат.')
                        break
        else:
            print('Строка с версией не найдена.')
        # Если версия обновлена, нужно сбросить релиз во все единицы
        if version_updated:
            for i in range(len(lines)):
                line = lines[i]
                if line.startswith('Release'):
                    line = line.strip()
                    indx = 0
                    while indx < len(line) and not line[indx].isdigit():
                        indx += 1
                    if indx < len(line):
                        release_list = line[indx:].split('.')
                        for k in range(len(release_list)):
                            if release_list[k].isdigit():
                                release_list[k] = '1'
                        line = line[:indx] + '.'.join(release_list) + '\n'
                        lines[i] = line
                        print(f'Релиз обновлён.')
                        break
                    else:
                        print('Строка с релизом имеет некорректный формат.')
                        break
            while not lines[-1].strip():
                lines = lines[:-1]
            with open(spec_file, 'w') as out:
                print(*lines, sep='', file=out)
        show_git_difference()
    except Exception as e:
        print(f'Возникло исключение: {e}')


def commit_update(spec_file='', commit=''):
    """
    Обновляет коммит в спеке, где указан %define commit
    :param spec_file: спек
    :param commit: коммит (40 символов)
    :return: -
    """
    if not spec_file:
        print('Не указан spec-файл.')
        return
    elif not commit or len(commit) != 40:
        print('Укажите полный хэш коммита. '
              'Синтаксис: pirpm commit-update [file.spec] c9ecd3c39cf004fd777c6caaaa1f502e1e8475f0')
        return
    else:
        try:
            commit_updated, release_updated = False, False
            with open(spec_file, 'r') as inp:
                lines = inp.readlines()
            for i in range(len(lines)):
                line = lines[i]
                if line.startswith('%define commit') and not commit_updated:
                    line = line.strip().split()[:-1]
                    line.append(commit)
                    line = ' '.join(line) + '\n'
                    lines[i] = line
                    commit_updated = True
                elif line.startswith('Release') and not release_updated:
                    release = line.strip().split()[-1]
                    index_to_replace = line.index(release)
                    macros = '%' + release.split('%')[-1].split('}')[0] + '}'
                    release_list = release.split('.')
                    macros_index = 0
                    for j in range(len(release_list)):
                        if macros in release_list[j]:
                            macros_index = j
                            break
                    # повышение релиза
                    release_list[macros_index - 1] = f'{int(release_list[macros_index - 1]) + 1}'
                    # сброс всего, что справа, в 1
                    for j in range(macros_index + 1, len(release_list)):
                        release_list[j] = '1'
                    release = '.'.join(release_list)
                    line = line[:index_to_replace] + release + '\n'
                    lines[i] = line
                    release_line = line
                    release_updated = True
            if commit_updated and release_updated:
                while not lines[-1].strip():
                    lines = lines[:-1]
                print(*lines, sep='', file=open(spec_file, 'w'))
                print(f'Коммит обновлён.')
                if '.abf.yml' in os.listdir(os.getcwd()):
                    get_archive_cmd = subprocess.run(f'spectool --define "_sourcedir $PWD" -g {spec_file}', shell=True)
                    if get_archive_cmd.returncode != 0:
                        print('Не удалось получить архив с исходным кодом для обновления .abf.yml')
                    else:
                        os.remove(os.path.join(os.getcwd(), '.abf.yml'))
                        create_link_cmd = subprocess.run(['abf', 'put'])
                        if create_link_cmd.returncode == 0:
                            print('Ссылка на архив в .abf.yml обновлена.')
                        else:
                            print('Не удалось загрузить архив с исходным кодом на abf.')
            else:
                print('Не удалось обновить коммит.')

            show_git_difference()
        except Exception as e:
            print(f'Возникло исключение: {e}')


def create_edit(spec_file='', edit_folder=''):
    """
    Скачивает и распаковывает исходники в ~/pirpm-edit/имя_пакета
    :param edit_folder: по умолчанию ~/pirpm-edit
    :return: -
    """
    if not spec_file:
        print('Не указан spec-файл.')
        return
    if not edit_folder:
        print('Не определена папка для распаковки исходного кода.')
        return
    current_folder = os.getcwd()
    try:
        os.mkdir(edit_folder)
    except FileExistsError:
        pass
    # current_pkg_edit_folder = os.path.join(edit_folder, os.path.basename(current_folder))
    # if os.path.isdir(current_pkg_edit_folder):
    #     if input(
    #             f'Папка {current_pkg_edit_folder} уже существует. Перезаписать? '
    #     ).lower() not in 'дy':
    #         print('Отменено пользователем.')
    #         return
    #     shutil.rmtree(current_pkg_edit_folder)
    # os.mkdir(current_pkg_edit_folder)
    package_name = subprocess.run(
        'rpmspec -q --qf "%{NAME}\n" *.spec | head -1',
        shell=True, capture_output=True
    ).stdout.decode().strip()
    cmd = subprocess.run(
        f'spectool --define "_sourcedir $PWD" -g {spec_file} && '
        f'rpmbuild -bp *.spec --define="_sourcedir $(pwd)" --define="_topdir $(pwd)" --define="_builddir {edit_folder}" && '
        f'rm -rf $(ls *.tar.gz | head -1) BUILDROOT/ RPMS/ SRPMS/ && '
        f'mv {edit_folder}/{package_name}-* {edit_folder}/{package_name}',
        shell=True
    )
    if cmd.returncode != 0:
        print('При скачивании и копировании исходников возникла ошибка.')
        return
    # shutil.copytree(current_folder, current_pkg_edit_folder, dirs_exist_ok=True)
    current_pkg_edit_folder = f'{edit_folder}/{package_name}'
    print(f'Текущий проект скопирован для правки в {current_pkg_edit_folder}')
    os.chdir(current_pkg_edit_folder)
    subprocess.run('git init && git add . && git commit -m "init"', shell=True)
    os.chdir(current_folder)


def create_patch(spec_file='', edit_folder='', commit_message='pirpm'):
    """
    Создаёт патч из изменений в ~/pirpm-edit/имя_пакета по сравнению с текущей папкой и записывает его в спек
    :param commit_message: сообщение коммита для названия патча
    :param spec_file: спек
    :param edit_folder: по умолчанию ~/pirpm-edit
    :return: -
    """
    if not spec_file:
        print('Укажите spec-файл.')
        return
    elif not os.path.isdir(f'{edit_folder}/{os.path.basename(os.getcwd())}'):
        print('Не найдена папка с отредактированным проектом.')
        return
    else:
        current_folder = os.getcwd()
        current_pkg_edit_folder = os.path.join(edit_folder, os.path.basename(current_folder))
        # Поиск номера первого патча, которого нет в папке с проектом
        current_patches = [i for i in os.listdir(current_folder) if i.endswith('.patch')]
        patch_number = 0
        patch_number_in_patches = True
        while patch_number_in_patches:
            patch_number += 1
            # 0001
            patch_number_string = str(patch_number).zfill(4)
            patch_number_in_patches = False
            for i in current_patches:
                if i.startswith(patch_number_string):
                    patch_number_in_patches = True
                    break
        cmd = subprocess.run(
            f'cd {current_pkg_edit_folder} && git add . && git commit -m "{commit_message}" && '
            f'git format-patch -o {current_folder} -1 --start-number={patch_number} && cd {current_folder}',
            shell=True)
        if cmd.returncode == 0:
            print('Патч создан.')
        else:
            print('При создании патча возникла ошибка.')
            return
        patch_filename = subprocess.run(
            f'ls -t {current_folder}/*.patch | head -n 1',
            shell=True, capture_output=True
        ).stdout.decode().strip()
        # os.rename('new_pirpm.patch', patch_filename)
        # print(f'Патч переименован в {patch_filename}.')
        # if cmd.returncode != 0:
        #     print('Во время создания патча возникла ошибка.')
        #     sys.exit(0)
        if os.path.getsize(patch_filename) == 0:
            print('Изменения не найдены. Патч будет удалён.')
            os.remove(patch_filename)
            return
        index_to_insert_patch = 0
        with open(spec_file, 'r') as inp:
            lines = inp.readlines()
        for i in range(len(lines)):
            # вставить патч нужно после всех источников и патчей, если они есть
            if lines[i].startswith('Source') or lines[i].startswith('Patch'):
                index_to_insert_patch = i + 1
        if index_to_insert_patch == 0:
            print('В spec-файле не найдены источники, проверьте его на корректность.')
            sys.exit(0)
        lines.insert(index_to_insert_patch, f'Patch{patch_number_string}:\t{os.path.basename(patch_filename)}\n')
        while not lines[-1].strip():
            lines = lines[:-1]
        with open(spec_file, 'w') as out:
            print(*lines, sep='', file=out)
        print(f'Патч {patch_filename} создан и записан в spec-файл.')
    if not edit_folder:
        print('Не определена папка для распаковки исходного кода.')
        return


def show_help():
    """
    Вывод справки. Выводится при любом некорректном вызове утилиты.
    """
    print(
        'Команды утилиты pirpm:\n'
        'pirpm install-packages-for-building - установка всех необходимых пакетов для сборки пакетов;\n'
        'pirpm replace-current-with-remote - замена текущего состояния проекта на состояние в удалённом репозитории;\n'
        'pirpm install-spec-build-requires [package.spec] [-r] - установка сборочных зависимостей указанного spec-файла и опциональное удаление полученного rpm-файла;\n'
        'pirpm remove-spec-build-requires [package.spec] - удаление сборочных зависимостей указанного spec-файла и удаление полученного rpm-файла;\n'
        'pirpm build-package [package.spec] [-i] [--no-delete] - выкачивание исходников, создание ссылки на abf, сборка пакета из указанного spec-файла и опциональная установка пакета после сборки; --no-delete не удаляет файлы, созданные для сборки;\n'
        'pirpm version-up [package.spec] [version] - повышение версии в указанном spec-файле;\n'
        'pirpm commit-update [package.spec] 712f35... - замена коммита и повышение релиза в указанном spec-файле;\n'
        'pirpm create-edit - копирование текущего проекта в ~/pirpm-edit для правки и последующего создания патча;\n'
        'pirpm create-patch [--message "commit message"]- создание патча из ~/pirpm-edit и его запись в spec-файл;\n'
        'pirpm help - вывод данной справки.'
    )


home_folder = pwd.getpwnam(os.environ.get('SUDO_USER') or os.environ.get('USER')).pw_dir
edit_folder = os.path.join(home_folder, 'pirpm-edit')

argv = sys.argv

if len(argv) < 2:
    show_help()
    sys.exit(0)

specs_in_cwd = [i for i in os.listdir(os.getcwd()) if i.endswith('.spec')]
# spec-файл во всех командах, где используется, стоит под 2 индексом.
# Если его не указать, но в текущей папке только один спек, имеет смысл дописать его автоматически.
if (len(argv) < 3 or not argv[2].endswith('.spec')) and argv[1] not in (
        'install-packages-for-building',
        'replace-current-with-remote',
        'help',
        '--help'
):
    if len(specs_in_cwd) == 1:
        argv.insert(2, specs_in_cwd[0])
        print(f'Не указан spec-файл. Найден в текущей папке: {specs_in_cwd[0]}')
    elif len(specs_in_cwd) == 0:
        print(f'Не указан spec-файл. В текущей папке также не найдено ни одного spec-файла. Утилита завершит работу.')
        sys.exit(0)
    else:
        print(f'Не указан spec-файл. В текущей папке найдено более одного spec-файла. Утилита завершит работу.')
        sys.exit(0)

if argv[1] == 'build-package' and len(specs_in_cwd) > 1:
    print(f'В текущей папке найдено более одного spec-файла. abf не поддерживает сборку пакетов в такой конфигурации. '
          f'Утилита завершит работу.')
    sys.exit(0)

if argv[1] == 'install-packages-for-building':
    install_packages_for_building()

elif argv[1] == 'replace-current-with-remote':
    replace_current_with_remote()

elif argv[1] == 'install-spec-build-requires':
    remove_rpm = True if '-r' in argv else False
    if '-r' in argv:
        argv.remove('-r')
    install_spec_build_requires(spec_file=argv[2], remove_rpm=remove_rpm)

elif argv[1] == 'remove-spec-build-requires':
    remove_spec_build_requires(spec_file=argv[2])

elif argv[1] == 'build-package':
    install_required = True if '-i' in argv else False
    if '-i' in argv:
        argv.remove('-i')
    no_delete = True if '--no-delete' in argv else False
    if '--no-delete' in argv:
        argv.remove('--no-delete')
    build_package(spec_file=argv[2], install_required=install_required, no_delete=no_delete)

elif argv[1] == 'version-up':
    version_up(spec_file=argv[2], specified_version=argv[3] if len(argv) > 3 else '')

elif argv[1] == 'commit-update':
    if len(argv) < 4:
        print('Укажите полный хэш коммита. '
              'Синтаксис: pirpm commit-update [file.spec] c9ecd3c39cf004fd777c6caaaa1f502e1e8475f0')
        sys.exit(0)
    commit_update(spec_file=argv[2], commit=argv[3])

elif argv[1] == 'create-edit':
    create_edit(spec_file=argv[2], edit_folder=edit_folder)

elif argv[1] == 'create-patch':
    try:
        if '--message' in argv:
            commit_message = argv[argv.index('--message') + 1]
        else:
            commit_message = 'pirpm'
        create_patch(spec_file=argv[2], edit_folder=edit_folder, commit_message=commit_message)
    except Exception as e:
        print(f'Некорректный вызов команды создания патча. Синтаксис: pirpm create-patch --message "Добавлен значок"\n'
              f'Исключение: {e}')
        sys.exit(0)

elif argv[1] in ('help', '--help'):
    show_help()

else:
    print(f'Команда {argv[1]} не найдена.')
    show_help()
