# -*- coding: utf-8 -*-
import pytest

from addons.wiki.tests.factories import WikiFactory
from api.base.settings import osf_settings
from api.base.settings.defaults import API_BASE
from api_tests import utils as test_utils
from framework.auth import core
from osf.models import Guid
from osf_tests.factories import (
    ProjectFactory,
    RegistrationFactory,
    AuthUserFactory,
    CommentFactory,
)
from rest_framework import exceptions


@pytest.fixture()
def user():
    return AuthUserFactory()


@pytest.mark.django_db
@pytest.mark.enable_implicit_clean
class NodeCommentsListMixin(object):

    @pytest.fixture()
    def user_non_contrib(self):
        return AuthUserFactory()

    @pytest.fixture()
    def project_private_dict(self):
        raise NotImplementedError

    @pytest.fixture()
    def project_public_dict(self):
        raise NotImplementedError

    @pytest.fixture()
    def registration_dict(self):
        raise NotImplementedError

    def test_return_comments(
            self, app, user, user_non_contrib,
            project_public_dict, project_private_dict,
            registration_dict):

        #   test_return_public_node_comments_logged_out_user
        res = app.get(project_public_dict['url'])
        assert res.status_code == 200
        comment_json = res.json['data']
        comment_ids = [comment['id'] for comment in comment_json]
        assert len(comment_json) == 1
        assert project_public_dict['comment']._id in comment_ids

    #   test_return_public_node_comments_logged_in_user
        res = app.get(project_public_dict['url'], auth=user_non_contrib)
        assert res.status_code == 200
        comment_json = res.json['data']
        comment_ids = [comment['id'] for comment in comment_json]
        assert len(comment_json) == 1
        assert project_public_dict['comment']._id in comment_ids

    #   test_return_private_node_comments_logged_out_user
        res = app.get(project_private_dict['url'], expect_errors=True)
        assert res.status_code == 401
        assert res.json['errors'][0]['detail'] == exceptions.NotAuthenticated.default_detail

    #   test_return_private_node_comments_logged_in_non_contributor
        res = app.get(
            project_private_dict['url'],
            auth=user_non_contrib,
            expect_errors=True)
        assert res.status_code == 401
        assert res.json['errors'][0]['detail'] == exceptions.NotAuthenticated.default_detail

    #   test_return_private_node_comments_logged_in_contributor
        res = app.get(project_private_dict['url'], auth=user.auth)
        assert res.status_code == 200
        comment_json = res.json['data']
        comment_ids = [comment['id'] for comment in comment_json]
        assert len(comment_json) == 1
        assert project_private_dict['comment']._id in comment_ids

    #   test_return_registration_comments_logged_in_contributor
        res = app.get(registration_dict['url'], auth=user.auth)
        assert res.status_code == 200
        comment_json = res.json['data']
        comment_ids = [comment['id'] for comment in comment_json]
        assert len(comment_json) == 1
        assert registration_dict['comment']._id in comment_ids

    def test_return_both_deleted_and_undeleted_comments(
            self, app, user, project_private_dict, mock_update_search=None):
        deleted_comment = CommentFactory(
            node=project_private_dict['project'], user=user,
            target=project_private_dict['comment'].target,
            is_deleted=True)
        res = app.get(project_private_dict['url'], auth=user.auth)
        assert res.status_code == 200
        comment_json = res.json['data']
        comment_ids = [comment['id'] for comment in comment_json]
        assert project_private_dict['comment']._id in comment_ids
        assert deleted_comment._id in comment_ids

    def test_node_comments_pagination(self, app, user, project_public_dict):

        #   test_node_comments_list_pagination
        url = '{}?filter[target]={}&related_counts=False'.format(
            project_public_dict['url'], project_public_dict['project']._id)
        res = app.get(url, user=user, auth=user.auth)
        assert res.status_code == 200
        assert res.json['links']['meta']['unread'] == 0

    #   test_node_comments_list_updated_pagination
        url = '{}?filter[target]={}&related_counts=False&version=2.1'.format(
            project_public_dict['url'], project_public_dict['project']._id)
        res = app.get(url, user=user, auth=user.auth)
        assert res.status_code == 200
        assert res.json['meta']['unread'] == 0


