"""
Validate the dependencies are installed.
"""
from __future__ import print_function

__all__ = [
    'installRequiredToolbox',
    'downloadDependency',
    'addExtensionPath',
    'loadExtensions',
    'installPackage',
    'tokenRefresher',
    'validateToken',
    'checkInstall',
    'updateToken',
    'TokenError',
    'userToken',
]

# python imports
import re
import os
import sys
import imp
import json
import urllib
import shutil
import inspect
import zipfile
import threading
import traceback
import subprocess

# Tools imports
import tools
from tools import HFX_PATH


TOKEN = None
PIP_CHECKED = False
INSTALL_CHECKED = False
INSTALLED = []


class TokenError(Exception):
    def __init__(self, *args):
        tools.TOOLS_LOGGER.error(args[0])


# install handling
def downloadDependency(url, saveAs=None):
    """
    Download a file required for FT.

    :param url:
    :param saveAs:
    """
    localPath = os.path.join(tools.PACKAGES, os.path.basename(url) if not saveAs else saveAs)
    return urllib.urlretrieve(url, localPath)[0]

def installPackage(package, pipName=None, test=True):
    """
Install packages into FT from pip.

:param package: the name to import the package
:param pipName: in-case the pip install name is different from the module name.
    """
    global PIP_CHECKED

    # check if pip is installed. This is installed at the Python installs site-packages. Everything else is installed in
    # the FloatingTools/packages directory.
    executable = None
    args = []
    if tools.activeWrapper():
        if tools.activeWrapper().ARGS:
            args = tools.activeWrapper().ARGS
        if tools.activeWrapper().EXECUTABLE:
            executable = tools.activeWrapper().EXECUTABLE

    if not executable:
        # determine executable from the application wrapper
        prefixLower = [f.lower() for f in prefix]

        for possible in ['python', 'python.exe']:
            if possible in prefixLower:
                executable = os.path.abspath(os.path.join(sys.exec_prefix, prefix[prefixLower.index(possible)]))
                break

    try:
        import pip
    except ImportError:
        tools.TOOLS_LOGGER.info("Python executable (+args) for pip install: " + executable)

        # install pip
        downloadPath = downloadDependency("https://raw.githubusercontent.com/aldmbmtl/tools/master/get-pip.py")

        with open(downloadPath, 'r') as pipDL:
            code = pipDL.read()
            code = code.replace('sys.exit(pip.main(["install", "--upgrade"] + args))',
                                'sys.exit(pip.main(["install", "pip", "-t", "%s"]))' % tools.PACKAGES)

        with open(downloadPath, 'w') as pipDL:
            pipDL.write(code)

        command = [os.path.abspath(executable)] + args + [downloadPath]
        # execute the python pip install call
        subprocess.call(command)

        # delete get-pip.py
        os.unlink(downloadPath)

        try:
            import pip
        except ImportError:
            raise Exception('Pip is required for install.' %
                            os.path.abspath(tools.__file__ + '/../../'))

    # Verify the the target package exists
    try:
        __import__(package)
    except ImportError:
        if not pipName:
            pipName = package

        command = [os.path.abspath(executable), os.path.dirname(pip.__file__), 'install', pipName, '-t', tools.PACKAGES, '--no-cache-dir']
        tools.TOOLS_LOGGER.info('Installing: ' + pipName)
        tools.TOOLS_LOGGER.info(command)
        subprocess.call(command)

        if test:
            # verify install
            __import__(package)

# handle token system
def updateToken(token):
    """
    For internal use.
    """
    with open(tools.HFX_TOKEN, 'w') as tokenFile:
        tokenFile.write(token)

    validateToken()

    # relaunch the initialize process with the new token
    tools.initialize()

def validateToken():
    """
    Checks the token created.
    """
    global TOKEN

    import requests

    response = requests.get(tools.buildCall('shed'), headers={'Authorization': str(TOKEN)}, verify=False)
    if not TOKEN or response.status_code == 401:
        if not os.path.exists(tools.HFX_TOKEN):
            with open(tools.HFX_TOKEN, 'w') as tokenFile:
                tokenFile.write('')

        with open(tools.HFX_TOKEN, 'r') as tokenFile:
            refreshToken = tokenFile.read()

            tools.TOOLS_LOGGER.info('Validating Access...')

            data = 'grant_type=refresh_token&client_id=2okiehdqupvt6icqil6nl255pg&refresh_token=' + refreshToken
            refreshResponse = requests.post(
                'https://dev-floating-tools.auth.us-west-2.amazoncognito.com/oauth2/token',
                data=data,
                headers={'Content-Type': 'application/x-www-form-urlencoded'}
            )
            if 'id_token' in refreshResponse.json():
                tools.TOOLS_LOGGER.info('Access Granted...')
                TOKEN = refreshResponse.json()['id_token']
                return

        raise TokenError('%s' % response.json()['message'])

