# -*- coding: utf-8 -*-
import pkg_resources
from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from pyramid.config import Configurator
from pyramid.events import BeforeRender
from pyramid.threadlocal import get_current_registry
from pyramid.util import DottedNameResolver
from pyramid_beaker import session_factory_from_settings
from six import binary_type, string_types
from sqlalchemy import MetaData
from sqlalchemy import engine_from_config
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker
from zope.sqlalchemy import ZopeTransactionExtension

from kotti.sqla import Base as KottiBase

metadata = MetaData()
DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
Base = declarative_base(cls=KottiBase)
Base.metadata = metadata
Base.query = DBSession.query_property()
TRUE_VALUES = ('1', 'y', 'yes', 't', 'true')
FALSE_VALUES = ('0', 'n', 'no', 'f', 'false', 'none')


def authtkt_factory(**settings):
    from kotti.security import list_groups_callback
    kwargs = dict(
        secret=settings['kotti.secret2'],
        hashalg='sha512',
        callback=list_groups_callback,
        )
    try:
        return AuthTktAuthenticationPolicy(**kwargs)
    except TypeError:
        # BBB with Pyramid < 1.4
        kwargs.pop('hashalg')
        return AuthTktAuthenticationPolicy(**kwargs)


def acl_factory(**settings):
    return ACLAuthorizationPolicy()


def beaker_session_factory(**settings):
    return session_factory_from_settings(settings)


def none_factory(**kwargs):  # pragma: no cover
    return None

# All of these can be set by passing them in the Paste Deploy settings:
conf_defaults = {
    'kotti.alembic_dirs': 'kotti:alembic',
    'kotti.asset_overrides': '',
    'kotti.authn_policy_factory': 'kotti.authtkt_factory',
    'kotti.authz_policy_factory': 'kotti.acl_factory',
    'kotti.available_types': ' '.join([
        'kotti.resources.Document',
        'kotti.resources.File',
        ]),
    'kotti.base_includes': ' '.join([
        'kotti',
        'kotti.traversal',
        'kotti.filedepot',
        'kotti.events',
        'kotti.sanitizers',
        'kotti.views',
        'kotti.views.cache',
        'kotti.views.view',
        'kotti.views.edit',
        'kotti.views.edit.actions',
        'kotti.views.edit.content',
        'kotti.views.edit.default_views',
        'kotti.views.edit.upload',
        'kotti.views.file',
        'kotti.views.login',
        'kotti.views.navigation',
        'kotti.views.users',
        ]),
    'kotti.caching_policy_chooser': (
        'kotti.views.cache.default_caching_policy_chooser'),
    'kotti.configurators': '',
    'kotti.date_format': 'medium',
    'kotti.datetime_format': 'medium',
    'kotti.depot_mountpoint': '/depot',
    'kotti.depot_replace_wsgi_file_wrapper': False,
    'kotti.depot.0.backend': 'kotti.filedepot.DBFileStorage',
    'kotti.depot.0.name': 'dbfiles',
    'kotti.fanstatic.edit_needed': 'kotti.fanstatic.edit_needed',
    'kotti.fanstatic.view_needed': 'kotti.fanstatic.view_needed',
    'kotti.login_success_callback': 'kotti.views.login.login_success_callback',
    'kotti.max_file_size': '10',
    'kotti.modification_date_excludes': ' '.join([
        'kotti.resources.Node.position',
    ]),
    'kotti.populators': 'kotti.populate.populate',
    'kotti.principals_factory': 'kotti.security.principals_factory',
    'kotti.register': 'False',
    'kotti.register.group': '',
    'kotti.register.role': '',
    'kotti.request_factory': 'kotti.request.Request',
    'kotti.reset_password_callback': 'kotti.views.login.reset_password_callback',  # noqa
    'kotti.root_factory': 'kotti.resources.default_get_root',
    'kotti.sanitizers': ' '.join([
        'xss_protection:kotti.sanitizers.xss_protection',
        'minimal_html:kotti.sanitizers.minimal_html',
        'no_html:kotti.sanitizers.no_html',
        ]),
    'kotti.sanitize_on_write': ' '.join([
        'kotti.resources.Document.body:xss_protection',
        'kotti.resources.Content.title:no_html',
        'kotti.resources.Content.description:no_html',
        ]),
    'kotti.search_content': 'kotti.views.util.default_search_content',
    'kotti.session_factory': 'kotti.beaker_session_factory',
    'kotti.static.edit_needed': '',  # BBB
    'kotti.static.view_needed': '',  # BBB
    'kotti.templates.api': 'kotti.views.util.TemplateAPI',
    'kotti.time_format': 'medium',
    'kotti.url_normalizer': 'kotti.url_normalizer.url_normalizer',
    'kotti.url_normalizer.map_non_ascii_characters': True,
    'kotti.use_tables': '',
    'kotti.use_workflow': 'kotti:workflow.zcml',
    'kotti.zcml_includes': ' '.join([
        ]),
    'pyramid.includes': '',
    'pyramid_deform.template_search_path': 'kotti:templates/deform',
    }

