import os
import shutil
from argparse import Namespace
from datetime import datetime
from typing import Dict, List

from bauh.core import disk
from bauh.core.controller import ApplicationManager
from bauh.core.disk import DiskCacheLoader
from bauh.core.model import ApplicationData, Application, ApplicationUpdate
from bauh.core.snap import snap
from bauh.core.snap.model import SnapApplication
from bauh.core.snap.worker import SnapAsyncDataLoader
from bauh.core.system import BauhProcess
from bauh.util.cache import Cache


class SnapManager(ApplicationManager):

    def __init__(self, app_args: Namespace,  api_cache: Cache, disk_cache: bool, http_session, locale_keys: dict):
        super(SnapManager, self).__init__(app_args=app_args, locale_keys=locale_keys)
        self.api_cache = api_cache
        self.http_session = http_session
        self.disk_cache = disk_cache

    def map_json(self, app_json: dict, installed: bool,  disk_loader: DiskCacheLoader) -> SnapApplication:
        app = SnapApplication(publisher=app_json.get('publisher'),
                              rev=app_json.get('rev'),
                              notes=app_json.get('notes'),
                              app_type=app_json.get('type'),
                              base_data=ApplicationData(id=app_json.get('name'),
                                                        name=app_json.get('name'),
                                                        version=app_json.get('version'),
                                                        latest_version=app_json.get('version'),
                                                        description=app_json.get('description')
                                                        ))

        if app.publisher:
            app.publisher = app.publisher.replace('*', '')

        app.installed = installed

        api_data = self.api_cache.get(app_json['name'])
        expired_data = api_data and api_data.get('expires_at') and api_data['expires_at'] <= datetime.utcnow()

        if (not api_data or expired_data) and not app.is_library():
            if disk_loader and app.installed:
                disk_loader.add(app)

            SnapAsyncDataLoader(app=app, api_cache=self.api_cache, manager=self, http_session=self.http_session, download_icons=self.app_args.download_icons).start()
        else:
            app.fill_cached_data(api_data)

        return app

    def search(self, word: str, disk_loader: DiskCacheLoader) -> Dict[str, List[SnapApplication]]:
        installed = self.read_installed(disk_loader)

        res = {'installed': [], 'new': []}

        for app_json in snap.search(word):

            already_installed = None

            if installed:
                already_installed = [i for i in installed if i.base_data.id == app_json.get('name')]
                already_installed = already_installed[0] if already_installed else None

            if already_installed:
                res['installed'].append(already_installed)
            else:
                res['new'].append(self.map_json(app_json, installed=False, disk_loader=disk_loader))

        return res

    def read_installed(self, disk_loader: DiskCacheLoader) -> List[SnapApplication]:
        return [self.map_json(app_json, installed=True, disk_loader=disk_loader) for app_json in snap.read_installed()]

    def downgrade_app(self, app: Application, root_password: str) -> BauhProcess:
        return BauhProcess(subproc=snap.downgrade_and_stream(app.base_data.name, root_password), wrong_error_phrase=None)

    def clean_cache_for(self, app: SnapApplication):
        self.api_cache.delete(app.base_data.name)

        if app.supports_disk_cache() and os.path.exists(app.get_disk_cache_path()):
            shutil.rmtree(app.get_disk_cache_path())

    def can_downgrade(self):
        return True

    def update_and_stream(self, app: SnapApplication) -> BauhProcess:
        pass

    def uninstall_and_stream(self, app: SnapApplication, root_password: str) -> BauhProcess:
        return BauhProcess(subproc=snap.uninstall_and_stream(app.base_data.name, root_password))

    def get_app_type(self):
        return SnapApplication

    def get_info(self, app: SnapApplication) -> dict:
        info = snap.get_info(app.base_data.name, attrs=('license', 'contact', 'commands', 'snap-id', 'tracking', 'installed'))
        info['description'] = app.base_data.description
        info['publisher'] = app.publisher
        info['revision'] = app.rev
        info['name'] = app.base_data.name

        if info.get('commands'):
            info['commands'] = ' '.join(info['commands'])

        return info

    def get_history(self, app: Application) -> List[dict]:
        return []

    def install_and_stream(self, app: SnapApplication, root_password: str) -> BauhProcess:
        return BauhProcess(subproc=snap.install_and_stream(app.base_data.name, app.confinement, root_password))

    def is_enabled(self) -> bool:
        return snap.is_installed()

    def cache_to_disk(self, app: Application, icon_bytes: bytes, only_icon: bool):
        if self.disk_cache and app.supports_disk_cache():
            disk.save(app, icon_bytes, only_icon)

    def requires_root(self, action: str, app: SnapApplication):
        return action != 'search'

    def refresh(self, app: SnapApplication, root_password: str) -> BauhProcess:
        return BauhProcess(subproc=snap.refresh_and_stream(app.base_data.name, root_password))

    def prepare(self):
        pass

    def list_updates(self) -> List[ApplicationUpdate]:
        return []

    def list_warnings(self) -> List[str]:
        if snap.get_snapd_version() == 'unavailable':
            return [self.locale_keys['snap.notification.snapd_unavailable']]

    def list_suggestions(self, limit: int) -> List[SnapApplication]:

        suggestions = []

        if limit != 0:
            for name in ('whatsdesk', 'slack', 'yakyak', 'instagraph', 'pycharm-professional', 'eclipse', 'gimp', 'supertuxkart'):
                res = snap.search(name, exact_name=True)
                if res:
                    suggestions.append(self.map_json(res[0], installed=False, disk_loader=None))

                if len(suggestions) == limit:
                    break

        return suggestions