def tokenRefresher():
    try:
        validateToken()
    except TokenError:
        if tools.activeWrapper():
            tools.activeWrapper().updateToken()
        else:
            tools.Wrapper.updateToken()

def userToken():
    """
    Grab the users token.
    """
    global TOKEN

    # check the token saved in memory
    if not TOKEN:
        tokenRefresher()

    return TOKEN


# extension handling
def addExtensionPath(path):
    """
Add a custom extensions path for your scripts and modifications to FloatingTools.

:param path: str to a place on disk.
    """
    if not os.path.exists(path):
        tools.TOOLS_LOGGER.warning('Extension path passed does not exist: ' + path)
        return

    for f in os.listdir(path):
        if f == 'hfx_init.py':
            try:
                imp.load_source('hfx_init', os.path.join(path, f))
            except ImportError:
                traceback.print_exc()

def loadExtensions():
    if 'HFX_PATH' in os.environ:
        path = os.environ['HFX_PATH']
        addExtensionPath(path)

# pipeline installers
def checkInstall():
    """
    Updates the existing install of the HFX pipeline.
    """
    global INSTALL_CHECKED

    if INSTALL_CHECKED or 'HFX_DEV':
        return

    tools.TOOLS_LOGGER.info('Running version check...')
    targetVersion = os.environ['HFX_INSTALL_VERSION']
    if targetVersion == 'latest':
        targetVersion = max(eval(urllib.urlopen('https://api.hatfieldfx.com/ft/releases').read()))

    INSTALL_CHECKED = True
    currentVersion = 'unknown'
    if os.path.exists(os.path.expanduser('~/.hfx/version')):
        with open(os.path.expanduser('~/.hfx/version'), 'r') as versionFile:
            currentVersion = versionFile.read()

    tools.TOOLS_LOGGER.info('Installed version: ' + currentVersion)

    if targetVersion != currentVersion:
        tools.TOOLS_LOGGER.info('Updating install: %s => %s' % (currentVersion, targetVersion))
        os.environ['HFX_INSTALL_VERSION'] = targetVersion
        os.environ['HFX_UPDATE'] = '1'
        exec urllib.urlopen('https://raw.githubusercontent.com/aldmbmtl/tools/master/installer.py').read()

        # force reload FT
        imp.reload(tools)
        tools.TOOLS_LOGGER.info('Upgraded to: %s' % targetVersion)
    INSTALL_CHECKED = True

def installRequiredToolbox(uid, service='Local_Path', **kwargs):
    """
    Install a toolbox programmatically.

    :param uid: name of the toolbox or uid number. MUST BE UNIQUE!!
    :param service: name of the service
    :param kwargs: fields required to install the toolbox
    """
    global INSTALLED

    service = str(service)
    envVar = 'HFX-ENV-' + str(uid)
    devMode = False
    serviceName = 'Local_Path'
    box = None

    try:
        toolboxConstruct = dict(service=service, **kwargs)
        if toolboxConstruct in INSTALLED:
            return

        INSTALLED.append(toolboxConstruct)

        if envVar in os.environ:
            envValue = os.environ[envVar]
            paths = envValue.split(';')

            tools.TOOLS_LOGGER.info('Development path(s) detected in ENV: %s = %s' % (uid, ', '.join(paths)))
            for path in paths:
                cleaned = path.strip()

                if os.path.exists(cleaned):
                    service = tools.Service.get('Local_Path')
                    box = service(source_tag=uid, Path=cleaned)
                    box.setName(box.name())
                    devMode = True
                    tools.TOOLS_LOGGER.info('Development Path Set: %s => %s' % (uid, cleaned))
                    break

            if not devMode:
                tools.TOOLS_LOGGER.warning('Development Path(s) Not Found: %s' % uid)

        if not devMode:
            # pull service
            serviceName = 'Local_Path'

            for loadedService in tools.shed()['services']:

                if loadedService['name'] == service:
                    serviceName = loadedService['name']
                    break

                if service.isdigit() and loadedService['id'] == int(service):
                    serviceName = loadedService['name']
                    break

            if serviceName == 'Local_Path':
                kwargs = dict(Path=service)

            if not serviceName:
                raise Exception('Service passed for install does not exist! Cant install toolbox.\n\tPassed Service: %s' % serviceName)

            service = tools.Service.get(serviceName)
            box = service(source_tag=uid, **kwargs)

        # only install if it is required
        if not os.path.exists(box.installDirectory()):
            tools.TOOLS_LOGGER.info(
                '%s => %s %s.\n\t+=> %s\n' % (
                    serviceName,
                    'Referencing' if serviceName == 'Local_Path' else 'Installing',
                    box.name(),
                    box.installDirectory()
                ))
            box.install()

        tools.TOOLS_LOGGER.info(
            'Loading %s (CACHED)\n\t+=> %s' % (
                box.name(),
                box.installDirectory()
            ))
        box.loadTools()
    except:
        traceback.print_exc()
        executable = os.path.abspath(sys.executable)

        prefix = os.listdir(sys.exec_prefix)