import json
import os
import os.path as osp
import shutil
import subprocess
import sys
from pathlib import Path

import pkg_resources

try:
    from cookiecutter.main import cookiecutter
except ImportError:
    raise RuntimeError("Please install cookiecutter")


DEFAULT_COOKIECUTTER_BRANCH = "3.0"


def update_extension(target, branch=DEFAULT_COOKIECUTTER_BRANCH, interactive=True):
    """Update an extension to the current JupyterLab

    target: str
        Path to the extension directory containing the extension
    branch: str [default: DEFAULT_COOKIECUTTER_BRANCH]
        Template branch to checkout
    interactive: bool [default: true]
        Whether to ask before overwriting content

    """
    # Input is a directory with a package.json or the current directory
    # Use the cookiecutter as the source
    # Pull in the relevant config
    # Pull in the Python parts if possible
    # Pull in the scripts if possible
    target = osp.abspath(target)
    package_file = osp.join(target, 'package.json')
    setup_file = osp.join(target, 'setup.py')
    if not osp.exists(package_file):
        raise RuntimeError('No package.json exists in %s' % target)

    # Infer the options from the current directory
    with open(package_file) as fid:
        data = json.load(fid)

    if osp.exists(setup_file):
        python_name = subprocess.check_output([sys.executable, 'setup.py', '--name'], cwd=target).decode('utf8').strip()
    else:
        python_name = data['name']
        if '@' in python_name:
            python_name = python_name[1:].replace('/', '_').replace('-', '_')

    output_dir = osp.join(target, '_temp_extension')
    if osp.exists(output_dir):
        shutil.rmtree(output_dir)

    # Build up the cookiecutter args and run the cookiecutter
    extra_context = dict(
        author_name = data.get('author', '<author_name>'),
        labextension_name = data['name'],
        project_short_description = data.get('description', '<description>'),
        has_server_extension = 'y' if osp.exists(osp.join(target, 'jupyter-config')) else 'n',
        has_binder = 'y' if osp.exists(osp.join(target, 'binder')) else 'n',
        repository = data.get('repository', {}).get('url', '<repository'),
        python_name = python_name
    )

    template = 'https://github.com/jupyterlab/extension-cookiecutter-ts'
    cookiecutter(template=template, checkout=branch, output_dir=output_dir,
                extra_context=extra_context, no_input=not interactive)

    python_name = os.listdir(output_dir)[0]

    # hoist the output up one level
    shutil.move(osp.join(output_dir, python_name), osp.join(output_dir, '_temp'))
    for filename in os.listdir(osp.join(output_dir, '_temp')):
        shutil.move(osp.join(output_dir, '_temp', filename), osp.join(output_dir, filename))
    shutil.rmtree(osp.join(output_dir, '_temp'))

    # Check whether there are any phosphor dependencies
    has_phosphor = False
    for name in ['devDependencies', 'dependencies']:
        if name not in data:
            continue

        for (key, value) in list(data[name].items()):
            if key.startswith('@phosphor/'):
                has_phosphor = True
                data[name][key.replace('@phosphor/', '@lumino/')] = value

        for key in list(data[name]):
            if key.startswith('@phosphor/'):
                del data[name][key]

    # From the created package.json grab the devDependencies
    with open(osp.join(output_dir, 'package.json')) as fid:
        temp_data = json.load(fid)

    if data.get('devDependencies'):
        for (key, value) in temp_data['devDependencies'].items():
            data['devDependencies'][key] = value
    else:
        data['devDependencies'] = temp_data['devDependencies'].copy()

    # Ask the user whether to upgrade the scripts automatically
    warnings = []
    if interactive:
        choice = input('overwrite scripts in package.json? [n]: ')
    else:
        choice = 'y'
    if choice.upper().startswith('Y'):
        warnings.append('Updated scripts in package.json')
        data.setdefault('scripts', dict())
        for (key, value) in temp_data['scripts'].items():
            data['scripts'][key] = value
        if 'install-ext' in data['scripts']:
            del data['scripts']['install-ext']
    else:
        warnings.append('package.json scripts must be updated manually')

    # Set the output directory
    data['jupyterlab']['outputDir'] = temp_data['jupyterlab']['outputDir']

    # Look for resolutions in JupyterLab metadata and upgrade those as well
    root_jlab_package = pkg_resources.resource_filename('jupyterlab', 'staging/package.json')
    with open(root_jlab_package) as fid:
        root_jlab_data = json.load(fid)

    data.setdefault('dependencies', dict())
    data.setdefault('devDependencies', dict())
    for (key, value) in root_jlab_data['resolutions'].items():
        if key in data['dependencies']:
            data['dependencies'][key] = value.replace('~', '^')
        if key in data['devDependencies']:
            data['devDependencies'][key] = value.replace('~', '^')

    # Sort the entries
    for key in ['scripts', 'dependencies', 'devDependencies']:
        if data[key]:
            data[key] = dict(sorted(data[key].items()))
        else:
            del data[key]

    # Update style settings
    data.setdefault('styleModule', 'style/index.js')
    if isinstance(data.get('sideEffects'), list) and 'style/index.js' not in data['sideEffects']:
        data['sideEffects'].append('style/index.js')
    if 'files' in data and 'style/index.js' not in data['files']:
        data['files'].append('style/index.js')

    # Update the root package.json file
    with open(package_file, 'w') as fid:
        json.dump(data, fid, indent=2)

    # For the other files, ask about whether to override (when it exists)
    # At the end, list the files that were: added, overridden, skipped
    path = Path(output_dir)
    for p in path.rglob("*"):
        relpath = osp.relpath(p, path)
        if relpath == "package.json":
            continue
        if p.is_dir():
            continue
        file_target = osp.join(target, relpath)
        if not osp.exists(file_target):
            os.makedirs(osp.dirname(file_target), exist_ok=True)
            shutil.copy(p, file_target)
        else:
            with open(p, "rb") as fid:
                old_data = fid.read()
            with open(file_target, "rb") as fid:
                new_data = fid.read()
            if old_data == new_data:
                continue
            if interactive:
                choice = input('overwrite "%s"? [n]: ' % relpath)
            else:
                choice = 'n'
            if choice.upper().startswith('Y'):
                shutil.copy(p, file_target)
            else:
                warnings.append('skipped _temp_extension/%s' % relpath)

    # Print out all warnings
    for warning in warnings:
        print('**', warning)

    print('** Remove _temp_extensions directory when finished')

    if has_phosphor:
        print('** Phosphor dependencies were upgraded to lumino dependencies, update imports as needed')


if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description='Upgrade a JupyterLab extension')

    parser.add_argument('--no-input',
                       action='store_true',
                       help='whether to prompt for information')

    parser.add_argument('path',
                       action='store',
                       type=str,
                       help='the target path')

    parser.add_argument('--branch',
                        help='the template branch to checkout',
                        default=DEFAULT_COOKIECUTTER_BRANCH)

    args = parser.parse_args()

    update_extension(args.path, args.branch, args.no_input==False)
