# -*- coding: utf-8 -*-

"""Utilities to fetch application configurations from the web."""

from __future__ import annotations

__all__ = ['ConfigurationDownloader', 'DOWNLOADER']

import functools
import os
import tempfile
import typing
from urllib import parse
import uuid

from qtpy import QtCore
import yaml

from anaconda_navigator.config import preferences, feature_flags
from anaconda_navigator.utils import download_manager as downloader
from anaconda_navigator.utils.logs import logger
from anaconda_navigator.utils import singletons
from anaconda_navigator.utils import url_utils

if typing.TYPE_CHECKING:
    from anaconda_navigator.config import structures
    from . import config_utils


class ConfigurationDownloader(QtCore.QObject):  # pylint: disable=too-few-public-methods
    """Utility that manages configuration files downloads for application tiles."""

    sig_configuration = QtCore.Signal()
    sig_resource = QtCore.Signal(str)
    _sig_fetch_resource = QtCore.Signal(str, str)

    def __init__(self, sources: 'structures.ApplicationSources') -> None:
        """Initialize new :class:`~ConfigurationDownloader` instance."""
        super().__init__()
        self.__manager: typing.Optional[downloader.Manager] = None
        self.__resources: typing.Final[typing.Dict[str, downloader.Download]] = {}
        self.__sources: typing.Final[structures.ApplicationSources] = sources

        self._sig_fetch_resource.connect(self.__fetch_resource)

    def download(self) -> None:
        """Download configuration files for application tiles."""
        if self.__manager is not None:
            return
        self.__manager = downloader.Manager()

        directory: str = tempfile.gettempdir()
        group: downloader.Group = downloader.Group(*(
            downloader.Download(
                source.url,
            ).extra(
                verify=True, headers=feature_flags.prepare_headers()
            ).into(
                os.path.join(directory, uuid.uuid4().hex),
            ).via(
                downloader.Medium.TARGET,
            ).process(
                functools.partial(self.__process_configuration, path=source.path),
            )
            for source in self.__sources.iter()
        )).require(
            downloader.Require.NONE,
        )
        group.sig_done.connect(self.__done)
        self.__manager.execute(group)

    def __process_configuration(self, download: downloader.Download, path: str) -> None:
        """Prepare fetched configuration file."""
        source: str = download.usable_file

        if os.path.getsize(source) > preferences.BIGGEST_APPLICATION_CONFIGURATION:
            logger.debug('%s: file is too large', download._url)  # pylint: disable=protected-access
            return

        stream: typing.TextIO
        with open(source, 'rt', encoding='utf-8') as stream:
            content: 'config_utils.AppConfiguration' = yaml.safe_load(stream) or {}

        record: 'config_utils.AppRecord'
        for record in content.values():
            try:
                # pylint: disable=protected-access
                record['image_path'] = self._request_resource(parse.urljoin(download._url, record['image_path']))
            except KeyError:
                continue

        os.makedirs(os.path.dirname(path), exist_ok=True)
        with open(path, 'wt', encoding='utf-8') as stream:
            yaml.safe_dump(content, stream)

    def __done(self) -> None:
        """
        Finish fetching configuration files.

        .. info::

            Icons could still be fetched in background.
        """
        self.__manager = None
        self.sig_configuration.emit()

    def _request_resource(self, url: str) -> str:
        """
        Request external resource located at `url`.

        Returns path to corresponding local resource.
        """
        target: str = os.path.join(self.__sources.root, url_utils.safe_file_name(url))
        self._sig_fetch_resource.emit(url, target)
        return target

    def __fetch_resource(self, url: str, target: str) -> None:
        """Perform resource fetching."""
        if url in self.__resources:
            return

        download: downloader.Download = (
            downloader.Download(url).extra(verify=True).into(target).keep(downloader.Keep.FRESH)
        )
        self.__resources[url] = download
        download.sig_done.connect(self.__process_resource)
        download._worker.start()  # pylint: disable=protected-access

    def __process_resource(self, download: downloader.Download) -> None:
        """Process resource after it is fetched."""
        self.__resources.pop(download._url)  # pylint: disable=protected-access
        if download.status == downloader.Status.SUCCEEDED:
            self.sig_resource.emit(download.usable_file)


DOWNLOADER: typing.Final[singletons.Singleton[ConfigurationDownloader]] = singletons.SingleInstanceOf(
    ConfigurationDownloader,
    sources=preferences.REMOTE_APPLICATION_SOURCES,
)
