import typing

from django.db import transaction
from rest_framework import status
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import APIException
from rest_framework.filters import SearchFilter
from rest_framework.response import Response

from apps.authentication.api.v1.api import GeneralOTPViewSet
from apps.authorization import models as authorize_models
from apps.core.api import BaseViewSet
from apps.core.mixins.search_mixin import DynamicSearchMixin
from apps.core.mixins.soft_delete_mixin import SoftDeleteMixin
from apps.tag import exceptions as tag_exceptions
from apps.tag import models as tag_models
from apps.tag.models import TagBatch
from apps.tag.services.tag_distribution_services import TagDistributionService
from apps.tag.services.tag_services import TagService
from common.helpers import get_organization_by_user
from common.liara_tools import upload_to_liara
from .serializers import (
    TagSerializer,
    TagAssignmentSerializer,
    AllocatedTagsSerializer, TagBatchSerializer, TagDistributionSerializer, TagDistributionBatchSerializer
)


class TagViewSet(BaseViewSet, TagService, SoftDeleteMixin, DynamicSearchMixin, viewsets.ModelViewSet):
    """ Tag View Set """
    queryset = tag_models.Tag.objects.all()
    serializer_class = TagSerializer
    filter_backends = [SearchFilter]
    search_fields = [
        'serial',
        'tag_code',
        'organization__name',
        'organization__type__key',
        'livestock_tag__herd__rancher__ranching_farm',
        'livestock_tag__herd__rancher__first_name',
        'livestock_tag__herd__rancher__last_name',
        'livestock_tag__herd__rancher__mobile',
        'livestock_tag__herd__rancher__national_code',
    ]

    def list(self, request, *args, **kwargs):
        """
        list of tags with filter & search
        """
        param = self.request.query_params  # noqa
        queryset = self.get_queryset(visibility_by_org_scope=True).order_by('-create_date')

        if 'status' in param.keys():
            queryset = queryset.filter(status=param.get('status', None))

        # filter queryset
        queryset = self.filter_query(queryset)

        page = self.paginate_queryset(queryset)
        if page is not None:  # noqa
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)
        return Response(self.serializer_class(queryset).data)

    @transaction.atomic
    def create(self, request: object, *args: list, **kwargs: dict) -> typing.Any:
        """ Create tag for livestocks """  # noqa

        org = get_organization_by_user(request.user)  # noqa
        serial_start_range, serial_end_range = request.data.pop('serial_range')  # serial_range is like [500, 550]
        print(serial_start_range, serial_end_range)
        data = request.data.copy()

        # create tag & batch
        created_tags = self.create_tag(
            serial_start_range=serial_start_range,
            serial_end_range=serial_end_range,
            data=data,
            org=org
        )

        serializer = self.serializer_class(created_tags, many=True)
        return Response(serializer.data, status.HTTP_201_CREATED)

    def update(self, request, pk=None, *args, **kwargs):
        """ update tag for livestocks """  # noqa

        org = get_organization_by_user(request.user)  # noqa
        serial_start_range, serial_end_range = request.data.pop('serial_range')  # serial_range is like [500, 550]
        data = request.data.copy()

        # create tag & batch
        created_tags = self.update_batch_tag(
            serial_start_range=serial_start_range,
            serial_end_range=serial_end_range,
            data=data,
            org=org,
            batch_id=int(pk)
        )

        serializer = self.serializer_class(created_tags, many=True)
        return Response(serializer.data, status.HTTP_201_CREATED)

    @action(
        methods=['get'],
        detail=True,
        url_path='tag_detail',
        url_name='tag_detail',
        name='tag_detail',
    )
    def get_tag_detail(self, request, pk=None):
        """
        show detail of a tag
        """
        response = self.tag_detail(by_id=pk)
        return Response(response)

    @action(
        methods=['get'],
        detail=True,
        url_name='tags_by_batch',
        url_path='tags_by_batch',
        name='tags_by_batch',
    )
    def get_tags_by_batch_id(self, request, pk=None):
        """
        get tags by batch id
        """
        tags = self.queryset.filter(tags__id=pk)

        page = self.paginate_queryset(tags)
        if page is not None:  # noqa
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)
        return Response(self.serializer_class(tags).data)

    @action(
        methods=['get'],
        detail=False,
        url_path='tag_dashboard',
        url_name='tag_dashboard',
        name='tag_dashboard'
    )
    def get_tag_dashboard(self, request):
        """
        dashboard of tags page
        """

        response = self.tag_dashboard()
        return Response(response)

    @action(
        methods=['get'],
        detail=False,
        url_path='ownership_code',
        url_name='ownership_code',
        name='ownership_code'
    )
    @transaction.atomic
    def ownership_code(self, request) -> typing.Any:
        """ just show ownership code of organization """
        try:
            ownership_code = authorize_models.UserRelations.objects.select_related('organization').get(
                user=request.user
            )
            return Response(ownership_code.organization.additional_data, status.HTTP_200_OK)
        except APIException as e:
            return Response(e, status.HTTP_204_NO_CONTENT)