conf_dotted = {
    'kotti.authn_policy_factory',
    'kotti.authz_policy_factory',
    'kotti.available_types',
    'kotti.base_includes',
    'kotti.caching_policy_chooser',
    'kotti.configurators',
    'kotti.fanstatic.edit_needed',
    'kotti.fanstatic.view_needed',
    'kotti.login_success_callback',
    'kotti.modification_date_excludes',
    'kotti.populators',
    'kotti.principals_factory',
    'kotti.request_factory',
    'kotti.reset_password_callback',
    'kotti.root_factory',
    'kotti.search_content',
    'kotti.session_factory',
    'kotti.templates.api',
    'kotti.url_normalizer',
}


def get_version():
    return pkg_resources.require("Kotti")[0].version


def get_settings():
    return get_current_registry().settings


def _resolve_dotted(d, keys=conf_dotted):

    resolved = d.copy()

    for key in keys:
        value = resolved[key]
        if not isinstance(value, string_types):
            continue
        new_value = []
        for dottedname in value.split():
            new_value.append(DottedNameResolver().resolve(dottedname))
        resolved[key] = new_value

    return resolved


def main(global_config, **settings):
    # This function is a 'paste.app_factory' and returns a WSGI
    # application.

    from kotti.resources import initialize_sql
    config = base_configure(global_config, **settings)
    engine = engine_from_config(config.registry.settings)
    initialize_sql(engine)
    return config.make_wsgi_app()


def base_configure(global_config, **settings):
    # Resolve dotted names in settings, include plug-ins and create a
    # Configurator.

    from kotti.resources import get_root

    for key, value in conf_defaults.items():
        settings.setdefault(key, value)

    for key, value in settings.items():
        if key.startswith('kotti') and isinstance(value, binary_type):
            settings[key] = unicode(value, 'utf8')

    # will be removed in 2.0
    import kotti_image
    kotti_image.kotti_configure(settings)

    # Allow extending packages to change 'settings' w/ Python:
    k = 'kotti.configurators'
    for func in _resolve_dotted(settings, keys=(k,))[k]:
        func(settings)

    settings = _resolve_dotted(settings)
    secret1 = settings['kotti.secret']
    settings.setdefault('kotti.secret2', secret1)

    # We'll process ``pyramid_includes`` later by hand, to allow
    # overrides of configuration from ``kotti.base_includes``:
    pyramid_includes = settings.pop('pyramid.includes', '')

    config = Configurator(
        request_factory=settings['kotti.request_factory'][0],
        settings=settings)
    config.begin()

    config.hook_zca()
    config.include('pyramid_zcml')

    # Chameleon bindings were removed from Pyramid core since pyramid>=1.5a2
    config.include('pyramid_chameleon')

    config.registry.settings['pyramid.includes'] = pyramid_includes

    # Include modules listed in 'kotti.base_includes':
    for module in settings['kotti.base_includes']:
        config.include(module)
    config.commit()

    # Modules in 'pyramid.includes' and 'kotti.zcml_includes' may
    # override 'kotti.base_includes':
    if pyramid_includes:
        for module in pyramid_includes.split():
            config.include(module)

    for name in settings['kotti.zcml_includes'].strip().split():
        config.load_zcml(name)

    config.commit()

    config._set_root_factory(get_root)

    return config


def includeme(config):
    """ Pyramid includeme hook.

    :param config: app config
    :type config: :class:`pyramid.config.Configurator`
    """

    import kotti.views.util

    settings = config.get_settings()

    authentication_policy = settings[
        'kotti.authn_policy_factory'][0](**settings)
    authorization_policy = settings[
        'kotti.authz_policy_factory'][0](**settings)
    session_factory = settings['kotti.session_factory'][0](**settings)
    if authentication_policy:
        config.set_authentication_policy(authentication_policy)
    if authorization_policy:
        config.set_authorization_policy(authorization_policy)
    config.set_session_factory(session_factory)

    config.add_subscriber(
        kotti.views.util.add_renderer_globals, BeforeRender)

    for override in [a.strip()
                     for a in settings['kotti.asset_overrides'].split()
                     if a.strip()]:
        config.override_asset(to_override='kotti', override_with=override)

    config.add_translation_dirs('kotti:locale')

    workflow = settings['kotti.use_workflow']
    if workflow.lower() not in FALSE_VALUES:
        config.load_zcml(workflow)

    return config
