import binascii
import hashlib
import pytest

from api.base.settings.defaults import API_BASE
from django.contrib.auth.models import Permission

from osf.models import RegistrationSchema
from osf_tests.factories import (
    ProjectFactory,
    DraftRegistrationFactory,
    AuthUserFactory,
    RegistrationFactory,
)
from rest_framework import exceptions
from test_node_draft_registration_list import DraftRegistrationTestCase
from website.project.metadata.schemas import LATEST_SCHEMA_VERSION


@pytest.mark.django_db
class TestDraftRegistrationDetail(DraftRegistrationTestCase):

    @pytest.fixture()
    def schema(self):
        return RegistrationSchema.objects.get(
            name='OSF-Standard Pre-Data Collection Registration',
            schema_version=LATEST_SCHEMA_VERSION)

    @pytest.fixture()
    def draft_registration(self, user, project_public, schema):
        return DraftRegistrationFactory(
            initiator=user,
            registration_schema=schema,
            branched_from=project_public
        )

    @pytest.fixture()
    def project_other(self, user):
        return ProjectFactory(creator=user)

    @pytest.fixture()
    def url_draft_registrations(self, project_public, draft_registration):
        return '/{}nodes/{}/draft_registrations/{}/'.format(
            API_BASE, project_public._id, draft_registration._id)

    def test_admin_can_view_draft(
            self, app, user, draft_registration,
            schema, url_draft_registrations):
        res = app.get(url_draft_registrations, auth=user.auth)
        assert res.status_code == 200
        data = res.json['data']
        assert schema._id in data['relationships']['registration_schema']['links']['related']['href']
        assert data['id'] == draft_registration._id
        assert data['attributes']['registration_metadata'] == {}

    def test_cannot_view_draft(
            self, app, user_write_contrib,
            user_read_contrib, user_non_contrib,
            url_draft_registrations):

        #   test_read_only_contributor_cannot_view_draft
        res = app.get(
            url_draft_registrations,
            auth=user_read_contrib.auth,
            expect_errors=True)
        assert res.status_code == 403

    #   test_read_write_contributor_cannot_view_draft
        res = app.get(
            url_draft_registrations,
            auth=user_write_contrib.auth,
            expect_errors=True)
        assert res.status_code == 403

    #   test_logged_in_non_contributor_cannot_view_draft
        res = app.get(
            url_draft_registrations,
            auth=user_non_contrib.auth,
            expect_errors=True)
        assert res.status_code == 403

    #   test_unauthenticated_user_cannot_view_draft
        res = app.get(url_draft_registrations, expect_errors=True)
        assert res.status_code == 401

    def test_cannot_view_deleted_draft(
            self, app, user, url_draft_registrations):
        res = app.delete_json_api(url_draft_registrations, auth=user.auth)
        assert res.status_code == 204

        res = app.get(
            url_draft_registrations,
            auth=user.auth,
            expect_errors=True)
        assert res.status_code == 410

    def test_draft_must_be_branched_from_node_in_kwargs(
            self, app, user, project_other, draft_registration):
        url = '/{}nodes/{}/draft_registrations/{}/'.format(
            API_BASE, project_other._id, draft_registration._id)
        res = app.get(url, auth=user.auth, expect_errors=True)
        assert res.status_code == 400
        errors = res.json['errors'][0]
        assert errors['detail'] == 'This draft registration is not created from the given node.'

    def test_reviewer_can_see_draft_registration(
            self, app, schema, draft_registration, url_draft_registrations):
        user = AuthUserFactory()
        administer_permission = Permission.objects.get(
            codename='administer_prereg')
        user.user_permissions.add(administer_permission)
        user.save()
        res = app.get(url_draft_registrations, auth=user.auth)
        assert res.status_code == 200
        data = res.json['data']
        assert schema._id in data['relationships']['registration_schema']['links']['related']['href']
        assert data['id'] == draft_registration._id
        assert data['attributes']['registration_metadata'] == {}


