import logging
import uuid

from os.path import isfile, join

from encore.events.api import ProgressManager

from egginst.progress import FileProgressManager
from egginst.utils import compute_md5, makedirs
from enstaller.fetch_utils import StoreResponse, checked_content
from enstaller.legacy_stores import URLFetcher
from enstaller.repository import egg_name_to_name_version


logger = logging.getLogger(__name__)


def encore_progress_manager_factory(event_manager, source, message, steps,
                                    operation_id=None):
    # Local import to avoid hard dependency on encore (this functionality is
    # only used within canopy)

    if operation_id is None:
        operation_id = uuid.uuid4()

    progress = ProgressManager(event_manager, source=source,
                               operation_id=operation_id, message=message,
                               steps=steps)
    return progress


class DownloadManager(object):
    def __init__(self, repository, local_dir, evt_mgr, auth=None):
        self._repository = repository
        self._fetcher = URLFetcher(local_dir, auth)
        self.local_dir = local_dir
        self.evt_mgr = evt_mgr

        makedirs(self.local_dir)

    def _path(self, fn):
        return join(self.local_dir, fn)

    def _fetch(self, package_metadata, execution_aborted=None):
        """ Fetch the given key.

        execution_aborted: a threading.Event object which signals when the execution
            needs to be aborted, or None, if we don't want to abort the fetching at all.
        """
        progress = encore_progress_manager_factory( "fetching",
                                                   package_metadata.key,
                                                   package_metadata.size,
                                                   self.evt_mgr, self)

        response = StoreResponse(self._fetcher.open(package_metadata.source_url),
                                 package_metadata.size)

        with FileProgressManager(progress) as progress:
            path = self._path(package_metadata.key)
            with checked_content(path, package_metadata.md5) as target:
                for chunk in response.iter_content():
                    if execution_aborted is not None and execution_aborted.is_set():
                        response.close()
                        target.abort = True
                        return

                    target.write(chunk)
                    progress.update(len(chunk))

    def _needs_to_download(self, package_metadata, force):
        needs_to_download = True
        path = self._path(package_metadata.key)

        if isfile(path):
            if force:
                if compute_md5(path) == package_metadata.md5:
                    logger.info("Not refetching, %r MD5 match", path)
                    needs_to_download = False
            else:
                logger.info("Not forcing refetch, %r exists", path)
                needs_to_download = False

        return needs_to_download

    def fetch_egg(self, egg, force=False, execution_aborted=None):
        """
        fetch an egg, i.e. copy or download the distribution into local dir
        force: force download or copy if MD5 mismatches
        execution_aborted: a threading.Event object which signals when the execution
            needs to be aborted, or None, if we don't want to abort the fetching at all.
        """
        name, version = egg_name_to_name_version(egg)
        package_metadata = self._repository.find_package(name, version)

        if self._needs_to_download(package_metadata, force):
            self._fetch(package_metadata, execution_aborted)
