# -*- coding: utf-8 -*-
import json
from mock import patch
from werkzeug.exceptions import NotFound
from flask_api_auth.models import APIUser, APIRole
from flask_api_auth.decorators import requires_auth
from test import utils

from . import ResourceTestCase


ADMIN_USER = 'yoda'


class RoleListTestCase(ResourceTestCase):
    test_user = ADMIN_USER

    def _get(self, **kwargs):
        return self.with_auth('get', '/roles/', **kwargs)

    def _post(self, **kwargs):
        return self.with_auth(
            'post',
            '/roles/',
            data=json.dumps(utils.get_stored_role_data()[0]),
            content_type='application/json',
            **kwargs
        )

    @patch(
        'flask_api_auth.models.APIRole.manager.search',
        return_value=[APIRole(role) for role in utils.get_stored_role_data()],
        autospec=True
    )
    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0]),
        autospec=True
    )
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_list(self, save_user, get_mock, search_mock):
        res = self._get()
        self.assertEqual(res.status_code, 200)
        roles = json.loads(res.data)
        self.assertEqual(len(roles), len(utils.get_stored_role_data()))

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0]),
        autospec=True
    )
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_get_superuser_with_invalid_referer_user(self, save_mock, get_role_mock):
        headers = {
            'ApiAuth-Referer': 'invalid-user',
        }
        self.apiuser_deco_mock.manager.get.side_effect = [
            self.current_user,
            NotFound,
        ]
        res = self._get(auth_headers=headers)
        self.assertEqual(res.status_code, 401)

    @patch(
        'flask_api_auth.models.APIRole.manager.search',
        return_value=[APIRole(role) for role in utils.get_stored_role_data()],
        autospec=True
    )
    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0]),
        autospec=True
    )
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_get_superuser_with_valid_referer_user(self, save_mock, get_role_mock, search_mock):
        headers = {
            'ApiAuth-Referer': 'han',
        }
        self.apiuser_deco_mock.manager.get.side_effect = [
            self.current_user,
            APIUser(utils.get_stored_apiusers_data()[3]),
        ]
        res = self._get(auth_headers=headers)
        self.assertEqual(res.status_code, 200)
        self.assertTrue(search_mock.called)

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        side_effect=[APIRole(utils.get_stored_role_data()[2]), NotFound]
    )
    @patch('flask_api_auth.models.APIUser.manager.save')
    @patch('flask_api_auth.models.APIRole.manager.save')
    def test_post(self, save_role, save_mock, get_mock):
        res = self._post()
        self.assertEqual(res.status_code, 201)
        role = json.loads(res.data)
        self.assertEqual(role, utils.get_stored_role_data()[0])

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0]),
        autospec=True
    )
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_post_existent(self, save_mock, get_mock):
        res = self._post()
        self.assertEqual(res.status_code, 409)

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0]),
        autospec=True
    )
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_post_invalid_schema(self, save_mock, get_mock):
        res = self.with_auth(
            'post',
            '/roles/',
            data=json.dumps({'nothing': 'foo', 'invalid': 'role'}),
            content_type='application/json',
        )
        self.assertEqual(res.status_code, 422)