@pytest.mark.django_db
class TestNodeCommentsList(NodeCommentsListMixin):

    @pytest.fixture()
    def project_private_dict(self, user):
        project_private = ProjectFactory(is_public=False, creator=user)
        comment_private = CommentFactory(node=project_private, user=user)
        url_private = '/{}nodes/{}/comments/'.format(
            API_BASE, project_private._id)
        return {
            'project': project_private,
            'comment': comment_private,
            'url': url_private}

    @pytest.fixture()
    def project_public_dict(self, user):
        project_public = ProjectFactory(is_public=True, creator=user)
        comment_public = CommentFactory(node=project_public, user=user)
        url_public = '/{}nodes/{}/comments/'.format(
            API_BASE, project_public._id)
        return {
            'project': project_public,
            'comment': comment_public,
            'url': url_public}

    @pytest.fixture()
    def registration_dict(self, user):
        registration = RegistrationFactory(creator=user)
        comment_registration = CommentFactory(node=registration, user=user)
        url_registration = '/{}registrations/{}/comments/'.format(
            API_BASE, registration._id)
        return {
            'registration': registration,
            'comment': comment_registration,
            'url': url_registration}


@pytest.mark.django_db
class TestNodeCommentsListFiles(NodeCommentsListMixin):

    @pytest.fixture()
    def project_private_dict(self, user):
        project_private = ProjectFactory(is_public=False, creator=user)
        file_private = test_utils.create_test_file(project_private, user)
        comment_private = CommentFactory(
            node=project_private, user=user,
            target=file_private.get_guid(), page='files')
        url_private = '/{}nodes/{}/comments/'.format(
            API_BASE, project_private._id)
        return {
            'project': project_private,
            'file': file_private,
            'comment': comment_private,
            'url': url_private}

    @pytest.fixture()
    def project_public_dict(self, user):
        project_public = ProjectFactory(is_public=True, creator=user)
        file_public = test_utils.create_test_file(project_public, user)
        comment_public = CommentFactory(
            node=project_public, user=user,
            target=file_public.get_guid(),
            page='files')
        url_public = '/{}nodes/{}/comments/'.format(
            API_BASE, project_public._id)
        return {
            'project': project_public,
            'file': file_public,
            'comment': comment_public,
            'url': url_public}

    @pytest.fixture()
    def registration_dict(self, user):
        registration = RegistrationFactory(creator=user)
        file_registration = test_utils.create_test_file(registration, user)
        comment_registration = CommentFactory(
            node=registration, user=user,
            target=file_registration.get_guid(),
            page='files')
        url_registration = '/{}registrations/{}/comments/'.format(
            API_BASE, registration._id)
        return {
            'registration': registration,
            'file': file_registration,
            'comment': comment_registration,
            'url': url_registration}

    def test_comments_on_deleted_files_are_not_returned(
            self, app, user, project_private_dict):
        # Delete commented file
        osfstorage = project_private_dict['project'].get_addon('osfstorage')
        osfstorage.get_root()
        project_private_dict['file'].delete(user=user)
        res = app.get(project_private_dict['url'], auth=user.auth)
        assert res.status_code == 200
        comment_json = res.json['data']
        comment_ids = [comment['id'] for comment in comment_json]
        assert project_private_dict['comment']._id not in comment_ids


