import os
import subprocess
import inflection
from datetime import datetime
from enum import Enum, auto
from cli.helpers.logger import *
from cli.helpers import FSHelper
from cli.helpers import sanitized_string
from cli.helpers import rendered_file_template
from cli.helpers.errors import DEFAULT_ERRORS
from cli.commands.generate.helpers import AdminHelper
from cli.commands.generate.helpers import FormHelper
from cli.commands.generate.helpers import ModelHelper
from cli.commands.generate.helpers import SerializerHelper
from cli.commands.generate.helpers import TestHelper
from cli.commands.generate.helpers import ViewSetHelper
from cli.decorators import watch_templates

now = datetime.now

BASE_DIR = os.path.dirname(os.path.abspath(__file__)).rsplit('/', 1)[0]

CURRENT_WORKING_DIRECTORY = '.'

PREVIOUS_WORKING_DIRECTORY = '..'

DEFAULT_APP_PACKAGES = {
    'admin',
    'fixtures',
    'forms',
    'middleware',
    'models',
    'serializers',
    'tasks',
    'templates',
    'templatetags',
    'views',
    'viewsets',
}

UNWANTED_FILES = {
    'apps.py',
    'admin.py',
    'models.py',
    'tests.py',
    'views.py',
    '__init__.py'
}

APP_TEMPLATES = {
    'api.py': 'api.tpl',
    'urls.py': 'urls.tpl',
    'apps.py': 'apps.tpl',
    'requires.py': 'requires.tpl',
    '__init__.py': 'init.tpl',
}

APP_PACKAGE_FILES = {
    '.gitignore': 'gitignore.tpl',
    'LICENSE.md': 'LICENSE.tpl',
    'MANIFEST.in': 'MANIFEST.tpl',
    'README.md': 'README.tpl',
    'setup.cfg': 'setup_cfg.tpl',
    'setup.py': 'setup.tpl',
}


class CustomAppType(Enum):
    def _generate_next_value_(self, start, count, last_values):
        return inflection.underscore(self)

    Authentication = 'auto()'
    ActiveRecord = auto()


