import collections
import json
from unittest import mock

import marshmallow_jsonapi
import pytest
from marshmallow_jsonapi import fields

from flask_jsonapi import api
from flask_jsonapi import filters_schema
from flask_jsonapi import resources

JSONAPI_HEADERS = {'content-type': 'application/vnd.api+json', 'accept': 'application/vnd.api+json'}


@pytest.fixture
def example_schema():
    class ExmapleSchema(marshmallow_jsonapi.Schema):
        id = fields.UUID(required=True)
        body = fields.Str()

        class Meta:
            type_ = 'example'
            self_view_many = 'example_list'
            self_view = 'example_detail'
            self_view_kwargs = {'example_id': '<id>'}
            strict = True
    return ExmapleSchema


@pytest.fixture
def example_model():
    ExampleModel = collections.namedtuple('ExampleModel', 'id body')
    return ExampleModel


def test_integration_get_list(app, example_schema, example_model):
    class ExampleListView(resources.ResourceList):
        schema = example_schema

        def read_many(self, filters, pagination):
            return [
                example_model(id='f60717a3-7dc2-4f1a-bdf4-f2804c3127a4', body='heheh'),
                example_model(id='f60717a3-7dc2-4f1a-bdf4-f2804c3127a5', body='hihi'),
            ]

    application_api = api.Api(app)
    application_api.route(ExampleListView, 'example_list', '/examples/')
    response = app.test_client().get(
        '/examples/',
        headers=JSONAPI_HEADERS
    )
    result = json.loads(response.data.decode())
    assert response.status_code == 200
    assert result == {
        'data': [
            {
                'id': 'f60717a3-7dc2-4f1a-bdf4-f2804c3127a4',
                'type': 'example',
                'attributes': {
                    'body': 'heheh'
                }
            }, {
                'id': 'f60717a3-7dc2-4f1a-bdf4-f2804c3127a5',
                'type': 'example',
                'attributes': {
                    'body': 'hihi'
                },
            }
        ],
        'jsonapi': {
            'version': '1.0'
        },
        'meta': {
            'count': 2
        }
    }


def test_integration_get_list_with_pagination(app, example_schema, example_model):
    class ExampleListView(resources.ResourceList):
        schema = example_schema

        def read_many(self, filters, pagination):
            return [
                example_model(id='f60717a3-7dc2-4f1a-bdf4-f2804c3127a4', body='heheh'),
                example_model(id='f60717a3-7dc2-4f1a-bdf4-f2804c3127a5', body='hihi'),
            ]

        def get_count(self, filters):
            return 5

    application_api = api.Api(app)
    application_api.route(ExampleListView, 'example_list', '/examples/')
    response = app.test_client().get(
        '/examples/?page[size]=2&page[number]=1',
        headers=JSONAPI_HEADERS
    )
    result = json.loads(response.data.decode())
    assert response.status_code == 200
    assert result == {
        'data': [
            {
                'type': 'example',
                'id': 'f60717a3-7dc2-4f1a-bdf4-f2804c3127a4',
                'attributes': {
                    'body': 'heheh'
                }
            },
            {
                'type': 'example',
                'id': 'f60717a3-7dc2-4f1a-bdf4-f2804c3127a5',
                'attributes': {
                    'body': 'hihi'
                }
            }
        ],
        'links': {
            'self': 'http://localhost/examples/?page[size]=2&page[number]=1',
            'first': 'http://localhost/examples/?page[size]=2&page[number]=1',
            'previous': None,
            'next': 'http://localhost/examples/?page[size]=2&page[number]=2',
            'last': 'http://localhost/examples/?page[size]=2&page[number]=3'
        },
        'meta': {
            'count': 2
        },
        'jsonapi': {
            'version': '1.0'
        }
    }


def test_integration_bad_accept_header(app, example_schema, example_model):
    class ExampleListView(resources.ResourceList):
        schema = example_schema

    application_api = api.Api(app)
    application_api.route(ExampleListView, 'example_list', '/examples/')
    response = app.test_client().get('/examples/', headers={'accept': 'text/html'})
    assert response.status_code == 406
    assert json.loads(response.data.decode()) == {
        'errors': [{
            'detail': 'Accept header must be application/vnd.api+json',
            'source': '',
            'status': 406,
            'title': 'InvalidRequestHeader'
        }],
        'jsonapi': {
            'version': '1.0'
        },
    }


def test_integration_bad_content_type_header(app, example_schema, example_model):
    class ExampleListView(resources.ResourceList):
        schema = example_schema

    application_api = api.Api(app)
    application_api.route(ExampleListView, 'example_list', '/examples/')
    response = app.test_client().post('/examples/', headers={'accept': 'application/vnd.api+json'})
    assert response.status_code == 415
    assert json.loads(response.data.decode()) == {
        'errors': [{
            'detail': 'Content-Type header must be application/vnd.api+json',
            'source': '',
            'status': 415,
            'title': 'InvalidRequestHeader'
        }],
        'jsonapi': {
            'version': '1.0'
        },
    }


def test_integration_get_filtered_list(app, example_schema, example_model):
    class ExampleFiltersSchema(filters_schema.FilterSchema):
        basic = filters_schema.FilterField()
        listed = filters_schema.ListFilterField()
        dumb_name = filters_schema.FilterField(attribute='renamed')
        integer = filters_schema.FilterField(type_=fields.Int)
        skipped_filter = filters_schema.FilterField()

    class ExampleListView(resources.ResourceList):
        schema = example_schema
        filter_schema = ExampleFiltersSchema()

        applied_filters = {}

        def read_many(self, filters, pagination):
            self.applied_filters.update(filters)
            return []

    application_api = api.Api(app)
    application_api.route(ExampleListView, 'example_list', '/examples/')
    response = app.test_client().get(
        '/examples/?filter[basic]=text&filter[listed]=first,second&filter[dumb-name]=another&filter[integer]=3',
        headers=JSONAPI_HEADERS
    )
    assert response.status_code == 200
    assert ExampleListView.applied_filters == {
        'basic': 'text',
        'listed': ['first', 'second'],
        'renamed': 'another',
        'integer': 3,
    }


def test_integration_pagination(app, example_schema):
    class ExampleListView(resources.ResourceList):
        schema = example_schema

        applied_pagination = {}

        def read_many(self, filters, pagination):
            self.applied_pagination.update(pagination)
            return []

        def get_count(self, filters):
            return 0

    application_api = api.Api(app)
    application_api.route(ExampleListView, 'example_list', '/examples/')
    response = app.test_client().get(
        '/examples/?page[size]=100&page[number]=50',
        headers=JSONAPI_HEADERS
    )
    assert response.status_code == 200