class TagAssignmentViewSet(BaseViewSet, SoftDeleteMixin, DynamicSearchMixin, viewsets.ModelViewSet):
    """ assignment of tags """
    queryset = tag_models.TagAssignment.objects.all()
    user_relations_queryset = authorize_models.UserRelations.objects.all()
    serializer_class = TagAssignmentSerializer

    @transaction.atomic
    def create(self, request: object, *args: list, **kwargs: dict) -> typing.Any:
        """ assign tags to organizations """
        response = {}
        serializer = self.serializer_class(data=request.data)
        if serializer.is_valid(raise_exception=True):
            tag_assignment = serializer.save()

        # get assigner organization code
        assigner_organization_code = self.user_relations_queryset.get(
            user=request.user).organization.type.code  # noqa
        tag_assignment.serial_sender_part = assigner_organization_code  # noqa
        tag_assignment.save()

        # get tags by species number like: 2 tags of species code 4
        tags_to_allocate = request.data['allocated_tags']
        for tag in tags_to_allocate:
            tags = tag_models.Tag.objects.filter(
                species_code=tag['species_code'],
                status='F'
            ).order_by('id')[:tag['number']]

            # check species number
            if tags.count() < tag['number']:
                raise tag_exceptions.SpeciesNumberCheckException()

            # create assignment
            for tag_to_allocate in tags:
                tag_models.AllocatedTags(
                    tag=tag_to_allocate,
                    assignment=tag_assignment,
                    status='W',
                    species_code=tag['species_code']
                ).save()  # noqa
                tag_to_allocate.status = ' '  # change tag status from free to waiting
                tag_to_allocate.save()

        return Response(serializer.data, status.HTTP_201_CREATED)

    @action(
        methods=['post', ],
        detail=True,
        url_name='assign_document',
        url_path='assign_document',
        name='assign_document'
    )
    @transaction.atomic
    def assign_document(self, request, pk=None):
        """ set document for tag assignment """

        # get tag assignment object & set document url
        assignment = self.queryset.get(id=pk)

        # upload document file to liara storage
        document = request.FILES.get('document')
        document_url = upload_to_liara(
            document,
            f'tag_assignment_document_{assignment.serial}.{str(document).split(".")[1]}'
        )
        assignment.document = document_url
        assignment.status = 'E'
        assignment.save()
        serializer = self.serializer_class(assignment)
        return Response(serializer.data, status=status.HTTP_200_OK)

    @action(
        methods=['post'],
        detail=True,
        url_path='remove_tags_by_group',
        url_name='remove_tags_by_group',
        name='remove_tags_by_group'
    )
    def remove_assigned_tags_by_group(self, request, pk=None):
        """ remove assigned tags """

        tag_assignment = self.queryset.get(id=pk)
        for species in request.data['species_group']:
            allocated_tags = tag_assignment.assigned_allocated_tags.filter(species_code=species)
            for allocate in allocated_tags:
                # change status of tag from allocated to free
                allocate.tag.status = 'F'
                allocate.tag.save()
            allocated_tags.delete()

        return Response(status.HTTP_200_OK)

    @action(
        methods=['post'],
        detail=True,
        url_name='remove_tags_by_id',
        url_path='remove_tags_by_id',
        name='remove_tags_by_id'
    )
    def remove_assigned_tags_by_id(self, request, pk=None):
        allocated_tag = tag_models.AllocatedTags.objects.filter(id__in=request.data['allocated_tag_ids'])
        for obj in allocated_tag:
            obj.tag.status = 'F'
            obj.tag.save()
        allocated_tag.delete()
        return Response(status.HTTP_200_OK)

    @action(
        methods=['post'],
        detail=False,
        url_path='otp_verification',
        url_name='otp_verification',
        name='otp_verification'
    )
    def otp_verification(self, request):
        """ OTP verification for organization """

        if request.data['type'] == 'send':
            otp_response = GeneralOTPViewSet().send_otp(request)
            if otp_response.status_code == 200:
                return Response(otp_response.status_code, status=status.HTTP_200_OK)
            else:
                return Response(otp_response.status_code, status=status.HTTP_403_FORBIDDEN)
        if request.data['type'] == 'check':
            check_response = GeneralOTPViewSet().check_otp(request)
            if check_response.status_code == 200:
                return Response(check_response.status_code, status=status.HTTP_200_OK)
            else:
                return Response(check_response.status_code, status=status.HTTP_403_FORBIDDEN)


