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

import glob
import os
import re
import shutil
import subprocess
import sys
import textwrap
from copy import copy, deepcopy
from distutils.version import LooseVersion

from six import iteritems

from ..config import data, get_settings
from ..utils import chdir, format_val

try:
    from shlex import quote as shlex_quote
except ImportError:
    from pipes import quote as shlex_quote


def create_project(config_data):
    """
    Call django-admin to create the project structure

    :param config_data: configuration data
    """
    env = deepcopy(dict(os.environ))
    env[str('DJANGO_SETTINGS_MODULE')] = str('{0}.settings'.format(config_data.project_name))
    env[str('PYTHONPATH')] = str(os.pathsep.join(map(shlex_quote, sys.path)))
    kwargs = {}
    args = []
    if config_data.template:
        kwargs['template'] = config_data.template
    args.append(config_data.project_name)
    if config_data.project_directory:
        args.append(config_data.project_directory)
        if not os.path.exists(config_data.project_directory):
            os.makedirs(config_data.project_directory)
    base_cmd = 'django-admin.py'
    start_cmds = [os.path.join(os.path.dirname(sys.executable), base_cmd)]
    start_cmd_pnodes = ['Scripts']
    start_cmds.extend([
        os.path.join(os.path.dirname(sys.executable), pnode, base_cmd)
        for pnode in start_cmd_pnodes
    ])
    start_cmd = [base_cmd]
    for p in start_cmds:
        if os.path.exists(p):
            start_cmd = [sys.executable, p]
            break
    cmd_args = start_cmd + ['startproject'] + args
    if config_data.verbose:
        sys.stdout.write('Project creation command: {0}\n'.format(' '.join(cmd_args)))
    try:
        output = subprocess.check_output(cmd_args, stderr=subprocess.STDOUT)
        sys.stdout.write(output.decode('utf-8'))
    except subprocess.CalledProcessError as e:  # pragma: no cover
        raise RuntimeError(e.output.decode('utf-8'))


def copy_files(config_data):
    """
    It's a little rude actually: it just overwrites the django-generated urls.py
    with a custom version and put other files in the project directory.

    :param config_data: configuration data
    """
    if config_data.i18n == 'yes':
        urlconf_path = os.path.join(os.path.dirname(__file__), '../config/urls_i18n.py')
    else:
        urlconf_path = os.path.join(os.path.dirname(__file__), '../config/urls_noi18n.py')
    share_path = os.path.join(os.path.dirname(__file__), '../share')
    template_path = os.path.join(share_path, 'templates')

    media_project = os.path.join(config_data.project_directory, 'media')
    static_main = os.path.join(config_data.project_path, 'static')
    static_project = os.path.join(config_data.project_directory, 'static')
    template_target = os.path.join(config_data.project_path, 'templates')
    if config_data.templates and os.path.isdir(config_data.templates):
        template_path = config_data.templates
    elif config_data.bootstrap:
        template_path = os.path.join(template_path, 'bootstrap')
    else:
        template_path = os.path.join(template_path, 'basic')

    shutil.copy(urlconf_path, config_data.urlconf_path)
    if media_project:
        os.makedirs(media_project)
    if static_main:
        os.makedirs(static_main)
    if not os.path.exists(static_project):
        os.makedirs(static_project)
    if not os.path.exists(template_target):
        os.makedirs(template_target)
    for filename in glob.glob(os.path.join(template_path, '*.html')):
        if os.path.isfile(filename):
            shutil.copy(filename, template_target)

    if config_data.noinput and not config_data.no_user:
        script_path = os.path.join(share_path, 'create_user.py')
        if os.path.isfile(script_path):
            shutil.copy(script_path, os.path.join(config_data.project_path, '..'))

    if config_data.starting_page:
        for filename in glob.glob(os.path.join(share_path, 'starting_page.*')):
            if os.path.isfile(filename):
                shutil.copy(filename, os.path.join(config_data.project_path, '..'))


