import base64
import datetime
import pymongo
import os

from ..web import base
from .. import util
from .. import config
from .. import validators
from ..auth import userauth, require_admin
from ..auth.apikeys import UserApiKey
from ..dao import containerstorage
from ..dao import noop

from ..web.errors import APIStorageException

log = config.log


class UserHandler(base.RequestHandler):

    def __init__(self, request=None, response=None):
        super(UserHandler, self).__init__(request, response)
        self.storage = containerstorage.ContainerStorage('users', use_object_id=False)

    def get(self, _id):
        user = self._get_user(_id)
        permchecker = userauth.default(self, user)
        projection = None
        if not self.user_is_admin:
            projection = {'wechat': 0}
        result = permchecker(self.storage.exec_op)('GET', _id, projection=projection or None)
        if result is None:
            self.abort(404, 'User does not exist')
        return result

    def self(self):
        """Return details for the current User."""
        if not self.uid:
            self.abort(400, 'no user is logged in')
        user = self.storage.exec_op('GET', self.uid)
        if not user:
            self.abort(403, 'user does not exist')
        api_key = UserApiKey.get(self.uid)
        if api_key:
            user['api_key'] = {
                'key': api_key['_id'],
                'created': api_key['created'],
                'last_used': api_key['last_used']
            }
        return user

    def get_all(self):
        permchecker = userauth.list_permission_checker(self)
        projection = {'preferences': 0, 'api_key': 0}
        if not self.user_is_admin:
            projection['wechat'] = 0
        result = permchecker(self.storage.exec_op)('GET', projection=projection)
        if result is None:
            self.abort(404, 'Not found')
        return result

    def delete(self, _id):
        user = self._get_user(_id)
        permchecker = userauth.default(self, user)
        # Check for authZ before cleaning up user permissions
        permchecker(noop)('DELETE', _id)
        self._cleanup_user_permissions(user.get('_id'))
        result = self.storage.exec_op('DELETE', _id)
        if result.deleted_count == 1:
            return {'deleted': result.deleted_count}
        else:
            self.abort(404, 'User {} not removed'.format(_id))
        return result

    @validators.verify_payload_exists
    def put(self, _id):
        """Update a user"""
        user = self._get_user(_id)
        permchecker = userauth.default(self, user)
        payload = self.request.json_body
        if not payload:
            self.abort(400, 'PUT request body cannot be empty')
        mongo_schema_uri = validators.schema_uri('mongo', 'user.json')
        mongo_validator = validators.decorator_from_schema_path(mongo_schema_uri)
        payload_schema_uri = validators.schema_uri('input', 'user-update.json')
        payload_validator = validators.from_schema_path(payload_schema_uri)
        payload_validator(payload, 'PUT')

        payload['modified'] = datetime.datetime.utcnow()
        result = mongo_validator(permchecker(self.storage.exec_op))('PUT', _id=_id, payload=payload)
        if result.modified_count == 1:
            if payload.get('disabled', False) and self.is_true('clear_permissions'):
                self._cleanup_user_permissions(_id)
            return {'modified': result.modified_count}
        else:
            self.abort(404, 'User {} not updated'.format(_id))

    def post(self):
        """Add user"""
        permchecker = userauth.default(self)
        payload = self.request.json_body
        if self.is_true('wechat'):
            payload['wechat'] = {'registration_code': base64.urlsafe_b64encode(os.urandom(42))}
        mongo_schema_uri = validators.schema_uri('mongo', 'user.json')
        mongo_validator = validators.decorator_from_schema_path(mongo_schema_uri)
        payload_schema_uri = validators.schema_uri('input', 'user-new.json')
        payload_validator = validators.from_schema_path(payload_schema_uri)
        payload_validator(payload, 'POST')
        payload['created'] = payload['modified'] = datetime.datetime.utcnow()
        payload['root'] = payload.get('root', False)
        payload.setdefault('email', payload['_id'])
        payload.setdefault('avatars', {})
        result = mongo_validator(permchecker(self.storage.exec_op))('POST', payload=payload)
        if result.acknowledged:
            return {'_id': result.inserted_id}
        else:
            self.abort(404, 'User {} not updated'.format(payload['_id']))

    def _cleanup_user_permissions(self, uid):
        try:

            query = {'permissions._id': uid}
            update = {'$pull': {'permissions' : {'_id': uid}}}

            for cont in ['collections', 'groups', 'projects', 'sessions', 'acquisitions']:
                config.db[cont].update_many(query, update)

        except APIStorageException:
            self.abort(500, 'Site-wide user permissions for {} were unabled to be removed'.format(uid))

    def avatar(self, uid):
        self.resolve_avatar(uid, default=self.request.GET.get('default'))

    def self_avatar(self):
        if self.uid is None:
            self.abort(404, 'not a logged-in user')
        self.resolve_avatar(self.uid, default=self.request.GET.get('default'))

    def resolve_avatar(self, email, default=None):
        """
        Given an email, redirects to their avatar.
        On failure, either 404s or redirects to default, if provided.
        """

        # Storage throws a 404; we want to catch that and handle it separately in the case of a provided default.
        try:
            user = self._get_user(email)
        except APIStorageException:
            user = {}

        avatar  = user.get('avatar', None)

        # If the user exists but has no set avatar, try to get one
        if user and avatar is None:
            gravatar = util.resolve_gravatar(email)

            if gravatar is not None:
                user = config.db['users'].find_one_and_update({
                        '_id': email,
                    }, {
                        '$set': {
                            'avatar': gravatar,
                            'avatars.gravatar': gravatar,
                        }
                    },
                    return_document=pymongo.collection.ReturnDocument.AFTER
                )

        if user.get('avatar', None):
            # Our data is unicode, but webapp2 wants a python-string for its headers.
            self.redirect(str(user['avatar']), code=307)
        elif default is not None:
            self.redirect(str(default), code=307)
        else:
            self.abort(404, 'no avatar')

    def generate_api_key(self):
        if not self.uid:
            self.abort(400, 'no user is logged in')
        generated_key = UserApiKey.generate(self.uid)
        return {'key': generated_key}

    @require_admin
    def reset_registration(self, uid):
        new_registration_code = base64.urlsafe_b64encode(os.urandom(42))
        update = {
            'modified': datetime.datetime.utcnow(),
            'wechat': {
                'registration_code': new_registration_code
            }
        }
        result = self.storage.exec_op('PUT', _id=uid, payload=update)
        if result.modified_count == 1:
            return {'registration_code': new_registration_code}
        else:
            self.abort(404, 'User {} not updated'.format(uid))

    def _get_user(self, _id):
        user = self.storage.get_container(_id)
        if user is not None:
            return user
        else:
            self.abort(404, 'user {} not found'.format(_id))