class AllocatedTagsViewSet(SoftDeleteMixin, viewsets.ModelViewSet):
    queryset = tag_models.AllocatedTags.objects.all()
    serializer_class = AllocatedTagsSerializer


class TagBatchViewSet(BaseViewSet, SoftDeleteMixin, DynamicSearchMixin, viewsets.ModelViewSet):
    queryset = TagBatch.objects.all()
    serializer_class = TagBatchSerializer
    filter_backends = [SearchFilter]
    search_fields = [
        "organization__name"
        "request_number"
        "tag__tag_code"
        "species_code"
    ]

    def list(self, request, *args, **kwargs):
        """
        list of tag batches
        """

        queryset = self.get_queryset(visibility_by_org_scope=True).order_by('-create_date')

        # filter queryset
        queryset = self.filter_query(self.filter_queryset(queryset))

        page = self.paginate_queryset(queryset)
        if page is not None:  # noqa
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)
        return Response(self.serializer_class(queryset).data)

    def destroy(self, request, pk=None, *args, **kwargs):
        """
        soft delete batch with tag items
        """

        batch = self.get_object()
        batch.soft_delete()
        for tag in batch.tag.all():
            tag.soft_delete()

        return Response(status=status.HTTP_200_OK)


class TagDistributionViewSet(
    BaseViewSet,
    SoftDeleteMixin,
    DynamicSearchMixin,
    viewsets.ModelViewSet,
    TagDistributionService
):
    queryset = tag_models.TagDistribution.objects.all()
    serializer_class = TagDistributionSerializer
    filter_backends = [SearchFilter]
    search_fields = [
        'batch__batch_identity',
        'tag__tag_code',
        'assigner_org__name',
        'assigned_org__name',
        'species_code',
    ]

    def list(self, request, *args, **kwargs):
        """
        list of tag distributions
        """
        queryset = self.get_queryset(visibility_by_org_scope=True).order_by('-create_date')

        queryset = self.filter_queryset(self.filter_query(queryset))

        page = self.paginate_queryset(queryset)
        if page is not None:  # noqa
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)
        return Response(self.serializer_class(queryset).data)

    def create(self, request, *args, **kwargs):
        """
        create tag distributions with batch or without batch in random
        """
        org = get_organization_by_user(request.user)
        data = request.data.copy()

        distribution_data = self.create_distribution(
            org=org,
            data=data
        )

        serializer = self.serializer_class(distribution_data.get('tag_distributions'), many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

    @action(
        methods=['get'],
        detail=True,
        url_name='dist_by_batch',
        url_path='dist_by_batch',
        name='dist_by_batch'
    )
    def get_dist_by_batch(self, request, pk=None):
        """
        get distributions by batch
        """
        pass


class TagDistributionBatchViewSet(
    BaseViewSet,
    SoftDeleteMixin,
    DynamicSearchMixin,
    viewsets.ModelViewSet,
    TagDistributionService
):
    queryset = tag_models.TagDistributionBatch.objects.all()
    serializer_class = TagDistributionBatchSerializer
    filter_backends = [SearchFilter]
    search_filter = [
        'dist_batch_identity',
        'assigner_org__name',
        'assigned_org__name',
        'total_tag_count',
        'is_closed',
    ]

    def list(self, request, *args, **kwargs):
        """
        list of tag distribution batches
        """

        queryset = self.get_queryset(visibility_by_org_scope=True).order_by('-create_date')

        queryset = self.filter_query(self.filter_queryset(queryset))

        page = self.paginate_queryset(queryset)
        if page is not None:  # noqa
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)
        return Response(self.serializer_class(queryset).data)