def patch_settings(config_data):
    """
    Modify the settings file created by Django injecting the django CMS
    configuration

    :param config_data: configuration data
    """
    import django
    current_django_version = LooseVersion(django.__version__)
    declared_django_version = LooseVersion(config_data.django_version)

    if not os.path.exists(config_data.settings_path):
        sys.stderr.write(
            'Error while creating target project, '
            'please check the given configuration: {0}\n'.format(config_data.settings_path)
        )
        return sys.exit(5)

    if current_django_version.version[:2] != declared_django_version.version[:2]:
        sys.stderr.write(
            'Currently installed Django version {} differs from the declared {}. '
            'Please check the given `--django-version` installer argument, your virtualenv '
            'configuration and any package forcing a different Django version'
            '\n'.format(
                current_django_version, declared_django_version
            )
        )
        return sys.exit(9)

    overridden_settings = (
        'MIDDLEWARE_CLASSES', 'MIDDLEWARE', 'INSTALLED_APPS', 'TEMPLATE_LOADERS',
        'TEMPLATE_CONTEXT_PROCESSORS', 'TEMPLATE_DIRS', 'LANGUAGES'
    )
    extra_settings = ''

    with open(config_data.settings_path, 'r') as fd_original:
        original = fd_original.read()

    # extra settings reading
    if config_data.extra_settings and os.path.exists(config_data.extra_settings):
        with open(config_data.extra_settings, 'r') as fd_extra:
            extra_settings = fd_extra.read()

    original = original.replace('# -*- coding: utf-8 -*-\n', '')

    DATA_DIR = 'DATA_DIR = os.path.dirname(os.path.dirname(__file__))\n'
    STATICFILES_DIR = 'os.path.join(BASE_DIR, \'{0}\', \'static\'),'.format(
        config_data.project_name
    )

    original = data.DEFAULT_PROJECT_HEADER + DATA_DIR + original
    original += 'MEDIA_URL = \'/media/\'\n'
    original += 'MEDIA_ROOT = os.path.join(DATA_DIR, \'media\')\n'
    original += 'STATIC_ROOT = os.path.join(DATA_DIR, \'static\')\n'
    original += """
STATICFILES_DIRS = (
    {0}
)
""".format(STATICFILES_DIR)
    original = original.replace('# -*- coding: utf-8 -*-\n', '')

    # I18N
    if config_data.i18n == 'no':
        original = original.replace('I18N = True', 'I18N = False')
        original = original.replace('L10N = True', 'L10N = False')

    # TZ
    if config_data.use_timezone == 'no':
        original = original.replace('USE_TZ = True', 'USE_TZ = False')

    if config_data.languages:
        original = original.replace(
            'LANGUAGE_CODE = \'en-us\'', 'LANGUAGE_CODE = \'{0}\''.format(config_data.languages[0])
        )
    if config_data.timezone:
        original = original.replace(
            'TIME_ZONE = \'UTC\'', 'TIME_ZONE = \'{0}\''.format(config_data.timezone)
        )

    for item in overridden_settings:
        item_re = re.compile(r'{0} = [^\]]+\]'.format(item), re.DOTALL | re.MULTILINE)
        original = item_re.sub('', original)
    # TEMPLATES is special, so custom regexp needed
    if declared_django_version >= LooseVersion('2.0'):
        item_re = re.compile(r'TEMPLATES = .+\},\n\s+\},\n]$', re.DOTALL | re.MULTILINE)
    else:
        item_re = re.compile(r'TEMPLATES = .+\]$', re.DOTALL | re.MULTILINE)
    original = item_re.sub('', original)
    # DATABASES is a dictionary, so different regexp needed
    item_re = re.compile(r'DATABASES = [^\}]+\}[^\}]+\}', re.DOTALL | re.MULTILINE)
    original = item_re.sub('', original)
    if original.find('SITE_ID') == -1:
        original += 'SITE_ID = 1\n\n'

    original += _build_settings(config_data)
    # Append extra settings at the end of the file
    original += ('\n' + extra_settings)

    with open(config_data.settings_path, 'w') as fd_dest:
        fd_dest.write(original)