@watch_templates(os.path.join(BASE_DIR, 'templates/app'))
class AppHelper(FSHelper):
    """
    Creator Helper

    Aids the creation of projects and apps.
    Class has access to file system through FSHelper.
    """

    def __init__(self, cwd, package=False, dry=False, force=False, default=False, verbose=False):
        self.package = package
        super(AppHelper, self).__init__(cwd, dry, force, default, verbose)

    def __custom_app(self, project, app, base, **kwargs):
        """
        :param project: name of django project
        :param app: name of app
        :param base: path to app directory
        :param kwargs:
        :return: None
        """
        paths = {
            'admin': f'{base}/admin',
            'forms': f'{base}/forms',
            'models': f'{base}/models',
            'serializers': f'{base}/serializers',
            'models_test': f'{base}/models/tests',
            'serializers_test': f'{base}/serializers/tests',
            'viewsets': f'{base}/viewsets'
        }

        t = kwargs.get('type')

        if t == CustomAppType.Authentication:
            AdminHelper(cwd=paths['admin']).create_auth_user()
            FormHelper(cwd=paths['forms']).create_auth_user()
            ModelHelper(cwd=paths['models']).create_auth_user()
            SerializerHelper(cwd=paths['serializers']).create_auth_user()
            ViewSetHelper(cwd=paths['viewsets']).create(model='user')
            TestHelper(cwd=paths['models_test']).create_auth_user(scope='model')
            TestHelper(cwd=paths['serializers_test']).create_auth_user(scope='serializer')

            # return to application directory
            self.change_directory(base)

        if t == CustomAppType.ActiveRecord:
            self.create_app(project=project, app=app, active_record=True)

    def create_app_package(self, package, project, app):
        """
        Create an app package and any associated sub-packages or modules

        :param package: name of app package
        :param project: name of django project
        :param app: name of app
        :return: None
        """

        package = sanitized_string(package)
        app = sanitized_string(app)

        # Create app package
        self.create_package(
            project=project,
            package=package,
            app=app
        )

        # Package customizations
        self.change_directory(package)

        if self.verbose:
            log_verbose(header=f'Customizations for "{package}":', message='')

        self.create_package(project=project, package='helpers', app=app)
        if package == 'admin':
            self.create_package(project=project, package='actions', app=app)
            self.create_package(project=project, package='inlines', app=app)
            self.create_package(project=project, package='permissions', app=app)
        if package == 'audit':
            filename = '__init__.py'
            index = self.TEMPLATE_FILES.index('audit.tpl')
            template = self.TEMPLATES_DIRECTORY[index]

            content = rendered_file_template(
                path=self.TEMPLATES_DIRECTORY,
                template=template,
                context={
                    'app': app,
                    'classname': inflection.camelize(app),
                    'package': package,
                    'project': project,
                }
            )

            self.create_file(
                path=os.getcwd(),
                filename=filename,
                content=content,
                force=True,
            )
        if package == 'models':
            self.create_package(project=project, package='managers', app=app)
            self.create_package(project=project, package='signals', app=app)
            self.create_package(project=project, package='tests', app=app)
            self.create_package(project=project, package='validators', app=app)
            self.create_package(project=project, package='permissions', app=app)
        if package == 'serializers':
            self.create_package(project=project, package='tests', app=app)
        if package == 'views':
            filename = 'urls.py'
            index = self.TEMPLATE_FILES.index('routes.tpl')
            template = self.TEMPLATE_FILES[index]

            content = rendered_file_template(
                path=self.TEMPLATES_DIRECTORY,
                template=template,
                context={
                    'app': app,
                    'classname': inflection.camelize(app),
                    'package': self.package,
                    'project': project,
                }
            )

            self.create_file(
                path=os.getcwd(),
                filename=filename,
                content=content,
            )
        if package == 'viewsets':
            self.create_package(project=project, package='permissions', app=app)
            filename = 'router.py'
            index = self.TEMPLATE_FILES.index('router.tpl')
            template = self.TEMPLATE_FILES[index]

            content = rendered_file_template(
                path=self.TEMPLATES_DIRECTORY,
                template=template,
                context={
                    'app': app,
                    'package': self.package,
                    'project': project,
                }
            )

            self.create_file(
                path=os.getcwd(),
                filename=filename,
                content=content,
            )

        self.change_directory(PREVIOUS_WORKING_DIRECTORY)

        if self.verbose:
            log_verbose(header=f'Finished customizing "{package}" package.', message='')

    def create_app(self, app, auth=False, project=None, **kwargs):
        """
        Creates a django app and customizes its directory

        :param project: name of django project
        :param app: name of django app
        :param auth: app is used for authentication
        :param kwargs:
        :return: app directory. None if an error occurred.
        """

        project = '' if not project else sanitized_string(project)
        app = sanitized_string(app)

        if self.is_dry:
            if self.verbose:
                log_standard(f'Skipping creation of app {app} with --dry enabled')
            return False

        if self.verbose:
            log_verbose(header=f'Creating app {app}:', message=f'\tLocation: {os.getcwd()}')

        # Create django application
        try:
            if self.package:
                self.make_directory(app)
                self.change_directory(app)

            subprocess.check_output([
                'django-admin',
                'startapp',
                app
            ])
        except subprocess.CalledProcessError:
            log_error(DEFAULT_ERRORS['app'].format(app))
            return False

        # Add license, setup.py, and other files
        if self.package:
            try:
                for filename, template in APP_PACKAGE_FILES.items():
                    content = rendered_file_template(
                        path=self.TEMPLATES_DIRECTORY,
                        template=template,
                        context={
                            'app': app,
                            'app_namespace': app,
                            'classname': inflection.camelize(app),
                            'package': self.package,
                            'project': project,
                            'year': datetime.today().year,
                            **kwargs,
                        }
                    )

                    self.create_file(
                        path=os.getcwd(),
                        filename=filename,
                        content=content
                    )
            except OSError:
                pass

        # Perform app customizations
        self.change_directory(app)
        directory = os.getcwd()
        FSHelper.config['apps'].append({
            'app': app,
            'path': directory,
            'created_at': now().strftime("%d/%m/%Y %H:%M:%S"),
            'updated_at': now().strftime("%d/%m/%Y %H:%M:%S"),
        })

        if self.verbose:
            log_verbose(header='Customizations:', message=f'\t{app}')

        # Remove unwanted files
        try:
            for unwanted in UNWANTED_FILES:
                os.remove(unwanted)
        except FileNotFoundError:
            pass

        if self.verbose:
            log_verbose(header="Removed unwanted modules:", message=f'\t{UNWANTED_FILES}')

        # Parse templates for apps.py and urls.py
        try:
            for filename, template in APP_TEMPLATES.items():
                content = rendered_file_template(
                    path=self.TEMPLATES_DIRECTORY,
                    template=template,
                    context={
                        'app': app,
                        'classname': inflection.camelize(app),
                        'package': self.package,
                        'project': project,
                    }
                )

                self.create_file(
                    path=os.getcwd(),
                    filename=filename,
                    content=content
                )
        except OSError:
            pass

        # Create app-specific packages and module
        if kwargs.get('api'):
            DEFAULT_APP_PACKAGES.remove('admin')
            DEFAULT_APP_PACKAGES.remove('forms')
            DEFAULT_APP_PACKAGES.remove('templates')
            DEFAULT_APP_PACKAGES.remove('views')

        if kwargs.get('audit_record'):
            DEFAULT_APP_PACKAGES.add('audit')

        for package in DEFAULT_APP_PACKAGES:
            try:
                self.create_app_package(
                    package=package,
                    project=project,
                    app=app
                )
            except OSError as e:
                log_error(f"Cannot create package: {package}\n{e}")

        if auth:
            if self.verbose:
                log_verbose(header=f'Adding custom user model in {app}', message='')

            self.__custom_app(
                project=project,
                app=app,
                base=os.getcwd(),
                type=CustomAppType.Authentication
            )

        self.change_directory(PREVIOUS_WORKING_DIRECTORY)
        log_success(DEFAULT_APP_CREATION_LOG.format(app))

        return directory
