#!/usr/bin/python3
# -*- coding: utf-8 -*-


import os
import time
import json
from pathlib import Path
from collections import OrderedDict

from slpkg.upgrade import Upgrade
from slpkg.configs import Configs
from slpkg.checksum import Md5sum
from slpkg.views.views import View
from slpkg.utilities import Utilities
from slpkg.dialog_box import DialogBox
from slpkg.gpg_verify import GPGVerify
from slpkg.downloader import Downloader
from slpkg.views.asciibox import AsciiBox
from slpkg.progress_bar import ProgressBar
from slpkg.multi_process import MultiProcess
from slpkg.binaries.required import Required
from slpkg.views.view_process import ViewProcess


class Packages(Configs):  # pylint: disable=[R0902]
    """Download and install packages with dependencies."""

    def __init__(self, repository: str, data: dict, packages: list, flags: list, mode: str):  # pylint: disable=[R0913]
        super(Configs, self).__init__()
        self.repository = repository
        self.data: dict = data
        self.packages: list = packages
        self.flags: list = flags
        self.mode: str = mode

        self.utils = Utilities()
        self.dialogbox = DialogBox()
        self.multi_proc = MultiProcess(flags)
        self.view = View(flags, repository, data)
        self.view_process = ViewProcess()
        self.check_md5 = Md5sum(flags)
        self.download = Downloader(flags)
        self.upgrade = Upgrade(repository, data)
        self.ascii = AsciiBox()
        self.gpg = GPGVerify()
        self.progress = ProgressBar()

        self.dependencies: list = []
        self.install_order: list = []
        self.binary_packages: list = []
        self.skipped_packages: list = []
        self.progress_message: str = f'{self.cyan}Installing{self.endc}'
        self.bar_process = None

        self.option_for_reinstall: bool = self.utils.is_option(
            ('-r', '--reinstall'), flags)

        self.option_for_skip_installed: bool = self.utils.is_option(
            ('-k', '--skip-installed'), flags)

        self.packages: list = self.utils.apply_package_pattern(data, packages)

    def execute(self) -> None:
        """Call methods in order."""
        self.view_process.message('Resolving dependencies')
        self.creating_dependencies_list()
        self.choose_package_dependencies()
        self.add_dependencies_to_install_order()
        self.clean_the_main_slackbuilds()
        self.add_main_packages_to_install_order()
        self.check_for_skipped()

        self.view.install_upgrade_packages(self.packages, self.dependencies, self.mode)
        self.view.missing_dependencies(self.install_order)
        self.view.question()

        start: float = time.time()
        self.view.skipping_packages(self.skipped_packages)
        self.creating_the_package_urls_list()
        self.checksum_binary_packages()
        self.set_progress_message()
        self.install_packages()
        elapsed_time: float = time.time() - start

        self.utils.finished_time(elapsed_time)

    def creating_dependencies_list(self) -> None:
        """Create the full list of dependencies."""
        for package in self.packages:
            dependencies: tuple = Required(self.data, package, self.flags).resolve()

            for dependency in dependencies:
                self.dependencies.append(dependency)

        self.dependencies: list = list(OrderedDict.fromkeys(self.dependencies))

    def add_dependencies_to_install_order(self) -> None:
        """Add dependencies in order to install."""
        self.install_order.extend(self.dependencies)

    def clean_the_main_slackbuilds(self) -> None:
        """Remove packages that already listed in dependencies."""
        for dependency in self.dependencies:
            if dependency in self.packages:
                self.packages.remove(dependency)

    def add_main_packages_to_install_order(self) -> None:
        """Add main packages in order to install."""
        self.install_order.extend(self.packages)

    def check_for_skipped(self) -> None:
        """Skip packages by user."""
        if self.option_for_skip_installed:
            for name in self.install_order:
                installed: str = self.utils.is_package_installed(name)
                if installed:
                    self.skipped_packages.append(name)

        # Remove packages from skipped packages.
        self.install_order: list = [pkg for pkg in self.install_order if pkg not in self.skipped_packages]

    def creating_the_package_urls_list(self) -> None:
        """Prepare package urls for downloading."""
        packages: dict = {}
        asc_files: list = []
        if self.install_order:
            self.view_process.message('Prepare sources for downloading')
            for pkg in self.install_order:
                package: str = self.data[pkg]['package']
                mirror: str = self.data[pkg]['mirror']
                location: str = self.data[pkg]['location']
                url: list = [f'{mirror}{location}/{package}']
                asc_url: list = [f'{mirror}{location}/{package}.asc']
                asc_file: Path = Path(self.tmp_slpkg, f'{package}.asc')

                packages[pkg] = (url, self.tmp_slpkg)
                if self.gpg_verification:
                    packages[f'{pkg}.asc'] = (asc_url, self.tmp_slpkg)
                    asc_files.append(asc_file)

                self.binary_packages.append(package)

            self.view_process.done()
            self.download_the_binary_packages(packages)
            if self.gpg_verification:
                self.gpg.verify(asc_files)

    def download_the_binary_packages(self, packages: dict) -> None:
        """Download the packages.

        Args:
            packages (dict): Packages for downloading.
        """
        if packages:
            print(f'Started to download total ({self.cyan}{len(packages)}{self.endc}) packages:\n')
            self.download.download(packages)
            print()

    def checksum_binary_packages(self) -> None:
        """Checksum packages."""
        for package in self.binary_packages:
            name: str = self.utils.split_package(Path(package).stem)['name']
            pkg_checksum: str = self.data[name]['checksum']
            self.check_md5.md5sum(self.tmp_slpkg, package, pkg_checksum)

    def install_packages(self) -> None:
        """Install the packages."""
        # Remove old slpkg.log file.
        if self.slpkg_log_file.is_file():
            self.slpkg_log_file.unlink()

        if self.binary_packages:
            print(f'Started the processing of ({self.cyan}{len(self.binary_packages)}{self.endc}) packages:\n')

            for package in self.binary_packages:
                command: str = f'{self.installpkg} {self.tmp_slpkg}/{package}'
                if self.option_for_reinstall:
                    command: str = f'{self.reinstall} {self.tmp_slpkg}/{package}'

                self.multi_proc.process_and_log(command, package, self.progress_message)
                name: str = self.utils.split_package(package)['name']
                self.write_deps_log(name)

                if self.delete_sources:
                    self.utils.remove_file_if_exists(self.tmp_slpkg, package)

    def write_deps_log(self, name: str) -> None:
        """Create log file with installed packages with dependencies.

        Args:
            name (str): Package name.
        """
        deps: dict = {}
        deps_logs: dict = {}
        installed_requires: list = []
        requires: tuple = Required(self.data, name, self.flags).resolve()
        # Verify for installation.
        for req in requires:
            if self.utils.is_package_installed(req):
                installed_requires.append(req)

        deps[name] = installed_requires
        if self.deps_log_file.is_file():
            deps_logs: dict = self.utils.read_json_file(self.deps_log_file)
            deps_logs.update(deps)
        self.deps_log_file.write_text(json.dumps(deps_logs, indent=4), encoding='utf-8')

    def set_progress_message(self) -> None:
        """Set message for upgrade method."""
        if self.mode == 'upgrade' or self.option_for_reinstall:
            self.progress_message: str = f'{self.violet}Upgrading{self.endc}'

    def choose_package_dependencies(self) -> None:
        """Choose dependencies for install with dialog tool."""
        if self.dependencies and self.dialog:
            height: int = 10
            width: int = 70
            list_height: int = 0
            choices: list = []
            title: str = ' Choose dependencies you want to install '

            for package in self.dependencies:
                status: bool = True
                repo_ver: str = self.data[package]['version']
                help_text: str = f'Package: {package} {repo_ver}'
                installed: str = self.utils.is_package_installed(package)
                upgradeable: bool = self.upgrade.is_package_upgradeable(installed)

                if installed:
                    status: bool = False

                if self.mode == 'upgrade' and upgradeable:
                    status: bool = True

                if self.option_for_reinstall:
                    status: bool = True

                choices.extend([(package, repo_ver, status, help_text)])

            self.view_process.done()

            text: str = f'There are {len(choices)} dependencies:'
            code, self.dependencies = self.dialogbox.checklist(text, title, height, width, list_height, choices)  # pylint: disable=[W0612]

            os.system('clear')
        else:
            self.view_process.done()