def _build_settings(config_data):
    """
    Build the django CMS settings dictionary

    :param config_data: configuration data
    """
    spacer = '    '
    text = []
    vars = get_settings()

    vars.MIDDLEWARE_CLASSES.insert(0, vars.APPHOOK_RELOAD_MIDDLEWARE_CLASS)

    processors = vars.TEMPLATE_CONTEXT_PROCESSORS + vars.TEMPLATE_CONTEXT_PROCESSORS_3
    text.append(data.TEMPLATES_1_8.format(
        loaders=(',\n' + spacer * 4).join([
            "'{0}'".format(var) for var in vars.TEMPLATE_LOADERS
            if (
                LooseVersion(config_data.django_version) < LooseVersion('2.0') or
                'eggs' not in var
            )
        ]),
        processors=(',\n' + spacer * 4).join(["'{0}'".format(var) for var in processors]),
        dirs="os.path.join(BASE_DIR, '{0}', 'templates'),".format(config_data.project_name)
    ))

    text.append('MIDDLEWARE = [\n{0}{1}\n]'.format(
        spacer, (',\n' + spacer).join(['\'{0}\''.format(var)
                                       for var in vars.MIDDLEWARE_CLASSES])
    ))

    apps = list(vars.INSTALLED_APPS)
    apps = list(vars.CMS_3_HEAD) + apps
    apps.extend(vars.TREEBEARD_APPS)
    apps.extend(vars.CMS_3_APPLICATIONS)

    if not config_data.no_plugins:
        apps.extend(vars.FILER_PLUGINS_3)

    text.append('INSTALLED_APPS = [\n{0}{1}\n]'.format(
        spacer, (',\n' + spacer).join(['\'{0}\''.format(var) for var in apps] +
                                      ['\'{0}\''.format(config_data.project_name)])
    ))

    text.append('LANGUAGES = (\n{0}{1}\n{0}{2}\n)'.format(
        spacer, '## Customize this',
        ('\n' + spacer).join(['(\'{0}\', gettext(\'{0}\')),'.format(item) for item in config_data.languages])  # NOQA
    ))

    cms_langs = deepcopy(vars.CMS_LANGUAGES)
    for lang in config_data.languages:
        lang_dict = {'code': lang, 'name': lang}
        lang_dict.update(copy(cms_langs['default']))
        cms_langs[1].append(lang_dict)
    cms_text = ['CMS_LANGUAGES = {']
    cms_text.append('{0}{1}'.format(spacer, '## Customize this'))
    for key, value in iteritems(cms_langs):
        if key == 'default':
            cms_text.append('{0}\'{1}\': {{'.format(spacer, key))
            for config_name, config_value in iteritems(value):
                cms_text.append('{0}\'{1}\': {2},'.format(spacer * 2, config_name, config_value))
            cms_text.append('{0}}},'.format(spacer))
        else:
            cms_text.append('{0}{1}: ['.format(spacer, key))
            for lang in value:
                cms_text.append('{0}{{'.format(spacer * 2))
                for config_name, config_value in iteritems(lang):
                    if config_name == 'code':
                        cms_text.append('{0}\'{1}\': \'{2}\','.format(spacer * 3, config_name, config_value))  # NOQA
                    elif config_name == 'name':
                        cms_text.append('{0}\'{1}\': gettext(\'{2}\'),'.format(spacer * 3, config_name, config_value))  # NOQA
                    else:
                        cms_text.append('{0}\'{1}\': {2},'.format(
                            spacer * 3, config_name, config_value
                        ))
                cms_text.append('{0}}},'.format(spacer * 2))
            cms_text.append('{0}],'.format(spacer))
    cms_text.append('}')

    text.append('\n'.join(cms_text))

    if config_data.bootstrap:
        cms_templates = 'CMS_TEMPLATES_BOOTSTRAP'
    else:
        cms_templates = 'CMS_TEMPLATES'

    text.append('CMS_TEMPLATES = (\n{0}{1}\n{0}{2}\n)'.format(
        spacer, '## Customize this',
        (',\n' + spacer).join(
            ['(\'{0}\', \'{1}\')'.format(*item) for item in getattr(vars, cms_templates)]
        )
    ))

    text.append('CMS_PERMISSION = {0}'.format(vars.CMS_PERMISSION))
    text.append('CMS_PLACEHOLDER_CONF = {0}'.format(vars.CMS_PLACEHOLDER_CONF))

    database = ['\'{0}\': {1}'.format(key, format_val(val)) for key, val in sorted(config_data.db_parsed.items(), key=lambda x: x[0])]  # NOQA
    text.append(textwrap.dedent("""
        DATABASES = {{
            'default': {{
                {0}
            }}
        }}""").strip().format((',\n' + spacer * 2).join(database)))  # NOQA

    if config_data.filer:
        text.append('THUMBNAIL_PROCESSORS = (\n{0}{1}\n)'.format(
            spacer, (',\n' + spacer).join(
                ['\'{0}\''.format(var) for var in vars.THUMBNAIL_PROCESSORS]
            )
        ))
    return '\n\n'.join(text)


