
# Python
import pytest
import mock

# DRF
from rest_framework import status
from rest_framework.response import Response
from rest_framework.exceptions import PermissionDenied

# AWX
from awx.api.generics import (
    ParentMixin,
    SubListCreateAttachDetachAPIView, SubListAttachDetachAPIView,
    DeleteLastUnattachLabelMixin,
    ResourceAccessList,
    ListAPIView
)
from awx.main.models import Organization, Credential


@pytest.fixture
def get_object_or_404(mocker):
    # pytest patch without return_value generates a random value, we are counting on this
    return mocker.patch('awx.api.generics.get_object_or_404')


@pytest.fixture
def get_object_or_400(mocker):
    return mocker.patch('awx.api.generics.get_object_or_400')


@pytest.fixture
def mock_response_new(mocker):
    m = mocker.patch('awx.api.generics.Response.__new__')
    m.return_value = m
    return m


@pytest.fixture
def mock_organization():
    return Organization(pk=4, name="Unsaved Org")


@pytest.fixture
def parent_relationship_factory(mocker):
    def rf(serializer_class, relationship_name, relationship_value=mocker.Mock()):
        mock_parent_relationship = mocker.MagicMock(**{'%s.add.return_value' % relationship_name: relationship_value})
        mocker.patch('awx.api.generics.ParentMixin.get_parent_object', return_value=mock_parent_relationship)

        serializer = serializer_class()
        [setattr(serializer, x, '') for x in ['relationship', 'model', 'parent_model']]
        serializer.relationship = relationship_name

        return (serializer, mock_parent_relationship)
    return rf


# TODO: Test create and associate failure (i.e. id doesn't exist, record already exists, permission denied)
# TODO: Mock and check return (Response)
class TestSubListCreateAttachDetachAPIView:
    def test_attach_validate_ok(self, mocker):
        mock_request = mocker.MagicMock(data=dict(id=1))
        serializer = SubListCreateAttachDetachAPIView()

        (sub_id, res) = serializer.attach_validate(mock_request)

        assert sub_id == 1
        assert res is None

    def test_attach_validate_invalid_type(self, mocker):
        mock_request = mocker.MagicMock(data=dict(id='foobar'))
        serializer = SubListCreateAttachDetachAPIView()

        (sub_id, res) = serializer.attach_validate(mock_request)

        assert type(res) is Response

    def test_attach_create_and_associate(self, mocker, get_object_or_400, parent_relationship_factory, mock_response_new):
        (serializer, mock_parent_relationship) = parent_relationship_factory(SubListCreateAttachDetachAPIView, 'wife')
        create_return_value = mocker.MagicMock(status_code=status.HTTP_201_CREATED)
        serializer.create = mocker.Mock(return_value=create_return_value)

        mock_request = mocker.MagicMock(data=dict())
        ret = serializer.attach(mock_request, None, None)

        assert ret == mock_response_new
        serializer.create.assert_called_with(mock_request, None, None)
        mock_parent_relationship.wife.add.assert_called_with(get_object_or_400.return_value)
        mock_response_new.assert_called_with(
            Response, create_return_value.data, status=status.HTTP_201_CREATED,
            headers={'Location': create_return_value['Location']}
        )

    def test_attach_associate_only(self, mocker, get_object_or_400, parent_relationship_factory, mock_response_new):
        (serializer, mock_parent_relationship) = parent_relationship_factory(SubListCreateAttachDetachAPIView, 'wife')
        serializer.create = mocker.Mock(return_value=mocker.MagicMock())

        mock_request = mocker.MagicMock(data=dict(id=1))
        ret = serializer.attach(mock_request, None, None)

        assert ret == mock_response_new
        serializer.create.assert_not_called()
        mock_parent_relationship.wife.add.assert_called_with(get_object_or_400.return_value)
        mock_response_new.assert_called_with(Response, status=status.HTTP_204_NO_CONTENT)

    def test_unattach_validate_ok(self, mocker):
        mock_request = mocker.MagicMock(data=dict(id=1))
        serializer = SubListCreateAttachDetachAPIView()

        (sub_id, res) = serializer.unattach_validate(mock_request)

        assert sub_id == 1
        assert res is None

    def test_unattach_validate_invalid_type(self, mocker):
        mock_request = mocker.MagicMock(data=dict(id='foobar'))
        serializer = SubListCreateAttachDetachAPIView()

        (sub_id, res) = serializer.unattach_validate(mock_request)

        assert type(res) is Response

    def test_unattach_validate_missing_id(self, mocker):
        mock_request = mocker.MagicMock(data=dict())
        serializer = SubListCreateAttachDetachAPIView()

        (sub_id, res) = serializer.unattach_validate(mock_request)

        assert sub_id is None
        assert type(res) is Response

    def test_unattach_by_id_ok(self, mocker, parent_relationship_factory, get_object_or_400):
        (serializer, mock_parent_relationship) = parent_relationship_factory(SubListCreateAttachDetachAPIView, 'wife')
        mock_request = mocker.MagicMock()
        mock_sub = mocker.MagicMock(name="object to unattach")
        get_object_or_400.return_value = mock_sub

        res = serializer.unattach_by_id(mock_request, 1)

        assert type(res) is Response
        assert res.status_code == status.HTTP_204_NO_CONTENT
        mock_parent_relationship.wife.remove.assert_called_with(mock_sub)

    def test_unattach_ok(self, mocker):
        mock_request = mocker.MagicMock()
        mock_sub_id = mocker.MagicMock()
        view = SubListCreateAttachDetachAPIView()
        view.unattach_validate = mocker.MagicMock()
        view.unattach_by_id = mocker.MagicMock()
        view.unattach_validate.return_value = (mock_sub_id, None)

        view.unattach(mock_request)

        view.unattach_validate.assert_called_with(mock_request)
        view.unattach_by_id.assert_called_with(mock_request, mock_sub_id)

    def test_unattach_invalid(self, mocker):
        mock_request = mocker.MagicMock()
        mock_res = mocker.MagicMock()
        view = SubListCreateAttachDetachAPIView()
        view.unattach_validate = mocker.MagicMock()
        view.unattach_by_id = mocker.MagicMock()
        view.unattach_validate.return_value = (None, mock_res)

        view.unattach(mock_request)

        view.unattach_validate.assert_called_with(mock_request)
        view.unattach_by_id.assert_not_called()


