import os
import logging
import shutil
import sys
import json

from urllib2 import urlopen
from StringIO import StringIO
from twisted.web import static
from twisted.internet import defer
from twisted.internet.task import LoopingCall
from lbrynet.conf import DEFAULT_UI_BRANCH, LOG_FILE_NAME
from lbrynet import __version__ as lbrynet_version
from lbryum.version import LBRYUM_VERSION as lbryum_version
from zipfile import ZipFile
from appdirs import user_data_dir

if sys.platform != "darwin":
    log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet")
else:
    log_dir = user_data_dir("LBRY")

if not os.path.isdir(log_dir):
    os.mkdir(log_dir)

lbrynet_log = os.path.join(log_dir, LOG_FILE_NAME)
log = logging.getLogger(__name__)


class LBRYUIManager(object):
    def __init__(self, root):
        if sys.platform != "darwin":
            self.data_dir = os.path.join(os.path.expanduser("~"), '.lbrynet')
        else:
            self.data_dir = user_data_dir("LBRY")

        self.ui_root = os.path.join(self.data_dir, "lbry-ui")
        self.active_dir = os.path.join(self.ui_root, "active")
        self.update_dir = os.path.join(self.ui_root, "update")

        if not os.path.isdir(self.data_dir):
            os.mkdir(self.data_dir)
        if not os.path.isdir(self.ui_root):
            os.mkdir(self.ui_root)
        if not os.path.isdir(self.active_dir):
            os.mkdir(self.active_dir)
        if not os.path.isdir(self.update_dir):
            os.mkdir(self.update_dir)

        self.config = os.path.join(self.ui_root, "active.json")
        self.update_requires = os.path.join(self.update_dir, "requirements.txt")
        self.requirements = {}
        self.check_requirements = True
        self.ui_dir = self.active_dir
        self.git_version = None
        self.root = root
        self.branch = None
        self.update_checker = LoopingCall(self.setup)

        if not os.path.isfile(os.path.join(self.config)):
            self.loaded_git_version = None
            self.loaded_branch = None
            self.loaded_requirements = None
        else:
            try:
                f = open(self.config, "r")
                loaded_ui = json.loads(f.read())
                f.close()
                self.loaded_git_version = loaded_ui['commit']
                self.loaded_branch = loaded_ui['branch']
                self.loaded_requirements = loaded_ui['requirements']
            except:
                self.loaded_git_version = None
                self.loaded_branch = None
                self.loaded_requirements = None

    def setup(self, branch=DEFAULT_UI_BRANCH, user_specified=None, branch_specified=False, check_requirements=None):
        if check_requirements is not None:
            self.check_requirements = check_requirements
        if self.branch is not None:
            self.branch = branch
        if user_specified:
            if os.path.isdir(user_specified):
                log.info("Checking user specified UI directory: " + str(user_specified))
                self.branch = "user-specified"
                self.loaded_git_version = "user-specified"
                d = self.migrate_ui(source=user_specified)
                d.addCallback(lambda _: self._load_ui())
                return d
            else:
                log.info("User specified UI directory doesn't exist, using " + self.branch)
        elif self.loaded_branch == "user-specified" and not branch_specified:
            log.info("Loading user provided UI")
            d = self._load_ui()
            return d
        else:
            log.info("Checking for updates for UI branch: " + branch)
            self._git_url = "https://s3.amazonaws.com/lbry-ui/{}/data.json".format(branch)
            self._dist_url = "https://s3.amazonaws.com/lbry-ui/{}/dist.zip".format(branch)

        d = self._up_to_date()
        d.addCallback(lambda r: self._download_ui() if not r else self._load_ui())
        return d

    def _up_to_date(self):
        def _get_git_info():
            response = urlopen(self._git_url)
            data = json.loads(response.read())
            return defer.succeed(data['sha'])

        def _set_git(version):
            self.git_version = version.replace('\n', '')
            if self.git_version == self.loaded_git_version:
                log.info("UI is up to date")
                return defer.succeed(True)
            else:
                log.info("UI updates available, checking if installation meets requirements")
                return defer.succeed(False)

        def _use_existing():
            log.info("Failed to check for new ui version, trying to use cached ui")
            return defer.succeed(True)

        d = _get_git_info()
        d.addCallbacks(_set_git, lambda _: _use_existing)
        return d

    def migrate_ui(self, source=None):
        if not source:
            requires_file = self.update_requires
            source_dir = self.update_dir
            delete_source = True
        else:
            requires_file = os.path.join(source, "requirements.txt")
            source_dir = source
            delete_source = False

        def _skip_requirements():
            log.info("Skipping ui requirement check")
            return defer.succeed(True)

        def _check_requirements():
            if not os.path.isfile(requires_file):
                log.info("No requirements.txt file, rejecting request to migrate this UI")
                return defer.succeed(False)

            f = open(requires_file, "r")
            for requirement in [line for line in f.read().split('\n') if line]:
                t = requirement.split('=')
                if len(t) == 3:
                    self.requirements[t[0]] = {'version': t[1], 'operator': '=='}
                elif t[0][-1] == ">":
                    self.requirements[t[0][:-1]] = {'version': t[1], 'operator': '>='}
                elif t[0][-1] == "<":
                    self.requirements[t[0][:-1]] = {'version': t[1], 'operator': '<='}
            f.close()
            passed_requirements = True
            for r in self.requirements:
                if r == 'lbrynet':
                    c = lbrynet_version
                elif r == 'lbryum':
                    c = lbryum_version
                else:
                    c = None
                if c:
                    if self.requirements[r]['operator'] == '==':
                        if not self.requirements[r]['version'] == c:
                            passed_requirements = False
                            log.info("Local version %s of %s does not meet UI requirement for version %s" % (
                            c, r, self.requirements[r]['version']))
                        else:
                            log.info("Local version of %s meets ui requirement" % r)
                    if self.requirements[r]['operator'] == '>=':
                        if not self.requirements[r]['version'] <= c:
                            passed_requirements = False
                            log.info("Local version %s of %s does not meet UI requirement for version %s" % (
                            c, r, self.requirements[r]['version']))
                        else:
                            log.info("Local version of %s meets ui requirement" % r)
                    if self.requirements[r]['operator'] == '<=':
                        if not self.requirements[r]['version'] >= c:
                            passed_requirements = False
                            log.info("Local version %s of %s does not meet UI requirement for version %s" % (
                            c, r, self.requirements[r]['version']))
                        else:
                            log.info("Local version of %s meets ui requirement" % r)
            return defer.succeed(passed_requirements)

        def _disp_failure():
            log.info("Failed to satisfy requirements for branch '%s', update was not loaded" % self.branch)
            return defer.succeed(False)

        def _do_migrate():
            if os.path.isdir(self.active_dir):
                shutil.rmtree(self.active_dir)
            shutil.copytree(source_dir, self.active_dir)
            if delete_source:
                shutil.rmtree(source_dir)

            log.info("Loaded UI update")

            f = open(self.config, "w")
            loaded_ui = {'commit': self.git_version, 'branch': self.branch, 'requirements': self.requirements}
            f.write(json.dumps(loaded_ui))
            f.close()

            self.loaded_git_version = loaded_ui['commit']
            self.loaded_branch = loaded_ui['branch']
            self.loaded_requirements = loaded_ui['requirements']
            return defer.succeed(True)

        d = _check_requirements() if self.check_requirements else _skip_requirements()
        d.addCallback(lambda r: _do_migrate() if r else _disp_failure())
        return d

    def _download_ui(self):
        def _delete_update_dir():
            if os.path.isdir(self.update_dir):
                shutil.rmtree(self.update_dir)
            return defer.succeed(None)

        def _dl_ui():
            url = urlopen(self._dist_url)
            z = ZipFile(StringIO(url.read()))
            names = [i for i in z.namelist() if '.DS_exStore' not in i and '__MACOSX' not in i]
            z.extractall(self.update_dir, members=names)
            log.info("Downloaded files for UI commit " + str(self.git_version).replace("\n", ""))
            return self.branch

        d = _delete_update_dir()
        d.addCallback(lambda _: _dl_ui())
        d.addCallback(lambda _: self.migrate_ui())
        d.addCallback(lambda _: self._load_ui())
        return d

    def _load_ui(self):
        for d in [i[0] for i in os.walk(self.active_dir) if os.path.dirname(i[0]) == self.active_dir]:
            self.root.putChild(os.path.basename(d), static.File(d))
        return defer.succeed(True)