@pytest.mark.django_db
class TestNodeCommentsListWiki(NodeCommentsListMixin):

    @pytest.fixture()
    def project_private_dict(self, user):
        project_private = ProjectFactory(is_public=False, creator=user)
        wiki_page_private = WikiFactory(node=project_private, user=user, page_name='Charmander')
        comment_private = CommentFactory(
            node=project_private, user=user, target=Guid.load(
                wiki_page_private._id), page='wiki')
        url_private = '/{}nodes/{}/comments/'.format(
            API_BASE, project_private._id)
        return {
            'project': project_private,
            'wiki': wiki_page_private,
            'comment': comment_private,
            'url': url_private
        }

    @pytest.fixture()
    def project_public_dict(self, user):
        project_public = ProjectFactory(is_public=True, creator=user)
        wiki_page_public = WikiFactory(node=project_public, user=user)
        comment_public = CommentFactory(
            node=project_public, user=user,
            target=Guid.load(wiki_page_public._id),
            page='wiki')
        url_public = '/{}nodes/{}/comments/'.format(
            API_BASE, project_public._id)
        return {
            'project': project_public,
            'wiki': wiki_page_public,
            'comment': comment_public,
            'url': url_public
        }

    @pytest.fixture()
    def registration_dict(self, user):
        registration = RegistrationFactory(creator=user)
        wiki_page_registration = WikiFactory(node=registration, user=user)
        comment_registration = CommentFactory(
            node=registration, user=user,
            target=Guid.load(wiki_page_registration._id),
            page='wiki')
        url_registration = '/{}registrations/{}/comments/'.format(
            API_BASE, registration._id
        )
        return {
            'registration': registration,
            'wiki': wiki_page_registration,
            'comment': comment_registration,
            'url': url_registration
        }

    def test_comments_on_deleted_wikis_are_not_returned(self, app, user, project_private_dict, mock_update_search=None):
        # Delete wiki
        project_private_dict['project'].delete_node_wiki(
            project_private_dict['wiki'].page_name, core.Auth(user))
        res = app.get(project_private_dict['url'], auth=user.auth)
        assert res.status_code == 200
        comment_json = res.json['data']
        comment_ids = [comment['id'] for comment in comment_json]
        assert project_private_dict['comment']._id not in comment_ids


