'''

    anaconda upload CONDA_PACKAGE_1.bz2
    anaconda upload notebook.ipynb
    anaconda upload environment.yml

##### See Also

  * [Uploading a Conda Package](http://docs.anaconda.org/using.html#Uploading)
  * [Uploading a PyPI Package](http://docs.anaconda.org/using.html#UploadingPypiPackages)

'''
from __future__ import unicode_literals

import tempfile
import argparse
import subprocess
from glob import glob
import logging
import os
from os.path import exists
import sys
from collections import defaultdict

import nbformat

from binstar_client import errors
from binstar_client.utils import bool_input
from binstar_client.utils import get_server_api
from binstar_client.utils import get_config
from binstar_client.utils import upload_print_callback
from binstar_client.utils.projects import upload_project
from binstar_client.utils.detect import detect_package_type, get_attrs


# Python 3 Support
try:
    input = raw_input
except NameError:
    input = input


logger = logging.getLogger('binstar.upload')


PACKAGE_TYPES = defaultdict(lambda: 'Package', {'env': 'Environment', 'ipynb': 'Notebook'})


def verbose_package_type(pkg_type, lowercase=True):
    verbose_type = PACKAGE_TYPES[pkg_type]
    if lowercase:
        verbose_type = verbose_type.lower()
    return verbose_type


def create_release(aserver_api, username, package_name, version, release_attrs, announce=None):
    aserver_api.add_release(username, package_name, version, [], announce, release_attrs)


def create_release_interactive(aserver_api, username, package_name, version, release_attrs):
    logger.info('\nThe release %s/%s/%s does not exist' % (username, package_name, version))
    if not bool_input('Would you like to create it now?'):
        logger.info('good-bye')
        raise SystemExit(-1)

    description = input('Enter a short description of the release:\n')
    logger.info("\nAnnouncements are emailed to your package followers.")
    make_announcement = bool_input('Would you like to make an announcement to the package followers?', False)
    if make_announcement:
        announce = input('Markdown Announcement:\n')
    else:
        announce = ''

    aserver_api.add_release(username, package_name, version, [], announce, release_attrs)


def determine_package_type(filename, args):
    """
    return the file type from the inspected package or from the
    -t/--package-type argument
    """
    if args.package_type:
        package_type = args.package_type
    else:
        logger.info('detecting file type ...')
        sys.stdout.flush()
        package_type = detect_package_type(filename)
        if package_type is None:
            message = 'Could not detect package type of file %r please specify package type with option --package-type' % filename
            logger.error(message)
            raise errors.BinstarError(message)
        logger.info(package_type)

    return package_type


def get_package_name(args, package_attrs, filename, package_type):
    if args.package:
        if 'name' in package_attrs and package_attrs['name'].lower() != args.package.lower():
            msg = 'Package name on the command line " {}" does not match the package name in the file "{}"'.format(
                args.package.lower(), package_attrs['name'].lower()
            )
            logger.error(msg)
            raise errors.BinstarError(msg)
        package_name = args.package
    else:
        if 'name' not in package_attrs:
            message = "Could not detect package name for package type %s, please use the --package option" % (package_type,)
            logger.error(message)
            raise errors.BinstarError(message)
        package_name = package_attrs['name']

    return package_name


def get_version(args, release_attrs, package_type):
    if args.version:
        version = args.version
    else:
        if 'version' not in release_attrs:
            message = "Could not detect package version for package type %s, please use the --version option" % (package_type,)
            logger.error(message)
            raise errors.BinstarError(message)
        version = release_attrs['version']
    return version


def add_package(aserver_api, args, username, package_name, package_attrs, package_type):
    try:
        return aserver_api.package(username, package_name)
    except errors.NotFound:
        if not args.auto_register:
            message = (
                'Anaconda Cloud package %s/%s does not exist. '
                'Please run "anaconda package --create" to create this package namespace in the cloud.' %
                (username, package_name)
            )
            logger.error(message)
            raise errors.UserError(message)
        else:

            if args.summary:
                summary = args.summary
            else:
                if 'summary' not in package_attrs:
                    message = "Could not detect package summary for package type %s, please use the --summary option" % (package_type,)
                    logger.error(message)
                    raise errors.BinstarError(message)
                summary = package_attrs['summary']

            public = not args.private

            return aserver_api.add_package(
                username,
                package_name,
                summary,
                package_attrs.get('license'),
                public=public,
                attrs=package_attrs,
                license_url=package_attrs.get('license_url'),
                license_family=package_attrs.get('license_family'),
                package_type=package_type,
            )


