# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals

import argparse
import locale
import os.path
import sys
import warnings
from distutils.version import LooseVersion

import pytz
import six

from .. import compat, utils
from ..utils import less_than_version, supported_versions
from . import data, ini
from .internal import DbAction, validate_project


def parse(args):
    """
    Define the available arguments
    """
    from tzlocal import get_localzone

    try:
        timezone = get_localzone()
        if isinstance(timezone, pytz.BaseTzInfo):
            timezone = timezone.zone
    except Exception:  # pragma: no cover
        timezone = 'UTC'
    if timezone == 'local':
        timezone = 'UTC'
    parser = argparse.ArgumentParser(description="""Bootstrap a django CMS project.
Major usage modes:

- wizard: djangocms -w -p /path/whatever project_name: ask for all the options through a
          CLI wizard.

- batch: djangocms project_name: runs with the default values plus any
         additional option provided (see below) with no question asked.

- config file: djangocms_installer --config-file /path/to/config.ini project_name: reads values
               from an ini-style config file.

Check https://djangocms-installer.readthedocs.io/en/latest/usage.html for detailed usage
information.
""", formatter_class=argparse.RawTextHelpFormatter)
    parser.add_argument('--config-file', dest='config_file', action='store',
                        default=None,
                        help='Configuration file for djangocms_installer')
    parser.add_argument('--config-dump', dest='config_dump', action='store',
                        default=None,
                        help='Dump configuration file with current args')
    parser.add_argument('--db', '-d', dest='db', action=DbAction,
                        default='sqlite://localhost/project.db',
                        help='Database configuration (in URL format). '
                             'Example: sqlite://localhost/project.db')
    parser.add_argument('--i18n', '-i', dest='i18n', action='store',
                        choices=('yes', 'no'),
                        default='yes', help='Activate Django I18N / L10N setting; this is '
                                            'automatically activated if more than '
                                            'language is provided')
    parser.add_argument('--use-tz', '-z', dest='use_timezone', action='store',
                        choices=('yes', 'no'),
                        default='yes', help='Activate Django timezone support')
    parser.add_argument('--timezone', '-t', dest='timezone',
                        required=False, default=timezone,
                        action='store', help='Optional default time zone. Example: Europe/Rome')
    parser.add_argument('--reversion', '-e', dest='reversion', action='store',
                        choices=('yes', 'no'),
                        default='yes', help='Install and configure reversion support '
                                            '(only for django CMS 3.2 and 3.3)')
    parser.add_argument('--permissions', dest='permissions', action='store',
                        choices=('yes', 'no'),
                        default='no', help='Activate CMS permission management')
    parser.add_argument('--pip-options', help='pass custom pip options', default='')
    parser.add_argument('--languages', '-l', dest='languages', action='append',
                        help='Languages to enable. Option can be provided multiple times, or as a '
                             'comma separated list. Only language codes supported by Django can '
                             'be used here. Example: en, fr-FR, it-IT')
    parser.add_argument('--django-version', dest='django_version', action='store',
                        choices=data.DJANGO_SUPPORTED,
                        default=data.DJANGO_DEFAULT, help='Django version')
    parser.add_argument('--cms-version', '-v', dest='cms_version', action='store',
                        choices=data.DJANGOCMS_SUPPORTED,
                        default=data.DJANGOCMS_DEFAULT, help='django CMS version')
    parser.add_argument('--parent-dir', '-p', dest='project_directory',
                        default='',
                        action='store', help='Optional project parent directory')
    parser.add_argument('--bootstrap', dest='bootstrap', action='store',
                        choices=('yes', 'no'),
                        default='no', help='Use Bootstrap 4 Theme')
    parser.add_argument('--templates', dest='templates', action='store',
                        default='no', help='Use custom template set')
    parser.add_argument('--starting-page', dest='starting_page', action='store',
                        choices=('yes', 'no'),
                        default='no', help='Load a starting page with examples after installation '
                                           '(english language only). Choose "no" if you use a '
                                           'custom template set.')
    parser.add_argument(dest='project_name', action='store',
                        help='Name of the project to be created')

    # Command that lists the supported plugins in verbose description
    parser.add_argument('--list-plugins', '-P', dest='plugins', action='store_true',
                        help='List plugins that\'s going to be installed and configured')

    # Command that lists the supported plugins in verbose description
    parser.add_argument('--dump-requirements', '-R', dest='dump_reqs', action='store_true',
                        help='It dumps the requirements that would be installed according to '
                             'parameters given. Together with --requirements argument is useful '
                             'for customizing the virtualenv')

    # Advanced options. These have a predefined default and are not asked
    # by config wizard.
    parser.add_argument('--no-input', '-q', dest='noinput', action='store_true',
                        default=True, help='Don\'t run the configuration wizard, just use the '
                                           'provided values')
    parser.add_argument('--wizard', '-w', dest='wizard', action='store_true',
                        default=False, help='Run the configuration wizard')
    parser.add_argument('--verbose', dest='verbose', action='store_true',
                        default=False,
                        help='Be more verbose and don\'t swallow subcommands output')
    parser.add_argument('--filer', '-f', dest='filer', action='store_true',
                        default=True, help='Install and configure django-filer plugins '
                                           '- Always enabled')
    parser.add_argument('--requirements', '-r', dest='requirements_file', action='store',
                        default=None, help='Externally defined requirements file')
    parser.add_argument('--no-deps', '-n', dest='no_deps', action='store_true',
                        default=False, help='Don\'t install package dependencies')
    parser.add_argument('--no-plugins', dest='no_plugins', action='store_true',
                        default=False, help='Don\'t install plugins')
    parser.add_argument('--no-db-driver', dest='no_db_driver', action='store_true',
                        default=False, help='Don\'t install database package')
    parser.add_argument('--no-sync', '-m', dest='no_sync', action='store_true',
                        default=False, help='Don\'t run syncdb / migrate after bootstrapping')
    parser.add_argument('--no-user', '-u', dest='no_user', action='store_true',
                        default=False, help='Don\'t create the admin user')
    parser.add_argument('--template', dest='template', action='store',
                        default=None, help='The path or URL to load the django project '
                                           'template from.')
    parser.add_argument('--extra-settings', dest='extra_settings', action='store',
                        default=None, help='The path to an file that contains extra settings.')
    parser.add_argument('--skip-empty-check', '-s', dest='skip_project_dir_check',
                        action='store_true',
                        default=False, help='Skip the check if project dir is empty.')
    parser.add_argument('--delete-project-dir', '-c', dest='delete_project_dir',
                        action='store_true',
                        default=False, help='Delete project directory on creation failure.')
    parser.add_argument('--utc', dest='utc',
                        action='store_true',
                        default=False, help='Use UTC timezone.')

    if '--utc' in args:
        for action in parser._positionals._actions:
            if action.dest == 'timezone':
                action.default = 'UTC'

    # If config_args then pretend that config args came from the stdin and run parser again.
    config_args = ini.parse_config_file(parser, args)
    args = parser.parse_args(config_args + args)
    if not args.wizard:
        args.noinput = True
    else:
        args.noinput = False

    if not args.project_directory:
        args.project_directory = args.project_name
    args.project_directory = os.path.abspath(args.project_directory)

    # First of all, check if the project name is valid
    if not validate_project(args.project_name):
        sys.stderr.write(
            'Project name "{0}" is not valid or it\'s already defined. '
            'Please use only numbers, letters and underscores.\n'.format(args.project_name)
        )
        sys.exit(3)

    # Checking the given path
    setattr(args, 'project_path', os.path.join(args.project_directory, args.project_name).strip())
    if not args.skip_project_dir_check:
        if (os.path.exists(args.project_directory) and
                [path for path in os.listdir(args.project_directory) if not path.startswith('.')]):
            sys.stderr.write(
                'Path "{0}" already exists and is not empty, please choose a different one\n'
                'If you want to use this path anyway use the -s flag to skip this check.\n'
                ''.format(args.project_directory)
            )
            sys.exit(4)

    if os.path.exists(args.project_path):
        sys.stderr.write(
            'Path "{0}" already exists, please choose a different one\n'.format(args.project_path)
        )
        sys.exit(4)

    if args.config_dump and os.path.isfile(args.config_dump):
        sys.stdout.write(
            'Cannot dump because given configuration file "{0}" exists.\n'.format(args.config_dump)
        )
        sys.exit(8)

    args = _manage_args(parser,  args)

    # what do we want here?!
    # * if languages are given as multiple arguments, let's use it as is
    # * if no languages are given, use a default and stop handling it further
    # * if languages are given as a comma-separated list, split it and use the
    #   resulting list.

    if not args.languages:
        try:
            args.languages = [locale.getdefaultlocale()[0].split('_')[0]]
        except Exception:  # pragma: no cover
            args.languages = ['en']
    elif isinstance(args.languages, six.string_types):
        args.languages = args.languages.split(',')
    elif len(args.languages) == 1 and isinstance(args.languages[0], six.string_types):
        args.languages = args.languages[0].split(',')

    args.languages = [lang.strip().lower() for lang in args.languages]
    if len(args.languages) > 1:
        args.i18n = 'yes'
    args.filer = True

    # Convert version to numeric format for easier checking
    try:
        django_version, cms_version = supported_versions(args.django_version, args.cms_version)
        cms_package = data.PACKAGE_MATRIX.get(
            cms_version, data.PACKAGE_MATRIX[data.DJANGOCMS_LTS]
        )
    except RuntimeError as e:  # pragma: no cover
        sys.stderr.write(compat.unicode(e))
        sys.exit(6)

    if django_version is None:  # pragma: no cover
        sys.stderr.write(
            'Please provide a Django supported version: {0}. Only Major.Minor '
            'version selector is accepted\n'.format(', '.join(data.DJANGO_SUPPORTED))
        )
        sys.exit(6)
    if cms_version is None:  # pragma: no cover
        sys.stderr.write(
            'Please provide a django CMS supported version: {0}. Only Major.Minor '
            'version selector is accepted\n'.format(', '.join(data.DJANGOCMS_SUPPORTED))
        )
        sys.exit(6)

    default_settings = '{}.settings'.format(args.project_name)
    env_settings = os.environ.get('DJANGO_SETTINGS_MODULE', default_settings)
    if env_settings != default_settings:
        sys.stderr.write(
            '`DJANGO_SETTINGS_MODULE` is currently set to \'{0}\' which is not compatible with '
            'djangocms installer.\nPlease unset `DJANGO_SETTINGS_MODULE` and re-run the installer '
            '\n'.format(env_settings)
        )
        sys.exit(10)

    if not getattr(args, 'requirements_file'):
        requirements = []

        # django CMS version check
        if args.cms_version == 'develop':
            requirements.append(cms_package)
            warnings.warn(data.VERSION_WARNING.format('develop', 'django CMS'))
        elif args.cms_version == 'rc':  # pragma: no cover
            requirements.append(cms_package)
        elif args.cms_version == 'beta':  # pragma: no cover
            requirements.append(cms_package)
            warnings.warn(data.VERSION_WARNING.format('beta', 'django CMS'))
        else:
            requirements.append(cms_package)

        if args.cms_version in ('rc', 'develop'):
            requirements.extend(data.REQUIREMENTS['cms-master'])
        elif LooseVersion(cms_version) >= LooseVersion('3.7'):
            requirements.extend(data.REQUIREMENTS['cms-3.7'])
        elif LooseVersion(cms_version) >= LooseVersion('3.6'):
            requirements.extend(data.REQUIREMENTS['cms-3.6'])

        if not args.no_db_driver:
            if args.db_driver == 'psycopg2' and django_version in ('1.11', '2.0', '2.1'):
                requirements.append('psycopg2<2.8')
            else:
                requirements.append(args.db_driver)
        if not args.no_plugins:
            if args.cms_version in ('rc', 'develop'):
                requirements.extend(data.REQUIREMENTS['plugins-master'])
            elif LooseVersion(cms_version) >= LooseVersion('3.7'):
                requirements.extend(data.REQUIREMENTS['plugins-3.7'])
            elif LooseVersion(cms_version) >= LooseVersion('3.6'):
                requirements.extend(data.REQUIREMENTS['plugins-3.6'])
            requirements.extend(data.REQUIREMENTS['filer'])

        # Django version check
        if args.django_version == 'develop':  # pragma: no cover
            requirements.append(data.DJANGO_DEVELOP)
            warnings.warn(data.VERSION_WARNING.format('develop', 'Django'))
        elif args.django_version == 'beta':  # pragma: no cover
            requirements.append(data.DJANGO_BETA)
            warnings.warn(data.VERSION_WARNING.format('beta', 'Django'))
        else:
            requirements.append('Django<{0}'.format(less_than_version(django_version)))

        if django_version == '2.2':
            requirements.extend(data.REQUIREMENTS['django-2.2'])
        elif django_version == '2.1':
            requirements.extend(data.REQUIREMENTS['django-2.1'])
        elif django_version == '2.0':
            requirements.extend(data.REQUIREMENTS['django-2.0'])
        elif django_version == '1.11':
            requirements.extend(data.REQUIREMENTS['django-1.11'])

        requirements.extend(data.REQUIREMENTS['default'])

        setattr(args, 'requirements', '\n'.join(requirements).strip())

    # Convenient shortcuts
    setattr(args, 'cms_version', cms_version)
    setattr(args, 'django_version', django_version)
    setattr(args, 'settings_path',
            os.path.join(args.project_directory, args.project_name, 'settings.py').strip())
    setattr(args, 'urlconf_path',
            os.path.join(args.project_directory, args.project_name, 'urls.py').strip())

    if args.config_dump:
        ini.dump_config_file(args.config_dump, args, parser)

    return args


