from rest_framework import viewsets
from apps.tag import models as tag_models
from rest_framework import status
from rest_framework.response import Response
from .serializers import (
    TagSerializer,
    TagAssignmentSerializer,
    AllocatedTagsSerializer
)
from rest_framework.decorators import action
from django.db import transaction
from rest_framework.exceptions import APIException
from apps.tag import permissions as tag_permissions
from apps.authorization import models as authorize_models
from apps.authentication.api.v1.api import GeneralOTPViewSet
from apps.tag.tools import tag_code_serial_scanning
from apps.tag import exceptions as tag_exceptions
from common.helpers import detect_file_extension
from common.liara_tools import upload_to_liara
from django.db import IntegrityError
import typing


def trash(queryset, pk):
    """ sent object to trash """
    obj = queryset.get(id=pk)
    obj.trash = True
    obj.save()


def delete(queryset, pk):
    """ full delete object """
    obj = queryset.get(id=pk)
    obj.delete()


class TagViewSet(viewsets.ModelViewSet):
    """ Tag View Set """
    queryset = tag_models.Tag.objects.all()
    serializer_class = TagSerializer

    @transaction.atomic
    def create(self, request: object, *args: list, **kwargs: dict) -> typing.Any:
        """ Create tag for livestocks """  # noqa
        tag_objects = []
        serial_start_range, serial_end_range = request.data['serial_range']  # serial_range is like [500, 550]
        while serial_start_range <= serial_end_range:
            try:
                request.data.update({'serial': str(serial_start_range)})
                serializer = self.serializer_class(data=request.data)
                if serializer.is_valid(raise_exception=True):
                    tag_objects.append(serializer.save())
            except IntegrityError as e:  # if tag exists before
                if 'unique constraint' in e.args[0]:
                    return Response("tag exists", status.HTTP_406_NOT_ACCEPTABLE)
            serial_start_range += 1

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

    @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)

    @action(
        methods=['put'],
        detail=True,
        url_path='trash',
        url_name='trash',
        name='trash',
    )
    @transaction.atomic
    def trash(self, request, pk=None):
        """ Sent Tag to trash """
        try:
            trash(self.queryset, pk)
        except APIException as e:
            return Response(e, status.HTTP_204_NO_CONTENT)

    @action(
        methods=['post'],
        detail=True,
        url_name='delete',
        url_path='delete',
        name='delete'
    )
    @transaction.atomic
    def delete(self, request, pk=None):
        """ Full delete of Tag object """
        try:
            delete(self.queryset, pk)
            return Response(status=status.HTTP_200_OK)
        except APIException as e:
            return Response(e, status=status.HTTP_204_NO_CONTENT)


class TagAssignmentViewSet(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)

    @action(
        methods=['put'],
        detail=True,
        url_path='trash',
        url_name='trash',
        name='trash',
    )
    @transaction.atomic
    def trash(self, request, pk=None):
        """ Sent TagAssigment to trash """
        try:
            trash(self.queryset, pk)
        except APIException as e:
            return Response(e, status.HTTP_204_NO_CONTENT)

    @action(
        methods=['post'],
        detail=True,
        url_name='delete',
        url_path='delete',
        name='delete'
    )
    @transaction.atomic
    def delete(self, request, pk=None):
        """ Full delete of TagAssignment object """
        try:
            delete(self.queryset, pk)
            return Response(status=status.HTTP_200_OK)
        except APIException as e:
            return Response(e, status=status.HTTP_204_NO_CONTENT)


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

    @action(
        methods=['put'],
        detail=True,
        url_path='trash',
        url_name='trash',
        name='trash',
    )
    @transaction.atomic
    def trash(self, request, pk=None):
        """ Sent AllocatedTag to trash """
        try:
            trash(self.queryset, pk)
        except APIException as e:
            return Response(e, status.HTTP_204_NO_CONTENT)

    @action(
        methods=['post'],
        detail=True,
        url_name='delete',
        url_path='delete',
        name='delete'
    )
    @transaction.atomic
    def delete(self, request, pk=None):
        """ Full delete of AllocatedTag object """
        try:
            delete(self.queryset, pk)
            return Response(status=status.HTTP_200_OK)
        except APIException as e:
            return Response(e, status=status.HTTP_204_NO_CONTENT)