def add_release(aserver_api, args, username, package_name, version, release_attrs):
    try:
        # Check if the release already exists
        aserver_api.release(username, package_name, version)
    except errors.NotFound:
        if args.mode == 'interactive':
            create_release_interactive(aserver_api, username, package_name, version, release_attrs)
        else:
            create_release(aserver_api, username, package_name, version, release_attrs)


def remove_existing_file(aserver_api, args, username, package_name, version, file_attrs):
    try:
        aserver_api.distribution(username, package_name, version, file_attrs['basename'])
    except errors.NotFound:
        return False
    else:
        if args.mode == 'force':
            logger.warning('Distribution %s already exists ... removing' % (file_attrs['basename'],))
            aserver_api.remove_dist(username, package_name, version, file_attrs['basename'])
        if args.mode == 'interactive':
            if bool_input('Distribution %s already exists. Would you like to replace it?' % (file_attrs['basename'],)):
                aserver_api.remove_dist(username, package_name, version, file_attrs['basename'])
            else:
                logger.info('Not replacing distribution %s' % (file_attrs['basename'],))
                return True


def upload_package(filename, package_type, aserver_api, username, args):
    logger.info('extracting {} attributes for upload ...'.format(verbose_package_type(package_type)))
    sys.stdout.flush()
    try:
        package_attrs, release_attrs, file_attrs = get_attrs(package_type, filename, parser_args=args)
    except Exception:
        message = 'Trouble reading metadata from {}. Is this a valid {} package?'.format(
            filename, verbose_package_type(package_type)
        )
        logger.error(message)
        if args.show_traceback:
            raise
        raise errors.BinstarError(message)

    if args.build_id:
        file_attrs['attrs']['binstar_build'] = args.build_id

    logger.info('done')

    package_name = get_package_name(args, package_attrs, filename, package_type)
    version = get_version(args, release_attrs, package_type)

    package = add_package(aserver_api, args, username, package_name, package_attrs, package_type)
    if package_type not in package.get('package_types', []):
        package_types = package.get('package_types')
        message = 'You already have a {} named \'{}\'. Use a different name for this {}.'.format(
            verbose_package_type(package_types[0] if package_types else ''),
            package_name,
            verbose_package_type(package_type),
        )
        logger.error(message)
        raise errors.BinstarError(message)
    add_release(aserver_api, args, username, package_name, version, release_attrs)
    binstar_package_type = file_attrs.pop('binstar_package_type', package_type)

    with open(filename, 'rb') as fd:
        logger.info('\nUploading file %s/%s/%s/%s ... ' % (username, package_name, version, file_attrs['basename']))
        sys.stdout.flush()

        if remove_existing_file(aserver_api, args, username, package_name, version, file_attrs):
            return None
        try:
            upload_info = aserver_api.upload(username,
                                             package_name,
                                             version,
                                             file_attrs['basename'],
                                             fd, binstar_package_type,
                                             args.description,
                                             dependencies=file_attrs.get('dependencies'),
                                             attrs=file_attrs['attrs'],
                                             channels=args.labels,
                                             callback=upload_print_callback(args))
        except errors.Conflict:
            full_name = '%s/%s/%s/%s' % (username, package_name, version, file_attrs['basename'])
            logger.info('Distribution already exists. Please use the '
                     '-i/--interactive or --force options or `anaconda remove %s`' % full_name)
            raise

        logger.info("\n\nUpload(s) Complete\n")
        return [package_name, upload_info]


def get_convert_files(files):
    tmpdir = tempfile.mkdtemp()

    for filepath in files:
        logger.info('Running conda convert on %s', filepath)
        process = subprocess.Popen(
            ['conda-convert', '-p', 'all', filepath, '-o', tmpdir],
            stdout=subprocess.PIPE, stderr=subprocess.PIPE
        )
        stdout, stderr = process.communicate()

        if stderr:
            logger.warning('Couldn\'t generate platform packages for %s: %s', filepath, stderr)

    result = []
    for path, dirs, files in os.walk(tmpdir):
        for filename in files:
            result.append(os.path.join(path, filename))

    return result


