from rest_framework.request import Request

from api_fhir_r4.mapping.invoiceMapping import InvoiceTypeMapping, BillTypeMapping
from api_fhir_r4.mixins import (
    MultiIdentifierRetrieveManySerializersMixin,
    MultiIdentifierRetrieverMixin
)
from api_fhir_r4.model_retrievers import (
    CodeIdentifierModelRetriever,
    DatabaseIdentifierModelRetriever,
    UUIDIdentifierModelRetriever
)
from api_fhir_r4.multiserializer import modelViewset
from api_fhir_r4.permissions import (
    FHIRApiInvoicePermissions,
    FHIRApiBillPermissions
)
from api_fhir_r4.serializers import (
    InvoiceSerializer,
    BillSerializer
)
from api_fhir_r4.views.fhir.base import BaseMultiserializerFHIRView
from api_fhir_r4.views.filters import DateUpdatedRequestParameterFilter
from invoice.models import Bill
from invoice.models import Invoice


class InvoiceViewSet(BaseMultiserializerFHIRView,
                     modelViewset.MultiSerializerModelViewSet,
                     MultiIdentifierRetrieveManySerializersMixin, MultiIdentifierRetrieverMixin):
    retrievers = [UUIDIdentifierModelRetriever, DatabaseIdentifierModelRetriever, CodeIdentifierModelRetriever]
    lookup_field = 'identifier'

    @property
    def serializers(self):
        return {
            InvoiceSerializer:
                (self._invoice_queryset(), self._invoice_serializer_validator, (FHIRApiInvoicePermissions,)),
            BillSerializer:
                (self._bill_queryset(), self._bill_serializer_validator, (FHIRApiBillPermissions,))
        }

    @classmethod
    def _invoice_serializer_validator(cls, context):
        return cls._base_request_validator_dispatcher(
            request=context['request'],
            get_check=lambda x: cls._get_type_from_query(x) in ('invoice', None),
            post_check=lambda x: cls._get_type_from_body(x) in [item['code'] for item in
                                                                InvoiceTypeMapping.invoice_type],
            put_check=lambda x: cls._get_type_from_body(x) in [item['code'] for item in
                                                               InvoiceTypeMapping.invoice_type],
        )

    @classmethod
    def _bill_serializer_validator(cls, context):
        return cls._base_request_validator_dispatcher(
            request=context['request'],
            get_check=lambda x: cls._get_type_from_query(x) in ('bill', None),
            post_check=lambda x: cls._get_type_from_body(x) in [item['code'] for item in BillTypeMapping.invoice_type],
            put_check=lambda x: cls._get_type_from_body(x) in [item['code'] for item in BillTypeMapping.invoice_type],
        )

    @classmethod
    def _base_request_validator_dispatcher(cls, request: Request, get_check, post_check, put_check):
        if request.method == 'GET':
            return get_check(request)
        elif request.method == 'POST':
            return post_check(request)
        elif request.method == 'PUT':
            return put_check(request)
        return True

    def list(self, request, *args, **kwargs):
        identifier = request.GET.get("code")
        if identifier:
            return self.retrieve(request, *args, **{**kwargs, 'identifier': identifier})
        return super().list(request, *args, **kwargs)

    def get_queryset(self):
        return Invoice.objects

    def _invoice_queryset(self):
        queryset = Invoice.objects.filter(is_deleted=False).order_by('date_created')
        return DateUpdatedRequestParameterFilter(self.request).filter_queryset(queryset)

    def _bill_queryset(self):
        queryset = Bill.objects.filter(is_deleted=False).order_by('date_created')
        return DateUpdatedRequestParameterFilter(self.request).filter_queryset(queryset)

    @classmethod
    def _get_type_from_body(cls, request):
        try:
            return request.data['type'][0]['coding'][0]['code'].lower()
        except KeyError:
            return None

    @classmethod
    def _get_type_from_query(cls, request):
        try:
            return request.GET['resourceType'].lower()
        except KeyError:
            return None