def get_settings():
    module = __import__(str('djangocms_installer.config'), globals(), locals(), [str('settings')])
    return module.settings


def write_default(config):
    pass


def show_plugins():
    """
    Shows a descriptive text about supported plugins
    """
    sys.stdout.write(compat.unicode(data.PLUGIN_LIST_TEXT))


def show_requirements(args):
    """
    Prints the list of requirements according to the arguments provided
    """
    sys.stdout.write(compat.unicode(args.requirements))


def _manage_args(parser,  args):
    """
    Checks and validate provided input
    """
    for item in data.CONFIGURABLE_OPTIONS:
        action = parser._option_string_actions[item]
        choices = default = ''
        input_value = getattr(args, action.dest)
        new_val = None
        # cannot count this until we find a way to test input
        if not args.noinput:  # pragma: no cover
            if action.choices:
                choices = ' (choices: {0})'.format(', '.join(action.choices))
            if input_value:
                if type(input_value) == list:
                    default = ' [default {0}]'.format(', '.join(input_value))
                else:
                    default = ' [default {0}]'.format(input_value)

            while not new_val:
                prompt = '{0}{1}{2}: '.format(action.help, choices, default)
                if action.choices in ('yes', 'no'):
                    new_val = utils.query_yes_no(prompt)
                else:
                    new_val = compat.input(prompt)
                new_val = compat.clean(new_val)
                if not new_val and input_value:
                    new_val = input_value
                if new_val and action.dest == 'templates':
                    if new_val != 'no' and not os.path.isdir(new_val):
                        sys.stdout.write('Given directory does not exists, retry\n')
                        new_val = False
                if new_val and action.dest == 'db':
                    action(parser, args, new_val, action.option_strings)
                    new_val = getattr(args, action.dest)
        else:
            if not input_value and action.required:
                raise ValueError(
                    'Option {0} is required when in no-input mode'.format(action.dest)
                )
            new_val = input_value
            if action.dest == 'db':
                action(parser, args, new_val, action.option_strings)
                new_val = getattr(args, action.dest)
        if action.dest == 'templates' and (new_val == 'no' or not os.path.isdir(new_val)):
            new_val = False
        if action.dest in ('bootstrap', 'starting_page'):
            new_val = (new_val == 'yes')
        setattr(args, action.dest, new_val)
    return args