import base64
import datetime
import pymongo
import os

from ..web import base
from .. import util
from .. import config
from .. import validators
from ..auth import userauth, require_admin
from ..auth.apikeys import UserApiKey
from ..dao import containerstorage
from ..dao import noop

from ..web.errors import APIStorageException

log = config.log


class UserHandler(base.RequestHandler):

    def __init__(self, request=None, response=None):
        super(UserHandler, self).__init__(request, response)
        self.storage = containerstorage.ContainerStorage('users', use_object_id=False)

    def get(self, _id):
        user = self._get_user(_id)
        permchecker = userauth.default(self, user)
        projection = None
        if not self.user_is_admin:
            projection = {'wechat': 0}
        result = permchecker(self.storage.exec_op)('GET', _id, projection=projection or None)
        if result is None:
            self.abort(404, 'User does not exist')
        return result

    def self(self):
        """Return details for the current User."""
        if not self.uid:
            self.abort(400, 'no user is logged in')
        user = self.storage.exec_op('GET', self.uid)
        if not user:
            self.abort(403, 'user does not exist')
        api_key = UserApiKey.get(self.uid)
        if api_key:
            user['api_key'] = {
                'key': api_key['_id'],
                'created': api_key['created'],
                'last_used': api_key['last_used']
            }
        return user

    def get_all(self):
        permchecker = userauth.list_permission_checker(self)
        projection = {'preferences': 0, 'api_key': 0}
        if not self.user_is_admin:
            projection['wechat'] = 0
        result = permchecker(self.storage.exec_op)('GET', projection=projection)
        if result is None:
            self.abort(404, 'Not found')
        return result

    def delete(self, _id):
        user = self._get_user(_id)
        permchecker = userauth.default(self, user)
        # Check for authZ before cleaning up user permissions
        permchecker(noop)('DELETE', _id)
        self._cleanup_user_permissions(user.get('_id'))
        result = self.storage.exec_op('DELETE', _id)
        if result.deleted_count == 1:
            return {'deleted': result.deleted_count}
        else:
            self.abort(404, 'User {} not removed'.format(_id))
        return result

    @validators.verify_payload_exists
    def put(self, _id):
        """Update a user"""
        user = self._get_user(_id)
        permchecker = userauth.default(self, user)
        payload = self.request.json_body
        if not payload:
            self.abort(400, 'PUT request body cannot be empty')
        mongo_schema_uri = validators.schema_uri('mongo', 'user.json')
        mongo_validator = validators.decorator_from_schema_path(mongo_schema_uri)
        payload_schema_uri = validators.schema_uri('input', 'user-update.json')
        payload_validator = validators.from_schema_path(payload_schema_uri)
        payload_validator(payload, 'PUT')

        payload['modified'] = datetime.datetime.utcnow()
        result = mongo_validator(permchecker(self.storage.exec_op))('PUT', _id=_id, payload=payload)
        if result.modified_count == 1:
            if payload.get('disabled', False) and self.is_true('clear_permissions'):
                self._cleanup_user_permissions(_id)
            return {'modified': result.modified_count}
        else:
            self.abort(404, 'User {} not updated'.format(_id))

    def post(self):
        """Add user"""
        permchecker = userauth.default(self)
        payload = self.request.json_body
        if self.is_true('wechat'):
            payload['wechat'] = {'registration_code': base64.urlsafe_b64encode(os.urandom(42))}
        mongo_schema_uri = validators.schema_uri('mongo', 'user.json')
        mongo_validator = validators.decorator_from_schema_path(mongo_schema_uri)
        payload_schema_uri = validators.schema_uri('input', 'user-new.json')
        payload_validator = validators.from_schema_path(payload_schema_uri)
        payload_validator(payload, 'POST')
        payload['created'] = payload['modified'] = datetime.datetime.utcnow()
        payload['root'] = payload.get('root', False)
        payload.setdefault('email', payload['_id'])
        payload.setdefault('avatars', {})
        result = mongo_validator(permchecker(self.storage.exec_op))('POST', payload=payload)
        if result.acknowledged:
            return {'_id': result.inserted_id}
        else:
            self.abort(404, 'User {} not updated'.format(payload['_id']))

    def _cleanup_user_permissions(self, uid):
        try:

            query = {'permissions._id': uid}
            update = {'$pull': {'permissions' : {'_id': uid}}}

            for cont in ['collections', 'groups', 'projects', 'sessions', 'acquisitions']:
                config.db[cont].update_many(query, update)

        except APIStorageException:
            self.abort(500, 'Site-wide user permissions for {} were unabled to be removed'.format(uid))

    def avatar(self, uid):
        self.resolve_avatar(uid, default=self.request.GET.get('default'))

    def self_avatar(self):
        if self.uid is None:
            self.abort(404, 'not a logged-in user')
        self.resolve_avatar(self.uid, default=self.request.GET.get('default'))

    def resolve_avatar(self, email, default=None):
        """
        Given an email, redirects to their avatar.
        On failure, either 404s or redirects to default, if provided.
        """

        # Storage throws a 404; we want to catch that and handle it separately in the case of a provided default.
        try:
            user = self._get_user(email)
        except APIStorageException:
            user = {}

        avatar  = user.get('avatar', None)

        # If the user exists but has no set avatar, try to get one
        if user and avatar is None:
            gravatar = util.resolve_gravatar(email)

            if gravatar is not None:
                user = config.db['users'].find_one_and_update({
                        '_id': email,
                    }, {
                        '$set': {
                            'avatar': gravatar,
                            'avatars.gravatar': gravatar,
                        }
                    },
                    return_document=pymongo.collection.ReturnDocument.AFTER
                )

        if user.get('avatar', None):
            # Our data is unicode, but webapp2 wants a python-string for its headers.
            self.redirect(str(user['avatar']), code=307)
        elif default is not None:
            self.redirect(str(default), code=307)
        else:
            self.abort(404, 'no avatar')

    def generate_api_key(self):
        if not self.uid:
            self.abort(400, 'no user is logged in')
        generated_key = UserApiKey.generate(self.uid)
        return {'key': generated_key}

    @require_admin
    def reset_registration(self, uid):
        new_registration_code = base64.urlsafe_b64encode(os.urandom(42))
        update = {
            'modified': datetime.datetime.utcnow(),
            'wechat': {
                'registration_code': new_registration_code
            }
        }
        result = self.storage.exec_op('PUT', _id=uid, payload=update)
        if result.modified_count == 1:
            return {'registration_code': new_registration_code}
        else:
            self.abort(404, 'User {} not updated'.format(uid))

    def _get_user(self, _id):
        user = self.storage.get_container(_id)
        if user is not None:
            return user
        else:
            self.abort(404, 'user {} not found'.format(_id))

Return details for the current User. # Check for authZ before cleaning up user permissions Update a user Add user Given an email, redirects to their avatar.         On failure, either 404s or redirects to default, if provided. # Storage throws a 404; we want to catch that and handle it separately in the case of a provided default. # If the user exists but has no set avatar, try to get one # Our data is unicode, but webapp2 wants a python-string for its headers.