def main(args):
    aserver_api = get_server_api(args.token, args.site)
    aserver_api.check_server()

    if args.user:
        username = args.user
    else:
        user = aserver_api.user()
        username = user['login']

    uploaded_packages = []
    uploaded_projects = []

    # Flatten file list because of 'windows_glob' function
    files = [f for fglob in args.files for f in fglob]

    if args.all:
        files += get_convert_files(files)

    for filename in files:
        if not exists(filename):
            message = 'file %s does not exist' % (filename)
            logger.error(message)
            raise errors.BinstarError(message)

        package_type = determine_package_type(filename, args)

        if package_type == 'project':
            uploaded_projects.append(upload_project(filename, args, username))
        else:
            if package_type == 'ipynb' and not args.mode == 'force':
                try:
                    nbformat.read(open(filename), nbformat.NO_CONVERT)
                except Exception as error:
                    logger.error("Invalid notebook file '%s': %s", filename, error)
                    logger.info("Use --force to upload the file anyways")
                    continue

            package_info = upload_package(
                filename,
                package_type=package_type,
                aserver_api=aserver_api,
                username=username,
                args=args)
            if package_info:
                uploaded_packages.append(package_info)

    for package, upload_info in uploaded_packages:
        package_url = upload_info.get('url', 'https://anaconda.org/%s/%s' % (username, package))
        logger.info("{} located at:\n{}\n".format(verbose_package_type(package_type), package_url))

    for project_name, url in uploaded_projects:
        logger.info("Project {} uploaded to {}.\n".format(project_name, url))


def windows_glob(item):
    if os.name == 'nt' and '*' in item:
        return glob(item)
    else:
        return [item]


def add_parser(subparsers):
    description = 'Upload packages to Anaconda Cloud'
    parser = subparsers.add_parser('upload',
                                   formatter_class=argparse.RawDescriptionHelpFormatter,
                                   help=description, description=description,
                                   epilog=__doc__)

    parser.add_argument('files', nargs='+', help='Distributions to upload', default=[], type=windows_glob)

    label_help = (
        '{deprecation}Add this file to a specific {label}. '
        'Warning: if the file {label}s do not include "main", '
        'the file will not show up in your user {label}')

    parser.add_argument('-c', '--channel', action='append', default=[], dest='labels',
                        help=label_help.format(deprecation='[DEPRECATED]\n', label='channel'),
                        metavar='CHANNELS')
    parser.add_argument('-l', '--label', action='append', dest='labels',
                        help=label_help.format(deprecation='', label='label'))
    parser.add_argument('--no-progress', help="Don't show upload progress", action='store_true')
    parser.add_argument('-u', '--user', help='User account or Organization, defaults to the current user')
    parser.add_argument('--all', help='Use conda convert to generate packages for all platforms and upload them',
                        action='store_true')

    mgroup = parser.add_argument_group('metadata options')
    mgroup.add_argument('-p', '--package', help='Defaults to the package name in the uploaded file')
    mgroup.add_argument('-v', '--version', help='Defaults to the package version in the uploaded file')
    mgroup.add_argument('-s', '--summary', help='Set the summary of the package')
    mgroup.add_argument('-t', '--package-type', help='Set the package type, defaults to autodetect')
    mgroup.add_argument('-d', '--description', help='description of the file(s)')
    mgroup.add_argument('--thumbnail', help='Notebook\'s thumbnail image')
    mgroup.add_argument('--private', help="Create the package with private access", action='store_true')

    register_group = parser.add_mutually_exclusive_group()
    register_group.add_argument("--no-register", dest="auto_register", action="store_false",
                        help='Don\'t create a new package namespace if it does not exist')
    register_group.add_argument("--register", dest="auto_register", action="store_true",
                        help='Create a new package namespace if it does not exist')
    parser.set_defaults(auto_register=bool(get_config().get('auto_register', True)))
    parser.add_argument('--build-id', help='Anaconda Cloud Build ID (internal only)')

    group = parser.add_mutually_exclusive_group()
    group.add_argument('-i', '--interactive', action='store_const', help='Run an interactive prompt if any packages are missing',
                        dest='mode', const='interactive')
    group.add_argument('-f', '--fail', help='Fail if a package or release does not exist (default)',
                                        action='store_const', dest='mode', const='fail')
    group.add_argument('--force', help='Force a package upload regardless of errors',
                                        action='store_const', dest='mode', const='force')

    parser.set_defaults(main=main)