def test_attach_detatch_only(mocker):
    mock_request = mocker.MagicMock()
    mock_request.data = {'name': 'name for my new model'}
    view = SubListAttachDetachAPIView()
    view.model = mocker.MagicMock()
    view.model._meta = mocker.MagicMock()
    view.model._meta.verbose_name = "Foo Bar"

    resp = view.post(mock_request)

    assert 'Foo Bar' in resp.data['msg']
    assert 'field is missing' in resp.data['msg']


class TestDeleteLastUnattachLabelMixin:
    @mock.patch('__builtin__.super')
    def test_unattach_ok(self, super, mocker):
        mock_request = mocker.MagicMock()
        mock_sub_id = mocker.MagicMock()
        super.return_value = super
        super.unattach_validate = mocker.MagicMock(return_value=(mock_sub_id, None))
        super.unattach_by_id = mocker.MagicMock()

        mock_model = mocker.MagicMock()
        mock_model.objects.get.return_value = mock_model
        mock_model.is_detached.return_value = True

        view = DeleteLastUnattachLabelMixin()
        view.model = mock_model

        view.unattach(mock_request, None, None)

        super.unattach_validate.assert_called_with(mock_request)
        super.unattach_by_id.assert_called_with(mock_request, mock_sub_id)
        mock_model.is_detached.assert_called_with()
        mock_model.objects.get.assert_called_with(id=mock_sub_id)
        mock_model.delete.assert_called_with()

    @mock.patch('__builtin__.super')
    def test_unattach_fail(self, super, mocker):
        mock_request = mocker.MagicMock()
        mock_response = mocker.MagicMock()
        super.return_value = super
        super.unattach_validate = mocker.MagicMock(return_value=(None, mock_response))
        view = DeleteLastUnattachLabelMixin()

        res = view.unattach(mock_request, None, None)

        super.unattach_validate.assert_called_with(mock_request)
        assert mock_response == res


class TestParentMixin:
    def test_get_parent_object(self, mocker, get_object_or_404):
        parent_mixin = ParentMixin()
        parent_mixin.lookup_field = 'foo'
        parent_mixin.kwargs = dict(foo='bar')
        parent_mixin.parent_model = 'parent_model'
        mock_parent_mixin = mocker.MagicMock(wraps=parent_mixin)

        return_value = mock_parent_mixin.get_parent_object()

        get_object_or_404.assert_called_with(parent_mixin.parent_model, **parent_mixin.kwargs)
        assert get_object_or_404.return_value == return_value


class TestResourceAccessList:

    def mock_request(self):
        return mock.MagicMock(
            user=mock.MagicMock(
                is_anonymous=mock.MagicMock(return_value=False),
                is_superuser=False
            ), method='GET')


    def mock_view(self, parent=None):
        view = ResourceAccessList()
        view.parent_model = Organization
        view.kwargs = {'pk': 4}
        if parent:
            view.get_parent_object = lambda: parent
        return view


    def test_parent_access_check_failed(self, mocker, mock_organization):
        mock_access = mocker.MagicMock(__name__='for logger', return_value=False)
        with mocker.patch('awx.main.access.BaseAccess.can_read', mock_access):
            with pytest.raises(PermissionDenied):
                self.mock_view(parent=mock_organization).check_permissions(self.mock_request())
            mock_access.assert_called_once_with(mock_organization)


    def test_parent_access_check_worked(self, mocker, mock_organization):
        mock_access = mocker.MagicMock(__name__='for logger', return_value=True)
        with mocker.patch('awx.main.access.BaseAccess.can_read', mock_access):
            self.mock_view(parent=mock_organization).check_permissions(self.mock_request())
            mock_access.assert_called_once_with(mock_organization)


def test_related_search_reverse_FK_field():
    view = ListAPIView()
    view.model = Credential
    assert 'unifiedjobtemplates__search' in view.related_search_fields