class RoleDetailTestCase(ResourceTestCase):
    test_user = ADMIN_USER

    def _get(self):
        return self.with_auth('get', '/roles/INTERNAL/some_role/')

    def _put(self):
        return self.with_auth(
            'put',
            '/roles/INTERNAL/some_role/',
            data=json.dumps({'permissions': {'some_argument': 18}}),
            content_type='application/json',
        )

    def _delete(self):
        return self.with_auth('delete', '/roles/INTERNAL/some_role/')

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0]),
        autospec=True
    )
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_get_existent(self, save_mock, get_mock):
        res = self._get()
        self.assertEqual(res.status_code, 200)
        self.assertEqual(res.mimetype, 'application/json')

        role = json.loads(res.data)
        stored_role = utils.get_stored_role_data()[0]

        for key, value in role.iteritems():
            self.assertEqual(value, stored_role[key])

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        side_effect=[APIRole(utils.get_stored_role_data()[2]), NotFound]
    )
    @patch('flask_api_auth.models.APIUser.manager.get', side_effect=NotFound)
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_get_missing(self, save_mock, get_mock, get_role):
        res = self._get()
        self.assertEqual(res.status_code, 404)

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0], validate=False),
        autospec=True
    )
    @patch('flask_api_auth.models.APIUser.manager.save')
    @patch('flask_api_auth.models.APIRole.manager.save')
    def test_put_existent(self, save_mock, save_user, get_mock):
        res = self._put()
        self.assertEqual(res.status_code, 200)

        role = json.loads(res.data)
        self.assertEqual(role['permissions']['some_argument'], 18)
        self.assertEqual(save_mock.call_count, 1)

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        side_effect=[APIRole(utils.get_stored_role_data()[2]), NotFound]
    )
    @patch('flask_api_auth.models.APIUser.manager.save')
    @patch('flask_api_auth.models.APIRole.manager.save')
    def test_put_missing(self, save_mock, save_user, get_mock):
        res = self._put()
        self.assertEqual(res.status_code, 404)

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0]),
        autospec=True
    )
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_put_invalid_schema(self, save_mock, get_mock):
        res = self.with_auth(
            'post',
            '/roles/',
            data=json.dumps({'valid_until': 'invalid'}),
            content_type='application/json',
        )
        self.assertEqual(res.status_code, 422)

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0]),
        autospec=True
    )
    @patch('flask_api_auth.models.APIRole.manager.delete')
    @patch('flask_api_auth.models.APIUser.manager.search', return_value=[])
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_delete_existent(self, save_mock, search, delete_mock, get_mock):
        res = self._delete()
        self.assertEqual(res.status_code, 204)

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        side_effect=[APIRole(utils.get_stored_role_data()[2]), NotFound]
    )
    @patch('flask_api_auth.models.APIUser.manager.get', side_effect=NotFound)
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_delete_missing(self, save_mock, get_user, get_mock):
        res = self._delete()
        self.assertEqual(res.status_code, 404)

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0]),
        autospec=True
    )
    @patch(
        'flask_api_auth.models.APIUser.manager.search',
        return_value=[APIUser(utils.get_stored_apiusers_data()[0])],
        autospec=True
    )
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_delete_in_use(self, save_mock, search, get_mock):
        res = self._delete()
        self.assertEqual(res.status_code, 409)


class APIUserListTestCase(ResourceTestCase):
    test_user = ADMIN_USER

    def _get(self):
        return self.with_auth('get', '/users/')

    def _post(self, index=0):
        return self.with_auth(
            'post',
            '/users/',
            data=json.dumps(utils.get_stored_apiusers_data()[index]),
            content_type='application/json',
        )

    @requires_auth
    @patch('flask_api_auth.models.APIUser.manager.get', side_effect=NotFound)
    def test_user_last_login(self, get):
        res = self._post()
        self.assertEqual(res.status_code, 201)
        apiusers = json.loads(res.data)
        self.assertEqual(
            apiusers['name'],
            utils.get_stored_apiusers_data()[0]['name'],
        )
        self.assertNotEqual(apiusers.last_login, None)
        from datetime import datetime
        self.assertTrue(isinstance(apiusers.last_login, datetime))

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0]),
        autospec=True
    )
    @patch(
        'flask_api_auth.models.APIUser.manager.search',
        return_value=[
            APIUser(apiuser, validate=False) for apiuser in utils.get_stored_apiusers_data()
        ],
        autospec=True
    )
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_list(self, save, search_mock, get_role):
        res = self._get()
        self.assertEqual(res.status_code, 200)
        apiusers = json.loads(res.data)
        self.assertEqual(len(apiusers), len(utils.get_stored_apiusers_data()))

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0]),
        autospec=True
    )
    @patch('flask_api_auth.models.APIUser.manager.get', side_effect=NotFound)
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_post_with_key(self, save, get, get_role):
        res = self._post()
        self.assertEqual(res.status_code, 201)
        apiusers = json.loads(res.data)
        self.assertEqual(apiusers['name'],
                         utils.get_stored_apiusers_data()[0]['name'])
        self.assertEqual(
            apiusers['key'], utils.get_stored_apiusers_data()[0]['key'])

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0]),
        autospec=True
    )
    @patch('flask_api_auth.models.APIUser.manager.get', side_effect=NotFound)
    @patch('flask_api_auth.models.APIUser.manager.save')
    @patch('flask_api_auth.models.APIUser.generate_key', return_value='supersecret-key')
    def test_post_without_key(self, generate_key, save, get, get_role):
        res = self._post(1)
        self.assertEqual(res.status_code, 201)
        apiusers = json.loads(res.data)
        self.assertEqual(apiusers['name'],
                         utils.get_stored_apiusers_data()[1]['name'])
        self.assertEqual(apiusers['key'], 'supersecret-key')

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0]),
        autospec=True
    )
    @patch(
        'flask_api_auth.models.APIUser.manager.get',
        return_value=APIUser(utils.get_stored_apiusers_data()[0]),
        autospec=True
    )
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_post_existent(self, save, get, get_role):
        res = self._post()
        self.assertEqual(res.status_code, 409)

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0]),
        autospec=True
    )
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_post_invalid_schema(self, save, get):
        res = self.with_auth(
            'post',
            '/users/',
            data=json.dumps(
                {'nothing': 'foo', 'invalid': 'apiusers'}),
            content_type='application/json'
        )
        self.assertEqual(res.status_code, 422)


