from django.core.exceptions import PermissionDenied
from django.http import Http404
from django.test import TestCase

from ..apipatch import ApiPatch, InvalidAction


class MockRequest(object):
    def __init__(self, data=None):
        self.data = data


class MockObject(object):
    def __init__(self, pk):
        self.id = pk
        self.pk = pk


class ApiPatchTests(TestCase):
    def test_add(self):
        """add method adds function to patch object"""
        patch = ApiPatch()

        def test_function():
            pass
        patch.add('test-add', test_function)

        self.assertEqual(len(patch._actions), 1)
        self.assertEqual(patch._actions[0]['op'], 'add')
        self.assertEqual(patch._actions[0]['path'], 'test-add')
        self.assertEqual(patch._actions[0]['handler'], test_function)

    def test_remove(self):
        """remove method adds function to patch object"""
        patch = ApiPatch()

        def test_function():
            pass
        patch.remove('test-remove', test_function)

        self.assertEqual(len(patch._actions), 1)
        self.assertEqual(patch._actions[0]['op'], 'remove')
        self.assertEqual(patch._actions[0]['path'], 'test-remove')
        self.assertEqual(patch._actions[0]['handler'], test_function)

    def test_replace(self):
        """replace method adds function to patch object"""
        patch = ApiPatch()

        def test_function():
            pass
        patch.replace('test-replace', test_function)

        self.assertEqual(len(patch._actions), 1)
        self.assertEqual(patch._actions[0]['op'], 'replace')
        self.assertEqual(patch._actions[0]['path'], 'test-replace')
        self.assertEqual(patch._actions[0]['handler'], test_function)

    def test_validate_action(self):
        """validate_action method validates action dict"""
        patch = ApiPatch()

        VALID_ACTIONS = (
            {'op': 'add', 'path': 'test', 'value': 42},
            {'op': 'remove', 'path': 'other-test', 'value': 'Lorem'},
            {'op': 'replace', 'path': 'false-test', 'value': None},
        )

        for action in VALID_ACTIONS:
            patch.validate_action(action)

        # undefined op
        UNSUPPORTED_ACTIONS = (
            {},
            {'op': ''},
            {'no': 'op'},
        )

        for action in UNSUPPORTED_ACTIONS:
            try:
                patch.validate_action(action)
            except InvalidAction as e:
                self.assertEqual(e.args[0], u"undefined op")

        # unsupported op
        try:
            patch.validate_action({'op': 'nope'})
        except InvalidAction as e:
            self.assertEqual(e.args[0], u'"nope" op is unsupported')

        # op lacking patch
        try:
            patch.validate_action({'op': 'add'})
        except InvalidAction as e:
            self.assertEqual(e.args[0], u'"add" op has to specify path')

        # op lacking value
        try:
            patch.validate_action({'op': 'add', 'path': 'yolo'})
        except InvalidAction as e:
            self.assertEqual(e.args[0], u'"add" op has to specify value')

        # empty value is allowed
        try:
            patch.validate_action({'op': 'add', 'path': 'yolo', 'value': ''})
        except InvalidAction as e:
            self.assertEqual(e.args[0], u'"add" op has to specify value')

    def test_dispatch_action(self):
        """dispatch_action calls specified actions"""
        patch = ApiPatch()

        mock_target = MockObject(13)

        def action_a(request, target, value):
            self.assertEqual(request, 'request')
            self.assertEqual(target, mock_target)
            return {'a': value * 2, 'b': 111}
        patch.replace('abc', action_a)

        def action_b(request, target, value):
            self.assertEqual(request, 'request')
            self.assertEqual(target, mock_target)
            return {'b': value * 10}
        patch.replace('abc', action_b)

        def action_fail(request, target, value):
            self.fail("unrequired action was called")

        patch.add('c', action_fail)
        patch.remove('c', action_fail)
        patch.replace('c', action_fail)

        patch_dict = {
            'id': 123
        }

        patch.dispatch_action(patch_dict, 'request', mock_target, {
            'op': 'replace',
            'path': 'abc',
            'value': 5,
        })

        self.assertEqual(len(patch_dict), 3)
        self.assertEqual(patch_dict['id'], 123)
        self.assertEqual(patch_dict['a'], 10)
        self.assertEqual(patch_dict['b'], 50)

    def test_dispatch(self):
        """dispatch calls actions and returns response"""
        patch = ApiPatch()

        def action_error(request, target, value):
            if value == '404':
                raise Http404()
            if value == 'perm':
                raise PermissionDenied("yo ain't doing that!")
        patch.replace('error', action_error)

        def action_mutate(request, target, value):
            return {'value': value * 2}
        patch.replace('mutate', action_mutate)

        # dispatch requires list as an argument
        response = patch.dispatch(MockRequest({}), {})
        self.assertEqual(response.status_code, 400)

        self.assertEqual(
            response.data['detail'],
            "PATCH request should be list of operations")

        # valid dispatch
        response = patch.dispatch(MockRequest([
            {'op': 'replace', 'path': 'mutate', 'value': 2},
            {'op': 'replace', 'path': 'mutate', 'value': 6},
            {'op': 'replace', 'path': 'mutate', 'value': 7},
        ]), MockObject(13))

        self.assertEqual(response.status_code, 200)

        self.assertEqual(len(response.data['detail']), 3)
        self.assertEqual(response.data['detail'][0], 'ok')
        self.assertEqual(response.data['detail'][1], 'ok')
        self.assertEqual(response.data['detail'][2], 'ok')
        self.assertEqual(response.data['id'], 13)
        self.assertEqual(response.data['value'], 14)

        # invalid action in dispatch
        response = patch.dispatch(MockRequest([
            {'op': 'replace', 'path': 'mutate', 'value': 2},
            {'op': 'replace', 'path': 'mutate', 'value': 6},
            {'op': 'replace'},
            {'op': 'replace', 'path': 'mutate', 'value': 7},
        ]), MockObject(13))

        self.assertEqual(response.status_code, 400)

        self.assertEqual(len(response.data['detail']), 3)
        self.assertEqual(response.data['detail'][0], 'ok')
        self.assertEqual(response.data['detail'][1], 'ok')
        self.assertEqual(
            response.data['detail'][2], '"replace" op has to specify path')
        self.assertEqual(response.data['id'], 13)
        self.assertEqual(response.data['value'], 12)

        # action in dispatch raised 404
        response = patch.dispatch(MockRequest([
            {'op': 'replace', 'path': 'mutate', 'value': 2},
            {'op': 'replace', 'path': 'error', 'value': '404'},
            {'op': 'replace', 'path': 'mutate', 'value': 6},
            {'op': 'replace', 'path': 'mutate', 'value': 7},
        ]), MockObject(13))

        self.assertEqual(response.status_code, 400)

        self.assertEqual(len(response.data['detail']), 2)
        self.assertEqual(response.data['detail'][0], 'ok')
        self.assertEqual(
            response.data['detail'][1], "NOT FOUND")
        self.assertEqual(response.data['id'], 13)
        self.assertEqual(response.data['value'], 4)

        # action in dispatch raised perm denied
        response = patch.dispatch(MockRequest([
            {'op': 'replace', 'path': 'mutate', 'value': 2},
            {'op': 'replace', 'path': 'mutate', 'value': 6},
            {'op': 'replace', 'path': 'mutate', 'value': 9},
            {'op': 'replace', 'path': 'error', 'value': 'perm'},
        ]), MockObject(13))

        self.assertEqual(response.status_code, 400)

        self.assertEqual(len(response.data['detail']), 4)
        self.assertEqual(response.data['detail'][0], 'ok')
        self.assertEqual(response.data['detail'][1], 'ok')
        self.assertEqual(response.data['detail'][2], 'ok')
        self.assertEqual(
            response.data['detail'][3], "yo ain't doing that!")
        self.assertEqual(response.data['id'], 13)
        self.assertEqual(response.data['value'], 18)