@pytest.mark.django_db
class TestDraftRegistrationUpdate(DraftRegistrationTestCase):

    @pytest.fixture()
    def schema(self):
        return RegistrationSchema.objects.get(
            name='OSF-Standard Pre-Data Collection Registration',
            schema_version=LATEST_SCHEMA_VERSION)

    @pytest.fixture()
    def draft_registration(self, user, project_public, schema):
        return DraftRegistrationFactory(
            initiator=user,
            registration_schema=schema,
            branched_from=project_public
        )

    @pytest.fixture()
    def schema_prereg(self):
        return RegistrationSchema.objects.get(
            name='Prereg Challenge',
            schema_version=LATEST_SCHEMA_VERSION)

    @pytest.fixture()
    def draft_registration_prereg(self, user, project_public, schema_prereg):
        return DraftRegistrationFactory(
            initiator=user,
            registration_schema=schema_prereg,
            branched_from=project_public
        )

    @pytest.fixture()
    def metadata_registration(
            self, prereg_metadata,
            draft_registration_prereg):
        return prereg_metadata(draft_registration_prereg)

    @pytest.fixture()
    def project_other(self, user):
        return ProjectFactory(creator=user)

    @pytest.fixture()
    def url_draft_registrations(self, project_public, draft_registration):
        return '/{}nodes/{}/draft_registrations/{}/'.format(
            API_BASE, project_public._id, draft_registration._id)

    @pytest.fixture()
    def payload(self, draft_registration):
        return {
            'data': {
                'id': draft_registration._id,
                'type': 'draft_registrations',
                'attributes': {
                    'registration_metadata': {
                        'datacompletion': {
                            'value': 'No, data collection has not begun'
                        },
                        'looked': {
                            'value': 'No'
                        },
                        'comments': {
                            'value': 'This is my first registration.'
                        }
                    }
                }
            }
        }

    @pytest.fixture()
    def administer_permission(self):
        return Permission.objects.get(codename='administer_prereg')

    def test_id_required_in_payload(self, app, user, url_draft_registrations):
        payload = {
            'data': {
                'type': 'draft_registrations',
                'attributes': {
                    'registration_metadata': {}
                }
            }
        }
        res = app.put_json_api(
            url_draft_registrations, payload,
            auth=user.auth, expect_errors=True)
        assert res.status_code == 400
        errors = res.json['errors'][0]
        assert errors['source']['pointer'] == '/data/id'
        assert errors['detail'] == 'This field may not be null.'

    def test_admin_can_update_draft(
            self, app, user, schema,
            payload, url_draft_registrations):
        res = app.put_json_api(
            url_draft_registrations,
            payload, auth=user.auth)
        assert res.status_code == 200
        data = res.json['data']
        assert schema._id in data['relationships']['registration_schema']['links']['related']['href']
        assert data['attributes']['registration_metadata'] == payload['data']['attributes']['registration_metadata']

    def test_draft_must_be_branched_from_node(
            self, app, user, project_other, draft_registration, payload):
        url = '/{}nodes/{}/draft_registrations/{}/'.format(
            API_BASE, project_other._id, draft_registration._id)
        res = app.put_json_api(
            url, payload, auth=user.auth,
            expect_errors=True)
        assert res.status_code == 400
        errors = res.json['errors'][0]
        assert errors['detail'] == 'This draft registration is not created from the given node.'

    def test_cannot_update_draft(
            self, app, user_write_contrib,
            user_read_contrib, user_non_contrib,
            payload, url_draft_registrations):

        #   test_read_only_contributor_cannot_update_draft
        res = app.put_json_api(
            url_draft_registrations,
            payload,
            auth=user_read_contrib.auth,
            expect_errors=True)
        assert res.status_code == 403

    #   test_read_write_contributor_cannot_update_draft
        res = app.put_json_api(
            url_draft_registrations,
            payload,
            auth=user_write_contrib.auth,
            expect_errors=True)
        assert res.status_code == 403

    #   test_logged_in_non_contributor_cannot_update_draft
        res = app.put_json_api(
            url_draft_registrations,
            payload,
            auth=user_non_contrib.auth,
            expect_errors=True)
        assert res.status_code == 403

    #   test_unauthenticated_user_cannot_update_draft
        res = app.put_json_api(
            url_draft_registrations,
            payload, expect_errors=True)
        assert res.status_code == 401

    def test_registration_metadata_must_be_supplied(
            self, app, user, payload, url_draft_registrations):
        payload['data']['attributes'] = {}

        res = app.put_json_api(
            url_draft_registrations,
            payload, auth=user.auth,
            expect_errors=True)
        errors = res.json['errors'][0]
        assert res.status_code == 400
        assert errors['source']['pointer'] == '/data/attributes/registration_metadata'
        assert errors['detail'] == 'This field is required.'

    def test_registration_metadata_must_be_a_dictionary(
            self, app, user, payload, url_draft_registrations):
        payload['data']['attributes']['registration_metadata'] = 'Registration data'

        res = app.put_json_api(
            url_draft_registrations,
            payload, auth=user.auth,
            expect_errors=True)
        errors = res.json['errors'][0]
        assert res.status_code == 400
        assert errors['source']['pointer'] == '/data/attributes/registration_metadata'
        assert errors['detail'] == 'Expected a dictionary of items but got type "unicode".'

    def test_registration_metadata_question_values_must_be_dictionaries(
            self, app, user, payload, url_draft_registrations):
        payload['data']['attributes']['registration_metadata']['datacompletion'] = 'No, data collection has not begun'

        res = app.put_json_api(
            url_draft_registrations,
            payload, auth=user.auth,
            expect_errors=True)
        errors = res.json['errors'][0]
        assert res.status_code == 400
        assert errors['detail'] == 'For your registration your response to the \'Has data collection begun for this project?\'' \
                                   ' field is invalid, your response must be one of the provided options.'

    def test_registration_metadata_question_keys_must_be_value(
            self, app, user, payload, url_draft_registrations):
        payload['data']['attributes']['registration_metadata']['datacompletion'] = {
            'incorrect_key': 'No, data collection has not begun'}

        res = app.put_json_api(
            url_draft_registrations,
            payload, auth=user.auth,
            expect_errors=True)
        errors = res.json['errors'][0]
        assert res.status_code == 400
        assert errors['detail'] == 'For your registration your response to the \'Has data collection begun for this project?\'' \
                                   ' field is invalid, your response must be one of the provided options.'

    def test_question_in_registration_metadata_must_be_in_schema(
            self, app, user, payload, url_draft_registrations):
        payload['data']['attributes']['registration_metadata']['q11'] = {
            'value': 'No, data collection has not begun'
        }

        res = app.put_json_api(
            url_draft_registrations,
            payload, auth=user.auth,
            expect_errors=True)
        errors = res.json['errors'][0]
        assert res.status_code == 400
        assert errors['detail'] == 'For your registration the \'datacompletion\' field is extraneous and not' \
                                   ' permitted in your response.'

    def test_multiple_choice_question_value_must_match_value_in_schema(
            self, app, user, payload, url_draft_registrations):
        payload['data']['attributes']['registration_metadata']['datacompletion'] = {
            'value': 'Nope, data collection has not begun'}

        res = app.put_json_api(
            url_draft_registrations,
            payload, auth=user.auth,
            expect_errors=True)
        errors = res.json['errors'][0]
        assert res.status_code == 400