import os

from anykeystore import create_store_from_settings

from pyramid.config import Configurator
from pyramid.exceptions import ConfigurationError
from pyramid.response import Response

from velruse.app.utils import generate_token
from velruse.app.utils import redirect_form


log = __import__('logging').getLogger(__name__)


def auth_complete_view(context, request):
    endpoint = request.registry.settings.get('endpoint')
    token = generate_token()
    storage = request.registry.velruse_store
    result_data = {
        'provider_type': context.provider_type,
        'provider_name': context.provider_name,
        'profile': context.profile,
        'credentials': context.credentials,
    }
    storage.store(token, result_data, expires=300)
    form = redirect_form(endpoint, token)
    return Response(body=form)


def auth_denied_view(context, request):
    endpoint = request.registry.settings.get('endpoint')
    token = generate_token()
    storage = request.registry.velruse_store
    error_dict = {
        'provider_type': context.provider_type,
        'provider_name': context.provider_name,
        'error': context.reason,
    }
    storage.store(token, error_dict, expires=300)
    form = redirect_form(endpoint, token)
    return Response(body=form)


def auth_info_view(request):
    # TODO: insecure URL, must be protected behind a firewall
    storage = request.registry.velruse_store
    token = request.GET.get('token')
    try:
        return storage.retrieve(token)
    except KeyError:
        log.info('auth_info requested invalid token "%s"')
        request.response.status = 400
        return None


def default_setup(config):
    """Configure Velruse's session factory and backend storage.

    The default setup uses Pyramid's
    ``UnencryptedCookieSessionFactoryConfig`` for storing session data.

    Relevant settings:

    ``session.secret`` controls the secret used when signing the session
    cookies and will be randomly generated if unspecified.

    ``session.cookie_name`` is the name of the cookie stored on a client's
    browser and will default to 'velruse.session'.

    ``store.*`` settings are used by the `anykeystore` library to construct
    a storage backend for user credentials. If no storage settings are
    specified then an in-memory storage backend will be used.

    """
    from pyramid.session import UnencryptedCookieSessionFactoryConfig

    log.info('Using an unencrypted cookie-based session. This can be '
             'changed by pointing the "velruse.setup" setting at a different '
             'function for configuring the session factory.')

    settings = config.registry.settings
    secret = settings.get('session.secret')
    cookie_name = settings.get('session.cookie_name', 'velruse.session')
    if secret is None:
        log.warn('Configuring unencrypted cookie-based session with a '
                 'random secret which will invalidate old cookies when '
                 'restarting the app.')
        secret = ''.join('%02x' % ord(x) for x in os.urandom(16))
        log.info('autogenerated session secret: %s', secret)
    factory = UnencryptedCookieSessionFactoryConfig(
        secret, cookie_name=cookie_name)
    config.set_session_factory(factory)

    # setup backing storage
    storage_string = settings.get('store', 'memory')
    settings['store.store'] = storage_string
    store = create_store_from_settings(settings, prefix='store.')
    config.register_velruse_store(store)


def register_velruse_store(config, storage):
    """Add key/value store for Velruse to the Pyramid application.

    This function is registered with Pyramid and can be used via
    ``config.register_velruse_store(storage)``.

    ``storage`` should be an instance of an `anykeystore` backend.

    """
    config.registry.velruse_store = storage


settings_adapter = {
    'bitbucket': 'add_bitbucket_login_from_settings',
    'douban': 'add_douban_login_from_settings',
    'facebook': 'add_facebook_login_from_settings',
    'github': 'add_github_login_from_settings',
    'google': 'add_google_login_from_settings',
    'google_oauth2': 'add_google_oauth2_login_from_settings',
    'lastfm': 'add_lastfm_login_from_settings',
    'linkedin': 'add_linkedin_login_from_settings',
    'live': 'add_live_login_from_settings',
    'qq': 'add_qq_login_from_settings',
    'renren': 'add_renren_login_from_settings',
    'taobao': 'add_taobao_login_from_settings',
    'twitter': 'add_twitter_login_from_settings',
    'weibo': 'add_weibo_login_from_settings',
}


def find_providers(settings):
    providers = set()
    for k in settings:
        if k.startswith('provider.'):
            k = k[9:].split('.', 1)[0]
            providers.add(k)
    return providers


def load_provider(config, provider):
    settings = config.registry.settings
    impl = settings.get('provider.%s.impl' % provider) or provider

    login_cfg = settings_adapter.get(impl)
    if login_cfg is None:
        raise ConfigurationError(
            'could not find configuration method for provider %s'
            '' % provider)
    loader = getattr(config, login_cfg)
    loader(prefix='provider.%s.' % provider)


def includeme(config):
    """Add the Velruse standalone app configuration to a Pyramid app."""
    settings = config.registry.settings
    config.add_directive('register_velruse_store', register_velruse_store)

    # setup application
    setup = settings.get('setup') or default_setup
    if setup:
        config.include(setup)

    # include supported providers
    for provider in settings_adapter:
        config.include('velruse.providers.%s' % provider)

    # configure requested providers
    for provider in find_providers(settings):
        load_provider(config, provider)

    # check for required settings
    if not settings.get('endpoint'):
        raise ConfigurationError(
            'missing required setting "endpoint"')

    # add views
    config.add_view(
        auth_complete_view,
        context='velruse.AuthenticationComplete')
    config.add_view(
        auth_denied_view,
        context='velruse.AuthenticationDenied')
    config.add_view(
        auth_info_view,
        name='auth_info',
        request_param='format=json',
        renderer='json')


def make_app(global_conf, **settings):
    """Construct a complete WSGI app.

    This function is compatible with the `PasteDeploy` WSGI application factory
    API, which is also used by Pyramid's ``pserve`` script.

    Example INI file:

    .. code-block:: ini

        [server:main]
        use = egg:Paste#http
        host = 0.0.0.0
        port = 80

        [composite:main]
        use = egg:Paste#urlmap
        / = YOURAPP
        /velruse = velruse

        [app:velruse]
        use = egg:velruse

        setup = myapp.setup_velruse

        endpoint = http://example.com/logged_in

        store = redis
        store.host = localhost
        store.port = 6379
        store.db = 0
        store.key_prefix = velruse_ustore

        provider.facebook.consumer_key = KMfXjzsA2qVUcnnRn3vpnwWZ2pwPRFZdb
        provider.facebook.consumer_secret =
            ULZ6PkJbsqw2GxZWCIbOEBZdkrb9XwgXNjRy
        provider.facebook.scope = email

        provider.tw.impl = twitter
        provider.tw.consumer_key = ULZ6PkJbsqw2GxZWCIbOEBZdkrb9XwgXNjRy
        provider.tw.consumer_secret = eoCrFwnpBWXjbim5dyG6EP7HzjhQzFsMAcQOEK

        [app:YOURAPP]
        use = egg:YOURAPP
        full_stack = true
        static_files = true

    """
    config = Configurator(settings=settings)
    config.include(includeme)
    return config.make_wsgi_app()