@pytest.mark.django_db
@pytest.mark.enable_implicit_clean
class NodeCommentsCreateMixin(object):

    @pytest.fixture()
    def user_read_contrib(self):
        return AuthUserFactory()

    @pytest.fixture()
    def user_non_contrib(self):
        return AuthUserFactory()

    @pytest.fixture()
    def payload(self):
        raise NotImplementedError

    @pytest.fixture()
    def project_private_comment_private(self):
        raise NotImplementedError

    @pytest.fixture()
    def project_public_comment_private(self):
        raise NotImplementedError

    @pytest.fixture()
    def project_public_comment_public(self):
        raise NotImplementedError

    @pytest.fixture()
    def project_private_comment_public(self):
        raise NotImplementedError

    def test_node_comments(
            self, app, user, user_read_contrib,
            user_non_contrib,
            project_private_comment_private,
            project_private_comment_public,
            project_public_comment_public,
            project_public_comment_private):

        #   test_private_node_private_comment_level_logged_in_admin_can_comment
        project_dict = project_private_comment_private
        res = app.post_json_api(
            project_dict['url'],
            project_dict['payload'],
            auth=user.auth)
        assert res.status_code == 201
        assert res.json['data']['attributes']['content'] == project_dict['payload']['data']['attributes']['content']

    #   test_private_node_private_comment_level_logged_in_read_contributor_can_comment
        project_dict = project_private_comment_private
        res = app.post_json_api(
            project_dict['url'],
            project_dict['payload'],
            auth=user_read_contrib.auth)
        assert res.status_code == 201
        assert res.json['data']['attributes']['content'] == project_dict['payload']['data']['attributes']['content']

    #   test_private_node_private_comment_level_non_contributor_cannot_comment
        project_dict = project_private_comment_private
        res = app.post_json_api(
            project_dict['url'],
            project_dict['payload'],
            auth=user_non_contrib.auth,
            expect_errors=True)
        assert res.status_code == 403
        assert res.json['errors'][0]['detail'] == exceptions.PermissionDenied.default_detail

    #   test_private_node_private_comment_level_logged_out_user_cannot_comment
        project_dict = project_private_comment_private
        res = app.post_json_api(
            project_dict['url'],
            project_dict['payload'],
            expect_errors=True)
        assert res.status_code == 401
        assert res.json['errors'][0]['detail'] == exceptions.NotAuthenticated.default_detail

    #   test_private_node_with_public_comment_level_admin_can_comment
        # Test admin contributors can still comment on a private project with
        # comment_level == 'public'
        project_dict = project_private_comment_public
        res = app.post_json_api(
            project_dict['url'],
            project_dict['payload'],
            auth=user.auth)
        assert res.status_code == 201
        assert res.json['data']['attributes']['content'] == project_dict['payload']['data']['attributes']['content']

    #   test_private_node_with_public_comment_level_read_only_contributor_can_comment
        # Test read-only contributors can still comment on a private project
        # with comment_level == 'public'
        project_dict = project_private_comment_public
        res = app.post_json_api(
            project_dict['url'],
            project_dict['payload'],
            auth=user_read_contrib.auth)
        assert res.status_code == 201
        assert res.json['data']['attributes']['content'] == project_dict['payload']['data']['attributes']['content']

    #   test_private_node_with_public_comment_level_non_contributor_cannot_comment
        # Test non-contributors cannot comment on a private project with
        # comment_level == 'public'
        project_dict = project_private_comment_public
        res = app.post_json_api(
            project_dict['url'],
            project_dict['payload'],
            auth=user_non_contrib.auth,
            expect_errors=True)
        assert res.status_code == 403

    #   test_private_node_with_public_comment_level_logged_out_user_cannot_comment
        # Test logged out users cannot comment on a private project with
        # comment_level == 'public'
        project_dict = project_private_comment_public
        res = app.post_json_api(
            project_dict['url'],
            project_dict['payload'],
            expect_errors=True)
        assert res.status_code == 401
        assert res.json['errors'][0]['detail'] == exceptions.NotAuthenticated.default_detail

    #   test_public_project_with_public_comment_level_admin_can_comment
        # Test admin contributor can still comment on a public project when it
        # is configured so any logged-in user can comment (comment_level ==
        # 'public')
        project_dict = project_public_comment_public
        res = app.post_json_api(
            project_dict['url'],
            project_dict['payload'],
            auth=user.auth)
        assert res.status_code == 201
        assert res.json['data']['attributes']['content'] == project_dict['payload']['data']['attributes']['content']

    #   test_public_project_with_public_comment_level_read_only_contributor_can_comment
        # Test read-only contributor can still comment on a public project when
        # it is configured so any logged-in user can comment (comment_level ==
        # 'public')
        project_dict = project_public_comment_public
        res = app.post_json_api(
            project_dict['url'],
            project_dict['payload'],
            auth=user_read_contrib.auth)
        assert res.status_code == 201
        assert res.json['data']['attributes']['content'] == project_dict['payload']['data']['attributes']['content']

    #   test_public_project_with_public_comment_level_non_contributor_can_comment
        # Test non-contributors can comment on a public project when it is
        # configured so any logged-in user can comment (comment_level ==
        # 'public')
        project_dict = project_public_comment_public
        res = app.post_json_api(
            project_dict['url'],
            project_dict['payload'],
            auth=user_non_contrib.auth)
        assert res.status_code == 201
        assert res.json['data']['attributes']['content'] == project_dict['payload']['data']['attributes']['content']

    #   test_public_project_with_public_comment_level_logged_out_user_cannot_comment
        # Test logged out users cannot comment on a public project when it is
        # configured so any logged-in user can comment (comment_level ==
        # 'public')
        project_dict = project_public_comment_public
        res = app.post_json_api(
            project_dict['url'],
            project_dict['payload'],
            expect_errors=True)
        assert res.status_code == 401
        assert res.json['errors'][0]['detail'] == exceptions.NotAuthenticated.default_detail

    #   test_public_node_private_comment_level_admin_can_comment
        # Test only contributors can comment on a public project when it is
        # configured so only contributors can comment (comment_level ==
        # 'private')
        project_dict = project_public_comment_private
        res = app.post_json_api(
            project_dict['url'],
            project_dict['payload'],
            auth=user.auth)
        assert res.status_code == 201
        assert res.json['data']['attributes']['content'] == project_dict['payload']['data']['attributes']['content']

    #   test_public_node_private_comment_level_read_only_contributor_can_comment
        project_dict = project_public_comment_private
        res = app.post_json_api(
            project_dict['url'],
            project_dict['payload'],
            auth=user_read_contrib.auth)
        assert res.status_code == 201
        assert res.json['data']['attributes']['content'] == project_dict['payload']['data']['attributes']['content']

    #   test_public_node_private_comment_level_non_contributor_cannot_comment
        project_dict = project_public_comment_private
        res = app.post_json_api(
            project_dict['url'],
            project_dict['payload'],
            auth=user_non_contrib.auth,
            expect_errors=True)
        assert res.status_code == 403
        assert res.json['errors'][0]['detail'] == exceptions.PermissionDenied.default_detail

    #   test_public_node_private_comment_level_logged_out_user_cannot_comment
        project_dict = project_public_comment_private
        res = app.post_json_api(
            project_dict['url'],
            project_dict['payload'],
            expect_errors=True)
        assert res.status_code == 401
        assert res.json['errors'][0]['detail'] == exceptions.NotAuthenticated.default_detail


