from unittest import TestCase

from elasticsearch import NotFoundError as ElasticsearchNotFoundError
from mock import patch
from werkzeug.exceptions import NotFound

from flask_api_auth.models import APIRole
from distribution.managers.auth import RoleManager

from . import ElasticsearchMock, get_document


def get_role_base_document(name, permissions={}, is_active=True, valid_until=None):
    doc = {
        '_index': 'distribution_feed',
        '_type': 'apirole',
        '_id': name,
        '_source': {
            'name': name,
            'permissions': permissions,
            'is_active': is_active,
            'valid_until': valid_until
        }
    }

    return doc


def get_role_search_results(roles):
    results = {
        'took': 69,
        'timed_out': False,
        '_shards': {
            'total': 5,
            'successful': 5,
            'failed': 0
        },
        'hits': {
            'total': len(roles),
            'max_score': 1,
            'hits': []
        }
    }

    for role in roles:
        doc = get_role_base_document(**role)
        doc['score'] = 1
        results['hits']['hits'].append(doc)

    return results


def get_role_document(*args, **kwargs):
    return get_document(get_role_base_document(*args, **kwargs))


class ElasticRoleManagerTestCase(TestCase):

    def setUp(self):
        APIRole.manager_class = RoleManager
        self.manager = APIRole.manager
        self.manager.backend._client = ElasticsearchMock()

    @patch('elasticsearch.Elasticsearch.get',
           return_value=get_role_document('admin', {'source': 'yourshot'}))
    def test_get_role_existent(self, get_mock):
        item = self.manager.get('admin')
        self.assertEqual(item.data['permissions'], {'source': 'yourshot'})
        self.assertEqual(item.data['is_active'], True)
        get_mock.assert_called_once_with('distribution_auth', 'admin', 'apirole')

    @patch('elasticsearch.Elasticsearch.get', side_effect=ElasticsearchNotFoundError)
    def test_get_role_missing(self, get_mock):
        with self.assertRaises(NotFound):
            self.manager.get('admin')
        get_mock.assert_called_once_with('distribution_auth', 'admin', 'apirole')

    @patch('elasticsearch.Elasticsearch.search', side_effect=Exception)
    def test_get_role_permissions_no_incoming_roles(self, search_mock):
        self.assertIsNone(self.manager.get_role_permissions([]))
        self.assertEqual(search_mock.call_count, 0)

    def check_search_call(self, search_mock, **kwargs):
        result = self.manager.get_role_permissions(['admin', 'producer'], **kwargs)
        self.assertEqual(search_mock.call_count, 1)
        self.assertEqual(search_mock.call_args[0][0], 'distribution_auth')
        self.assertEqual(search_mock.call_args[0][1], 'apirole')

        return result

    @patch('elasticsearch.Elasticsearch.search', return_value=get_role_search_results([]))
    def test_get_role_permissions_empty_result(self, search_mock):
        self.assertIsNone(self.check_search_call(search_mock))
        self.assertEqual(len(search_mock.call_args[0][2]['post_filter']['bool']['must']), 3)

    @patch('elasticsearch.Elasticsearch.search', return_value=get_role_search_results([
        {'name': 'admin', 'permissions': {'source': 'aem'}}
    ]))
    def test_get_role_permissions_single_result(self, search_mock):
        result = self.check_search_call(search_mock)
        self.assertEqual(result['source'], 'aem')
        self.assertEqual(len(search_mock.call_args[0][2]['post_filter']['bool']['must']), 3)

    @patch('elasticsearch.Elasticsearch.search', return_value=get_role_search_results([
        {'name': 'producer', 'permissions': {'source': 'yourshot'}},
        {'name': 'admin', 'permissions': {'source': 'aem'}}
    ]))
    def test_get_role_permissions_many_result(self, search_mock):
        result = self.check_search_call(search_mock)
        self.assertEqual(result['source'], 'yourshot')
        self.assertEqual(len(search_mock.call_args[0][2]['post_filter']['bool']['must']), 3)

    @patch('elasticsearch.Elasticsearch.search', return_value=get_role_search_results([
        {'name': 'admin', 'permissions': {'source': 'aem'}}
    ]))
    def test_get_role_permissions_is_active_false(self, search_mock):
        result = self.check_search_call(search_mock, is_active=False)
        self.assertEqual(result['source'], 'aem')
        self.assertEqual(len(search_mock.call_args[0][2]['post_filter']['bool']['must']), 2)

    @patch('elasticsearch.Elasticsearch.search', return_value=get_role_search_results([
        {'name': 'admin', 'permissions': {'source': 'aem'}}
    ]))
    def test_get_role_permissions_use_valid_until_false(self, search_mock):
        result = self.check_search_call(search_mock, use_valid_until=False)
        self.assertEqual(result['source'], 'aem')
        self.assertEqual(len(search_mock.call_args[0][2]['post_filter']['bool']['must']), 2)

    @patch('elasticsearch.Elasticsearch.search', return_value=get_role_search_results([
        {'name': 'admin', 'permissions': {'source': 'aem'}}
    ]))
    def test_get_role_permissions_all_false(self, search_mock):
        result = self.check_search_call(search_mock, is_active=False, use_valid_until=False)
        self.assertEqual(result['source'], 'aem')
        self.assertEqual(len(search_mock.call_args[0][2]['post_filter']['bool']['must']), 1)

    @patch('elasticsearch.Elasticsearch.search', return_value=get_role_search_results([
        {'name': 'admin', 'permissions': {'source': 'aem'}},
        {'name': 'user', 'permissions': {'source': 'yourshot'}}
    ]))
    def test_get_all_roles(self, search_mock):
        result = self.manager.search()
        self.assertEqual(len(result), 2)
        self.assertEqual(result[0].data['name'], 'admin')
        self.assertEqual(result[0].data['permissions']['source'], 'aem')
        self.assertEqual(result[1].data['name'], 'user')
        self.assertEqual(result[1].data['permissions']['source'], 'yourshot')

    @patch('distribution.managers.backends.elastic.bulk')
    def test_delete_roles(self, bulk_mock):
        self.manager.delete(['admin', 'user', 'publisher'])

        self.assertEqual(len(bulk_mock.call_args[0][1]), 3)

        self.assertEqual(bulk_mock.call_args[0][1][0], {
            '_op_type': 'delete',
            '_index': 'distribution_auth',
            '_type': 'apirole',
            '_id': 'admin'
        })

    @patch('elasticsearch.Elasticsearch.index')
    def test_save_role(self, index_mock):
        role = APIRole({'name': 'admin', 'permissions': {'source': 'aem'}})
        self.manager.save(role)

        index_mock.assert_called_once_with('distribution_auth', 'apirole', role.data, role.key)