def setup_database(config_data):
    """
    Run the migrate command to create the database schema

    :param config_data: configuration data
    """
    with chdir(config_data.project_directory):
        env = deepcopy(dict(os.environ))
        env[str('DJANGO_SETTINGS_MODULE')] = str('{0}.settings'.format(config_data.project_name))
        env[str('PYTHONPATH')] = str(os.pathsep.join(map(shlex_quote, sys.path)))
        commands = []

        commands.append(
            [sys.executable, '-W', 'ignore', 'manage.py', 'migrate'],
        )

        if config_data.verbose:
            sys.stdout.write(
                'Database setup commands: {0}\n'.format(
                    ', '.join([' '.join(cmd) for cmd in commands])
                )
            )
        for command in commands:
            try:
                output = subprocess.check_output(
                    command, env=env, stderr=subprocess.STDOUT
                )
                sys.stdout.write(output.decode('utf-8'))
            except subprocess.CalledProcessError as e:  # pragma: no cover
                if config_data.verbose:
                    sys.stdout.write(e.output.decode('utf-8'))
                raise

        if not config_data.no_user:
            sys.stdout.write('Creating admin user\n')
            if config_data.noinput:
                create_user(config_data)
            else:
                subprocess.check_call(' '.join(
                    [sys.executable, '-W', 'ignore', 'manage.py', 'createsuperuser']
                ), shell=True, stderr=subprocess.STDOUT)


def create_user(config_data):
    """
    Create admin user without user input

    :param config_data: configuration data
    """
    with chdir(os.path.abspath(config_data.project_directory)):
        env = deepcopy(dict(os.environ))
        env[str('DJANGO_SETTINGS_MODULE')] = str('{0}.settings'.format(config_data.project_name))
        env[str('PYTHONPATH')] = str(os.pathsep.join(map(shlex_quote, sys.path)))
        subprocess.check_call(
            [sys.executable, 'create_user.py'], env=env, stderr=subprocess.STDOUT
        )
        for ext in ['py', 'pyc']:
            try:
                os.remove('create_user.{0}'.format(ext))
            except OSError:
                pass


def load_starting_page(config_data):
    """
    Load starting page into the CMS

    :param config_data: configuration data
    """
    with chdir(os.path.abspath(config_data.project_directory)):
        env = deepcopy(dict(os.environ))
        env[str('DJANGO_SETTINGS_MODULE')] = str('{0}.settings'.format(config_data.project_name))
        env[str('PYTHONPATH')] = str(os.pathsep.join(map(shlex_quote, sys.path)))
        subprocess.check_call(
            [sys.executable, 'starting_page.py'], env=env, stderr=subprocess.STDOUT
        )
        for ext in ['py', 'pyc', 'json']:
            try:
                os.remove('starting_page.{0}'.format(ext))
            except OSError:
                pass