@pytest.mark.django_db
class TestNodeCommentCreate(NodeCommentsCreateMixin):

    @pytest.fixture()
    def payload(self):
        def make_payload(target_id):
            return {
                'data': {
                    'type': 'comments',
                    'attributes': {
                        'content': 'This is a comment'
                    },
                    'relationships': {
                        'target': {
                            'data': {
                                'type': 'nodes',
                                'id': target_id
                            }
                        }
                    }
                }
            }
        return make_payload

    @pytest.fixture()
    def project_private_comment_private(
            self, user, user_read_contrib, payload):
        project_private = ProjectFactory(
            is_public=False, creator=user, comment_level='private')
        project_private.add_contributor(
            user_read_contrib, permissions=['read'])
        project_private.save()
        url_private = '/{}nodes/{}/comments/'.format(
            API_BASE, project_private._id)
        payload_private = payload(project_private._id)
        return {
            'project': project_private,
            'url': url_private,
            'payload': payload_private}

    @pytest.fixture()
    def project_public_comment_private(self, user, user_read_contrib, payload):
        project_public = ProjectFactory(
            is_public=True, creator=user, comment_level='private')
        project_public.add_contributor(user_read_contrib, permissions=['read'])
        project_public.save()
        url_public = '/{}nodes/{}/comments/'.format(
            API_BASE, project_public._id)
        payload_public = payload(project_public._id)
        return {
            'project': project_public,
            'url': url_public,
            'payload': payload_public}

    @pytest.fixture()
    def project_public_comment_public(self, user, user_read_contrib, payload):
        """ Public project configured so that any logged-in user can comment."""
        project_public = ProjectFactory(is_public=True, creator=user)
        project_public.add_contributor(user_read_contrib, permissions=['read'])
        project_public.save()
        url_public = '/{}nodes/{}/comments/'.format(
            API_BASE, project_public._id)
        payload_public = payload(project_public._id)
        return {
            'project': project_public,
            'url': url_public,
            'payload': payload_public}

    @pytest.fixture()
    def project_private_comment_public(self, user, user_read_contrib, payload):
        project_private = ProjectFactory(is_public=False, creator=user)
        project_private.add_contributor(
            user_read_contrib, permissions=['read'])
        project_private.save()
        url_private = '/{}nodes/{}/comments/'.format(
            API_BASE, project_private._id)
        payload_private = payload(project_private._id)
        return {
            'project': project_private,
            'url': url_private,
            'payload': payload_private}

    def test_create_comment_errors(
            self, app, user, payload,
            project_private_comment_private):

        #   test_create_comment_invalid_data
        project_dict = project_private_comment_private
        res = app.post_json_api(
            project_dict['url'],
            'Incorrect data',
            auth=user.auth,
            expect_errors=True)
        assert res.status_code == 400

    #   test_create_comment_no_relationships
        project_dict = project_private_comment_private
        payload_req = {
            'data': {
                'type': 'comments',
                'attributes': {
                    'content': '4:44'
                }
            }
        }
        res = app.post_json_api(
            project_dict['url'],
            payload_req,
            auth=user.auth,
            expect_errors=True)
        assert res.status_code == 400
        assert res.json['errors'][0]['detail'] == 'Request must include /data/relationships.'
        assert res.json['errors'][0]['source']['pointer'] == '/data/relationships'

    #   test_create_comment_empty_relationships
        project_dict = project_private_comment_private
        payload_req = {
            'data': {
                'type': 'comments',
                'attributes': {
                    'content': 'Center for Closed Logic'
                },
                'relationships': {}
            }
        }
        res = app.post_json_api(
            project_dict['url'],
            payload_req,
            auth=user.auth,
            expect_errors=True)
        assert res.status_code == 400
        assert res.json['errors'][0]['detail'] == 'Request must include /data/relationships.'
        assert res.json['errors'][0]['source']['pointer'] == '/data/relationships'

    #   test_create_comment_relationship_is_a_list
        project_dict = project_private_comment_private
        payload_req = {
            'data': {
                'type': 'comments',
                'attributes': {
                    'content': 'This is a comment'
                },
                'relationships': [{'id': project_dict['project']._id}]
            }
        }
        res = app.post_json_api(
            project_dict['url'],
            payload_req,
            auth=user.auth,