class APIUserDetailTestCase(ResourceTestCase):
    test_user = ADMIN_USER

    def _get(self):
        return self.with_auth('get', '/users/han/')

    def _put(self):
        return self.with_auth(
            'put',
            '/users/han/',
            data=json.dumps({'is_active': False}),
            content_type='application/json',
        )

    def _update_superuser(self, superuser=False):
        return self.with_auth(
            'put',
            '/users/han/',
            data=json.dumps({'is_superuser': superuser}),
            content_type='application/json',
        )

    def _delete(self):
        return self.with_auth('delete', '/users/han/')

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0]),
        autospec=True
    )
    @patch(
        'flask_api_auth.models.APIUser.manager.get',
        return_value=APIUser(utils.get_stored_apiusers_data()[0],
                             validate=False),
        autospec=True
    )
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_get_existent(self, save_mock, get_mock, get_role):
        res = self._get()
        self.assertEqual(res.status_code, 200)
        self.assertEqual(res.mimetype, 'application/json')

        obj = json.loads(res.data)
        # Han solo
        stored_obj = utils.get_stored_apiusers_data()[0]

        self.assertEqual(obj['name'], stored_obj['name'])
        self.assertEqual(obj['is_active'], True)
        self.assertEqual(obj['is_superuser'], False)
        self.assertEqual(obj['key'], stored_obj['key'])

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0]),
        autospec=True
    )
    @patch('flask_api_auth.models.APIUser.manager.get', side_effect=NotFound)
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_get_missing(self, save_mock, get_mock, get_role):
        res = self._get()
        self.assertEqual(res.status_code, 404)

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0]),
        autospec=True
    )
    @patch(
        'flask_api_auth.models.APIUser.manager.get',
        return_value=APIUser(utils.get_stored_apiusers_data()[0],
                             validate=False),
        autospec=True
    )
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_put_existent(self, save_mock, get_mock, get_role):
        res = self._put()
        self.assertEqual(res.status_code, 200)

        obj = json.loads(res.data)
        stored_obj = utils.get_stored_apiusers_data()[0]
        self.assertEqual(obj['name'], stored_obj['name'])
        self.assertEqual(obj['is_active'], False)
        self.assertEqual(obj['key'], stored_obj['key'])
        self.assertEqual(get_mock.call_count, 1)

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0]),
        autospec=True
    )
    @patch(
        'flask_api_auth.models.APIUser.manager.get',
        return_value=APIUser(utils.get_stored_apiusers_data()[0],
                             validate=False),
        autospec=True
    )
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_put_superuser_existent(self, save_mock, get_mock, get_role):
        res = self._update_superuser(superuser=True)
        self.assertEqual(res.status_code, 200)
        obj = json.loads(res.data)
        self.assertEqual(obj['is_superuser'], True)
        res = self._update_superuser(superuser=False)
        self.assertEqual(res.status_code, 200)
        obj = json.loads(res.data)
        self.assertEqual(obj['is_superuser'], False)

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0]),
        autospec=True
    )
    @patch('flask_api_auth.models.APIUser.manager.get', side_effect=NotFound)
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_put_missing(self, save_mock, get_mock, get_role):
        res = self._put()
        self.assertEqual(res.status_code, 404)

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0]),
        autospec=True
    )
    @patch(
        'flask_api_auth.models.APIUser.manager.get',
        return_value=APIUser(utils.get_stored_apiusers_data()[0]),
        autospec=True
    )
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_put_invalid_schema(self, save_mock, get_mock, get_role):
        res = self.with_auth('put', '/users/han/',
                             data=json.dumps({'key': 'invalid'}),
                             content_type='application/json')
        self.assertEqual(res.status_code, 422)

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0]),
        autospec=True
    )
    @patch(
        'flask_api_auth.models.APIUser.manager.get',
        return_value=APIUser(utils.get_stored_apiusers_data()[0]),
        autospec=True
    )
    @patch('flask_api_auth.models.APIUser.manager.delete')
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_delete_existent(self, save_mock, delete_mock, get_mock, get_role):
        res = self._delete()
        self.assertEqual(res.status_code, 204)

    @patch(
        'flask_api_auth.models.APIRole.manager.get',
        return_value=APIRole(utils.get_stored_role_data()[0]),
        autospec=True
    )
    @patch('flask_api_auth.models.APIUser.manager.get', side_effect=NotFound)
    @patch('flask_api_auth.models.APIUser.manager.save')
    def test_delete_missing(self, save_mock, get_mock, get_role):
        res = self._delete()
        self.assertEqual(res.status_code, 404)
