import configparser
import shutil
import os
from django.shortcuts import render
from rest_framework import viewsets
from rest_framework.views import APIView
from app.entities import models

from datetime import datetime
from datetime import timedelta

from rest_framework.response import Response
from rest_framework import status, views

from rest_framework.decorators import action
from django.http import Http404
from django.db.models import Q
import requests
from django.conf import settings
from rest_framework.exceptions import ValidationError
from django.db import transaction, IntegrityError, connection
from app.applicationlayer.utils import (QuerySetHelper,
                                        status_message_response,
                                        send_broadcast_message)
from app.businesslayer.changerequest import change_request

from app.applicationlayer.cms.utils_cr import (number_generator,
                                               send_mail_requestor,
                                               next_approver_email,
                                               crhistory_save,
                                               send_mail_vendor,
                                               get_max_batchno,
                                               generate_template_id,
                                               crhistory_create_save,
                                               entity_log_bulk,
                                               reminder_trigger_save,
                                               overdue_trigger_save,
                                               reset_autoemail_tables)

from app.entities import enums
from app.applicationlayer.utils import model_to_dict
import json
from django.shortcuts import get_object_or_404
from django.db.models import Min
from app.applicationlayer.utils import (CustomPagination,
                                        status_message_response)

from rest_framework.exceptions import ParseError
from app.businesslayer.changerequest.change_request import (
    form_add_edit_delete
)

from app.applicationlayer.cms.form.approver.serializers import ChangeRequestFormApproversSerializer
from app.applicationlayer.cms.form.stakeholder.serializers import ChangeRequestFormStakeHoldersSerializer
from app.applicationlayer.cms.form.details.serializers import ChangeRequestFormDetailsSerializer
from app.applicationlayer.cms.form.attachment.serializers import (
    ChangeRequestFormAttachmentsFileUploadSerializer,
    ChangeRequestFormAttachmentsSerializer)
from app.applicationlayer.cms.form.header.serializers import (
    ChangeRequestFormHeaderSerializer,
    ChangeRequestFormHeaderSerializerList)
from app.applicationlayer.cms.form.header.table_filters import HeaderFilterSet

from rest_framework.filters import SearchFilter, OrderingFilter
from django_filters import rest_framework as filters
from django_filters.rest_framework import DjangoFilterBackend
import json
from app.applicationlayer.utils import main_threading

from django.core.files.base import ContentFile
from django.conf import settings
from io import BytesIO
from django.http import HttpResponse
from xhtml2pdf import pisa


config = configparser.ConfigParser()
config_file = os.path.join('./', 'env.ini')
config.read(config_file)

APPROVER_MESSAGE = settings.APPROVER_MESSAGE
REQUESTOR_MESSAGE = settings.REQUESTOR_MESSAGE
REQUESTOR_REJECT_MESSAGE = settings.REQUESTOR_REJECT_MESSAGE
VENDOR_ACKNOWLEDGE_MESSAGE = settings.VENDOR_ACKNOWLEDGE_MESSAGE
REQUESTOR_ACKNOWLEDGE_MESSAGE = settings.REQUESTOR_ACKNOWLEDGE_MESSAGE
REQUESTOR_COMPLETION_MESSAGE = settings.REQUESTOR_COMPLETION_MESSAGE
VENDOR_ACCEPTANCE_MESSAGE = settings.VENDOR_ACCEPTANCE_MESSAGE
VENDOR_REJECT_MESSAGE = settings.VENDOR_REJECT_MESSAGE


class ChangeRequestFormsViewset(viewsets.ModelViewSet):

    queryset = models.ChangeRequestFormHeader.objects.select_related(
        'requested_by_user', 'requested_by_department', 'template_no'
    ).all()
    # queryset = models.ChangeRequestFormHeader.objects.all()
    serializer_class = ChangeRequestFormHeaderSerializer
    pagination_class = CustomPagination
    lookup_field = 'form_code'
    filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter)
    filterset_class = HeaderFilterSet
    ordering_fields = (
        'form_code',
        'requested_to_template_id',
        'requested_to_user__name',
        'requested_to_department__company__name',
        'requested_to_department__name',
        'requested_by_user__name',
        'status',
        'requested_to_priority',
        'created',
        'requested_to_target_date'
    )

    search_fields = (
        "form_code", "status", "requested_to_template_id", "requested_by_user__name",
        "requested_by_user__code", "requested_by_department__name",
        "requested_to_company__name", "requested_to_department__name",
        "requested_to_user__name", "requested_to_template_name",
        "requested_to_objective", "requested_to_priority", "description"
    )
# comment
    def list(self, request, *args, **kwargs):
        
        self.serializer_class = ChangeRequestFormHeaderSerializerList
        id_number = self.request.user.code
        self.queryset = change_request.list_by_user(id_number)

        self.queryset = change_request.filter_base(
            self.queryset,
            request.query_params.get('company_requested_to'),
            request.query_params.get('department_requested_to'),
            request.query_params.get('date_modified_from'),
            request.query_params.get('date_modified_to'),
            request.query_params.get('date_required_from'),
            request.query_params.get('date_required_to'),
            request.query_params.get('form_type'),
        )

        queryset = self.filter_queryset(self.queryset)
        
        page = self.paginate_queryset(queryset)

        if page is not None:
            serializer = self.get_serializer(page, many=True)

            message = status_message_response(
                200,
                'success',
                'List of Change Request Form found',
                serializer.data
            )

            return self.get_paginated_response(message)

        serializer = self.get_serializer(self.queryset, many=True)

        return Response(
            serializer.data,
            status=status.HTTP_200_OK
        )
        
    @action(detail=False,
            methods=['get'],
            url_path='dashboard',
            name="Dashboard Summary")
    def dashboard_view(self, request):
        id_number = self.request.user.code
        
        self.queryset = change_request.list_by_user(id_number)

        self.queryset = change_request.filter_base(
            self.queryset,
            request.query_params.get('company_requested_to'),
            request.query_params.get('department_requested_to'),
            request.query_params.get('date_modified_from'),
            request.query_params.get('date_modified_to'),
            request.query_params.get('date_required_from'),
            request.query_params.get('date_required_to'),
            request.query_params.get('form_type'),
        )
        
        pending = self.queryset.filter(
            status__iexact='Pending'
        ).count()

        approved = self.queryset.filter(
            status__iexact='Approved'
        ).count()

        rejected = self.queryset.filter(
            status__iexact='Rejected'
        ).count()

        cancelled = self.queryset.filter(
            status__iexact='Cancelled'
        ).count()
        
        completed = self.queryset.filter(
            status__iexact='Completed & Accepted'  #Completed
        ).count()
        
        high = self.queryset.filter(
            requested_to_priority='High'
        ).count()

        normal = self.queryset.filter(
            requested_to_priority='Normal'
        ).count()

        self.queryset = change_request.list_by_user_without_dept(
            id_number)

        awaiting_filtered = change_request.filter_awaiting(self.queryset, id_number)

        awaiting = awaiting_filtered.count()
                
        overdue_filtered = change_request.filter_overdue(self.queryset)

        overdue = overdue_filtered.count()

        # server side computations
        # status_total = pending + approved + rejected + cancelled + completed
        
        # close = rejected + cancelled + completed

        # opened = pending + approved

        # open_percent = round((opened/status_total) * 100)

        # close_percent = round((close/status_total) * 100)

        # priority_total = high + normal

        # high_percent = round((high/priority_total) * 100)
        # normal_percent = round((normal/priority_total) * 100)
        
        message = {
            'account_no': id_number,
            'pending': pending,
            'approved': approved,
            'rejected': rejected,
            'cancelled': cancelled,
            'completed': completed,
            'high': high,
            'normal': normal,
            'awaiting': awaiting,
            'overdue': overdue,
            # 'open_percent': open_percent,
            # 'close_percent': close_percent,
            # 'high_percent': high_percent,
            # 'normal_percent': normal_percent,
            'code': 200,
            'status': 'success',
            'message': 'Dashboard Summary'
        }

        return Response(message, status=status.HTTP_200_OK)        

    @action(detail=False,
            methods=['get'],
            url_path='status',
            name="Dashboard Summary Status")
    def list_by_status_view(self, request):
        self.serializer_class = ChangeRequestFormHeaderSerializerList
        id_number = self.request.user.code

        self.queryset = change_request.list_by_user(id_number)
        
        self.queryset = change_request.filter_status(
            self.queryset,
            request.query_params.get('status')
        )

        self.queryset = change_request.filter_base(
            self.queryset,
            request.query_params.get('company_requested_to'),
            request.query_params.get('department_requested_to'),
            request.query_params.get('date_modified_from'),
            request.query_params.get('date_modified_to'),
            request.query_params.get('date_required_from'),
            request.query_params.get('date_required_to'),
            request.query_params.get('form_type'),
        )

        if not request.query_params.get('status') == 'completed_accepted':
            
            self.queryset = self.filter_queryset(self.queryset)

        page = self.paginate_queryset(self.queryset)
        
        if page is not None:
            serializer = self.get_serializer(page, many=True)

            message = status_message_response(
                200,
                'success',
                'List of Change Request Form by status found',
                serializer.data
            )

            return self.get_paginated_response(message)

        serializer = self.get_serializer(self.queryset, many=True)

        return Response(
            serializer.data,
            status=status.HTTP_200_OK
        )
    
    @action(detail=False,
            methods=['get'],
            url_path='overdue',
            name="Dashboard Summary Overdue")
    def list_by_overdue_view(self, request):
        self.serializer_class = ChangeRequestFormHeaderSerializerList

        id_number = self.request.user.code

        self.queryset = change_request.list_by_user_without_dept(
            id_number)

        self.queryset = change_request.filter_overdue(self.queryset)
        
        self.queryset = change_request.filter_base(
            self.queryset,
            request.query_params.get('company_requested_to'),
            request.query_params.get('department_requested_to'),
            request.query_params.get('date_modified_from'),
            request.query_params.get('date_modified_to'),
            request.query_params.get('date_required_from'),
            request.query_params.get('date_required_to'),
            request.query_params.get('form_type'),
        )

        queryset = self.filter_queryset(self.queryset)
        
        page = self.paginate_queryset(queryset)

        if page is not None:
            serializer = self.get_serializer(page, many=True)

            message = status_message_response(
                200,
                'success',
                'List of Change Request Form by overdue found',
                serializer.data
            )

            return self.get_paginated_response(message)

        serializer = self.get_serializer(self.queryset, many=True)

        return Response(
            serializer.data,
            status=status.HTTP_200_OK
        )

    @action(detail=False,
            methods=['get'],
            url_path='awaiting',
            name="Dashboard Summary Awaiting")
    def list_by_awaiting_view(self, request):
        self.serializer_class = ChangeRequestFormHeaderSerializerList

        id_number = self.request.user.code

        self.queryset = change_request.list_by_user_without_dept(id_number)

        self.queryset = change_request.filter_awaiting(
            self.queryset, id_number)
        
        self.queryset = change_request.filter_base(
            self.queryset,
            request.query_params.get('company_requested_to'),
            request.query_params.get('department_requested_to'),
            request.query_params.get('date_modified_from'),
            request.query_params.get('date_modified_to'),
            request.query_params.get('date_required_from'),
            request.query_params.get('date_required_to'),
            request.query_params.get('form_type'),
        )
        
        queryset = self.filter_queryset(self.queryset)

        page = self.paginate_queryset(queryset)
        
        if page is not None:
            serializer = self.get_serializer(page, many=True)

            message = status_message_response(
                200,
                'success',
                'List of Change Request Form found',
                serializer.data
            )

            return self.get_paginated_response(message)

        serializer = self.get_serializer(self.queryset, many=True)

        return Response(
            serializer.data,
            status=status.HTTP_200_OK
        )

    def retrieve(self, request, *args, **kwargs):
        id_number = self.request.user.code
        form_code = kwargs['form_code']

        instance = self.get_object()
        serializer = self.get_serializer(instance)

        # queryset = self.get_serializer_class().setup_eager_loading(self.queryset)

        # models.Notification.objects.filter(
        #     account_no=id_number,
        #     form_code=form_code,
        #     is_read=False).update(is_read=True)
        
        # ROOM = id_number
        # SENDER = id_number
        
        # notif = send_broadcast_message(
        #     ROOM,
        #     SENDER,
        #     'UPDATE NOTIFICATIONS'
        # )

        # main_threading(1, notif)

        return Response(serializer.data)
        
    @transaction.atomic()
    def destroy(self, request, *args, **kwargs):
        try:
            form_code = self.kwargs['form_code']

            current_status = models.ChangeRequestFormHeader.objects.get(
                form_code=form_code
            )
            
            if current_status.status.lower() == 'draft':
                existing_transaction = models.ChangeRequestHistory.objects.filter(
                    form_code=form_code
                )
                
                if not existing_transaction.count() > 0:
                    enum_approver = enums.LogEntitiesEnum.ChangeRequestFormApprovers.value
                    enum_stake = enums.LogEntitiesEnum.ChangeRequestFormStakeHolders.value
                    enum_attach = enums.LogEntitiesEnum.ChangeRequestFormAttachments.value
                    enum_detail = enums.LogEntitiesEnum.ChangeRequestFormDetails.value
                    enum_header = enums.LogEntitiesEnum.ChangeRequestFormHeader.value

                    approver = models.ChangeRequestFormApprovers.objects.filter(
                        form_code=form_code
                    )

                    if approver.count() > 0:
                        entity_log_bulk(
                            approver, enum_approver,
                            models.ChangeRequestFormApprovers
                        )
                    
                    stake = models.ChangeRequestFormStakeHolders.objects.filter(
                        form_code=form_code
                    )

                    if stake.count() > 0:
                        entity_log_bulk(
                            stake, enum_stake,
                            models.ChangeRequestFormStakeHolders
                        )

                    attachment = models.ChangeRequestFormAttachments.objects.filter(
                        form_code=form_code
                    )

                    if attachment.count() > 0:
                        entity_log_bulk(
                            attachment, enum_attach,
                            models.ChangeRequestFormAttachments
                        )

                    details = models.ChangeRequestFormDetails.objects.filter(
                        form_code=form_code
                    )

                    if details.count() > 0:
                        entity_log_bulk(
                            details, enum_detail,
                            models.ChangeRequestFormDetails
                        )

                    header = models.ChangeRequestFormHeader.objects.filter(
                        form_code=form_code
                    )

                    if header.count() > 0:
                        entity_log_bulk(
                            header, enum_header,
                            models.ChangeRequestFormHeader
                        )
                    
                    return Response({"message": "Deleted"},
                    status=status.HTTP_200_OK)

                else:
                    
                    models.ChangeRequestFormHeader.objects.filter(
                        form_code=form_code
                    ).update(is_active=False)

                    return Response(
                        {"message": "Draft change request sucessfully archived"},
                         status=status.HTTP_200_OK)

            elif current_status.status.lower() == 'cancelled':
                
                models.ChangeRequestFormHeader.objects.filter(
                    form_code=form_code
                ).update(is_active=False)
                
                return Response(
                    {"message": "Change Request successfully archived!"},
                    status=status.HTTP_200_OK)
            else:
                return Response(
                    {"message": "Cannot archive this change request due to ongoing transaction"},
                        status=status.HTTP_400_BAD_REQUEST)

        except Exception as e:
            return Response(e,
                            status=status.HTTP_500_INTERNAL_SERVER_ERROR)


    @action(
        methods=['GET'], detail=True,
        url_path='form-download', url_name='form-download'
    )
    def FormDownload(self, request, form_code=None):
        args = ['CHANGE_REQUEST_TEMPLATE.html']

        my_folder = os.path.join(settings.MEDIA_ROOT, f'cr/{request.user.code}')

        attch = models.ChangeRequestFormAttachments.objects.filter(
            form_code=str(form_code)
        ).values('file_upload__url')
        attch_list = [data['file_upload__url'].split('/')[1] for data in attch]
        # print(attch.query)
        # print(attch)
        # print(attch_list)

        if os.path.isdir(my_folder):
            shutil.rmtree(my_folder)

        user_folder = os.mkdir(my_folder)

        for data in args:
            F = open(os.path.join(settings.EMAIL_TEMPLATES_ROOT, data), 'r')
            result = BytesIO()
            pdf = pisa.pisaDocument(F, result)
            updated_file = ContentFile(result.getvalue())
            updated_file.name = f"{data}.pdf"

            completeName = os.path.join(
                os.path.join(settings.MEDIA_ROOT, f'cr/{request.user.code}'),
                updated_file.name
            )


            file1 = open(completeName, "w")
            file1.close()

        # for file_name in attch_list:
        #     full_file_name = os.path.join(my_folder, file_name)
        #     if os.path.isfile(full_file_name):
        #         shutil.copy(full_file_name, dest)
        print(attch)

        for file_name in attch:
            print(file_name['file_upload__url'])
            a = os.path.join(settings.MEDIA_ROOT, file_name['file_upload__url'])
            # full_file_name = os.path.join(my_folder, file_name['file_upload__url'])
            # print(full_file_name)
            if os.path.isfile(a):
                print('ddd')
                print(a)
                # print(type(a))
                b = open(a, 'r')
                shutil.copy(b, my_folder)
            else:
                print('xxx')

        test = shutil.make_archive(my_folder + 'archive', 'zip', my_folder, ".")

        response = HttpResponse(open(test, 'rb'), content_type='application/zip')
        response['Content-Disposition'] = 'attachment; filename=change request.zip'
        return response
    
    @transaction.atomic()
    @action(
        methods=['PATCH'], detail=True,
        url_path='re_submit', url_name='re_submit'
    )
    def re_submit(self, request, *args, **kwargs):
        
        # generate batchno history
        batchno = get_max_batchno("batch")
        
        # partial update
        partial = kwargs.pop('partial', True)
        instance = self.get_object()
        
        form_code = kwargs['form_code']

        # get prefix from template header
        frm = models.ChangeRequestFormHeader.objects.get(
            form_code=form_code)
        tmp_prefix = models.ChangeRequestTemplateHeader.objects.get(
            template_no=frm.template_no.template_no
        )

        data_update = {
            "status": 'Draft',
            "created": datetime.now(),
            "requested_to_template_id": tmp_prefix.requested_to_template_id
        }
        
        serializer = self.get_serializer(instance,
                                         data=data_update,
                                         partial=partial)
        
        serializer.is_valid(raise_exception=True)
        old_instance = model_to_dict(instance)
        self.perform_update(serializer)

        form_header = get_object_or_404(models.ChangeRequestFormHeader,
                                        pk=instance.id)

        new_instance = model_to_dict(form_header)

        # save history in form header
        crhistory_save(
            batchno,
            enums.CREnum.RESUBMIT.value,
            enums.CREnum.UPDATE.value,
            enums.CREntitiesEnum.CR_FRM_HEADER.value,
            form_code,
            old_instance,
            new_instance
        )
        
        approver_data = []
        # get all approvers of form
        approvers = models.ChangeRequestFormApprovers.objects.filter(
                    form_code=form_code)
        
        for approver in approvers:
            
            approver_add = {
                'id': approver.id,
                'action': None,
                'remarks': None,
                'date_sent': None,
                'action_date': None,
                'created': datetime.now(),
                'is_action': False
            }
            
            approver_data.append(approver_add)

        change_request.form_add_edit_delete(
            approver_data,
            models.ChangeRequestFormApprovers,
            enums.CREntitiesEnum.CR_FRM_APPROVER.value,
            ChangeRequestFormApproversSerializer,
            partial,
            self,
            form_code,
            batchno,
            enums.CREnum.RESUBMIT.value
        )

        message = status_message_response(
            200, 'success',
            'Change request form successfully re submitted',
            serializer.data
        )

        return Response(message, status=status.HTTP_200_OK)

    @transaction.atomic()
    @action(
        methods=['PATCH'], detail=False,
        url_path='actions', url_name='actions'
    )
    def actions(self, request, *args, **kwargs):
        
        current_user = self.request.user.code
        action_body = request.data
        id = action_body.get('id', False)
        form_code = action_body.get('form_code', False)
        action = action_body.get('action', False)
        remarks = action_body.get('remarks', False)
        move_to_level = action_body.get('move_to_level', False)
        
        # generate batchno history
        batchno = get_max_batchno("batch")
        
        action_data = {
            'id': id,
            'action': action,
            'remarks': remarks,
            'action_date': datetime.now()
        }
        
        approver_instance = models.ChangeRequestFormApprovers.objects.get(
            pk=id
        )
        
        serializer = ChangeRequestFormApproversSerializer(
                                        approver_instance,
                                        data=action_data,
                                        partial=True)
        
        serializer.is_valid(raise_exception=True)
        old_instance = model_to_dict(approver_instance)
        
        self.perform_update(serializer)
        new_instance = serializer.data
        
        crhistory_save(
            batchno,
            enums.CREnum.ACTION.value,
            enums.CREnum.UPDATE.value,
            enums.CREntitiesEnum.CR_FRM_APPROVER.value,
            form_code,
            old_instance,
            new_instance
        )
        
        change_request.cr_routing_actions(new_instance, current_user, move_to_level)
        # ---------------- removed code
        message = status_message_response(
            200, 'success',
            'Action performed',
            serializer.data
        )

        return Response(message, status=status.HTTP_200_OK)

    @transaction.atomic
    @action(
        methods=['PATCH'], detail=True,
        url_path='save', url_name='save'
    )
    def save(self, request, *args, **kwargs):
        
        partial = kwargs.pop('partial', True)
        instance = self.get_object()

        form_code = kwargs['form_code']
        form_data = request.data

        # generate batchno history
        batchno = get_max_batchno("batch")

        data_update = {
            "status": 'Draft'
        }

        data = {**form_data, **data_update}

        # update form header
        serializer = self.get_serializer(instance,
                                         data=data,
                                         partial=partial)
        
        serializer.is_valid(raise_exception=True)
        old_instance = model_to_dict(instance)

        requestor = serializer.validated_data['requested_by_user'].code
        poc = serializer.validated_data['requested_to_user'].code
        if requestor == poc:
            message = {
                'code': 400,
                'status': 'failed',
                'message': 'Cannot assign same user on point of contact and requestor',
            }
            return Response(message, status=status.HTTP_400_BAD_REQUEST)
        else:
            self.perform_update(serializer)

        new_instance = serializer.data
        
        # save history in form header
        crhistory_save(
            batchno,
            enums.CREnum.SAVE.value,
            enums.CREnum.UPDATE.value,
            enums.CREntitiesEnum.CR_FRM_HEADER.value,
            form_code,
            old_instance,
            new_instance
        )

        change_request.form_add_edit_delete(
                form_data['frm_approvers'],
                models.ChangeRequestFormApprovers,
                enums.CREntitiesEnum.CR_FRM_APPROVER.value,
                ChangeRequestFormApproversSerializer,
                partial,
                self,
                form_code,
                batchno,
                enums.CREnum.SAVE.value
        )
        
        change_request.form_add_edit_delete(
                form_data['frm_stakes'],
                models.ChangeRequestFormStakeHolders,
                enums.CREntitiesEnum.CR_FRM_STAKE.value,
                ChangeRequestFormStakeHoldersSerializer,
                partial,
                self,
                form_code,
                batchno,
                enums.CREnum.SAVE.value
        )

        change_request.form_add_edit_delete(
                form_data['frm_attachments'],
                models.ChangeRequestFormAttachments,
                enums.CREntitiesEnum.CR_FRM_ATTACHMENT.value,
                ChangeRequestFormAttachmentsSerializer,
                partial,
                self,
                form_code,
                batchno,
                enums.CREnum.SAVE.value
        )

        change_request.form_add_edit_delete(
                form_data['frm_details'],
                models.ChangeRequestFormDetails,
                enums.CREntitiesEnum.CR_FRM_DETAIL.value,
                ChangeRequestFormDetailsSerializer,
                partial,
                self,
                form_code,
                batchno,
                enums.CREnum.SAVE.value
        )
        
        serializer = self.get_serializer(instance)

        message = status_message_response(
            200, 'success',
            'Change Request successfully saved',
            serializer.data
        )

        return Response(message, status=status.HTTP_200_OK)
        
    @transaction.atomic
    @action(
        methods=['PATCH'], detail=True,
        url_path='submit', url_name='submit'
    )
    def submit(self, request, *args, **kwargs):
        
        partial = kwargs.pop('partial', True)
        instance = self.get_object()

        form_code = kwargs['form_code']
        form_data = request.data

        # generate batchno history
        batchno = get_max_batchno("batch")
        
        # generate requested template id
        requested_to_template_id = generate_template_id(
                                    form_data['requested_to_template_id'],
                                    form_code,
                                    form_data['template_no'])
        
        data_update = {
            "status": 'Pending',
            "created": datetime.now(),
            "requested_to_template_id": requested_to_template_id
        }

        data = {**form_data, **data_update}

        # update form header
        serializer = self.get_serializer(instance,
                                         data=data,
                                         partial=partial)

        serializer.is_valid(raise_exception=True)
        old_instance = model_to_dict(instance)

        requestor = serializer.validated_data['requested_by_user'].code
        poc = serializer.validated_data['requested_to_user'].code
        if requestor == poc:
            message = {
                'code': 400,
                'status': 'failed',
                'message': 'Cannot assign same user on point of contact and requestor',
            }
            return Response(message, status=status.HTTP_400_BAD_REQUEST)
        else:
            self.perform_update(serializer)
        
        form_header = get_object_or_404(models.ChangeRequestFormHeader,
                                        pk=instance.id)
        
        new_instance = model_to_dict(form_header)
        
        # save history in form header
        crhistory_create_save(
                    batchno,
                    enums.CREnum.SUBMIT.value,
                    enums.CREnum.UPDATE.value,
                    enums.CREntitiesEnum.CR_FRM_HEADER.value,
                    form_code,
                    old_instance,
                    new_instance,
                    requested_to_template_id,
                    form_data['template_no'])

        change_request.form_add_edit_delete(
                form_data['frm_approvers'],
                models.ChangeRequestFormApprovers,
                enums.CREntitiesEnum.CR_FRM_APPROVER.value,
                ChangeRequestFormApproversSerializer,
                partial,
                self,
                form_code,
                batchno,
                enums.CREnum.SUBMIT.value
        )
        
        change_request.form_add_edit_delete(
                form_data['frm_stakes'],
                models.ChangeRequestFormStakeHolders,
                enums.CREntitiesEnum.CR_FRM_STAKE.value,
                ChangeRequestFormStakeHoldersSerializer,
                partial,
                self,
                form_code,
                batchno,
                enums.CREnum.SUBMIT.value
            )

        change_request.form_add_edit_delete(
                form_data['frm_attachments'],
                models.ChangeRequestFormAttachments,
                enums.CREntitiesEnum.CR_FRM_ATTACHMENT.value,
                ChangeRequestFormAttachmentsSerializer,
                partial,
                self,
                form_code,
                batchno,
                enums.CREnum.SUBMIT.value
            )

        change_request.form_add_edit_delete(
                form_data['frm_details'],
                models.ChangeRequestFormDetails,
                enums.CREntitiesEnum.CR_FRM_DETAIL.value,
                ChangeRequestFormDetailsSerializer,
                partial,
                self,
                form_code,
                batchno,
                enums.CREnum.SUBMIT.value
            )
        
        min_level = models.ChangeRequestFormApprovers.objects.filter(
            form_code=form_code
        ).aggregate(Min('level'))
        
        min_level = min_level.get('level__min')
        
        next_approver_email(form_code, min_level)

        date_now = datetime.now()

        # update next approver details
        models.ChangeRequestFormApprovers.objects.filter(
            Q(form_code=form_code) & Q(level=int(min_level))
            ).update(
                date_sent=datetime.now(),
                is_action=True
            )
        
        # save details for overdue and auto cancellation
        overdue_trigger_save(form_code)

        # save details for reminder for first approver
        reminder_trigger_save(form_code, date_now,
        date_now)
        
        serializer = self.get_serializer(instance)

        message = status_message_response(
            200, 'success',
            'Change Request successfully submitted',
            serializer.data
        )

        return Response(message, status=status.HTTP_200_OK)

    @action(
        methods=['GET'], detail=False,
        url_path='archived', url_name='archived'
    )
    def archived(self, request, *args, **kwargs):
        
        self.serializer_class = ChangeRequestFormHeaderSerializerList
        id_number = self.request.user.code
        self.queryset = change_request.list_by_user_archived(id_number)

        self.queryset = change_request.filter_base(
            self.queryset,
            request.query_params.get('company_requested_to'),
            request.query_params.get('department_requested_to'),
            request.query_params.get('date_modified_from'),
            request.query_params.get('date_modified_to'),
            request.query_params.get('date_required_from'),
            request.query_params.get('date_required_to'),
            request.query_params.get('form_type'),
        )

        queryset = self.filter_queryset(self.queryset)
        
        page = self.paginate_queryset(queryset)

        if page is not None:
            serializer = self.get_serializer(page, many=True)

            message = status_message_response(
                200,
                'success',
                'List of Archived Change Request Form found',
                serializer.data
            )

            return self.get_paginated_response(message)

        serializer = self.get_serializer(self.queryset, many=True)

        return Response(
            serializer.data,
            status=status.HTTP_200_OK
        )


    @action(
        methods=['GET'], detail=True,
        url_path='history', url_name='history'
    )
    def history(self, request, *args, **kwargs):

        instance = self.get_object()
        list_his = []

        history = models.ChangeRequestHistory.objects.filter(
            Q(form_code=instance) &
            Q(main_action='ACTION') &
            Q(entity='CR_FRM_APPROVER')
        ).values().order_by('-created')
        
        for data in history:
            data = data['toValue'].replace("'", '"').replace('None', '""').replace('True', '""').replace('False', '""')
            convert = json.loads(data)

            history_dict = {
                "name": convert['user']['name'],
                "department" : convert['department']['name'],
                "company" : convert['company']['name'],
                "date_sent" : convert['date_sent'],
                "delegation" : convert['delegation'],
                "action" : convert['action'],
                "remarks" : convert['remarks'],
                "created": convert['action_date']
            }

            list_his.append(history_dict)

        return Response(
            {"results":list_his},
            status=status.HTTP_200_OK
        )

    #restore archived CR # comment
    def partial_update(self, request, *args, **kwargs):
        try:
            kwargs['partial'] = True
            form_code = self.kwargs['form_code']
            instance = models.ChangeRequestFormHeader.objects.filter(
                form_code=form_code
            )
            instance.update(is_active=True)
            message = status_message_response(
                200, 'success',
                'Archived Change Request restored',
                ''
            )

            return Response(message, status=status.HTTP_200_OK)

        except Exception as e:
            message = status_message_response(
                            500, 'failed',
                            'Request was not able to process' + str(e), []
                        )
            return Response(message,
                            status=status.HTTP_500_INTERNAL_SERVER_ERROR)


    @transaction.atomic()
    @action(
        methods=['PATCH'], detail=True,
        url_path='file_upload', url_name='file_upload'
    )
    def file_upload(self, request, *args, **kwargs):
        try:
            partial = kwargs.pop('partial', True)

            attach_body = request.data

            form_code = kwargs['form_code']
            id_number = self.request.user.code

            # generate batchno history
            batchno = get_max_batchno("batch")

            change_request.attachment_add_edit_delete(
                attach_body['attachments'],
                models.ChangeRequestFormAttachments,
                enums.CREntitiesEnum.CR_FRM_ATTACHMENT.value,
                ChangeRequestFormAttachmentsFileUploadSerializer,
                partial,
                self,
                form_code,
                batchno,
                enums.CREnum.FILE_UPLOAD.value,
                id_number
            )

            self.queryset = models.ChangeRequestFormAttachments.objects.filter(
                form_code=form_code
            )

            self.serializer_class = ChangeRequestFormAttachmentsFileUploadSerializer

            serializer = self.get_serializer(self.queryset, many=True)
            
            message = status_message_response(
                200, 'success',
                'Attachments successfully updated!',
                serializer.data
            )

            return Response(message, status=status.HTTP_200_OK)

        except Exception as e:
            message = status_message_response(
                            500, 'failed',
                            'Request was not able to process' + str(e), []
                        )
            return Response(message,
                            status=status.HTTP_500_INTERNAL_SERVER_ERROR)


class ChangeRequestFormPost(APIView):

    @transaction.atomic()
    def post(self, request):
        form_header = request.data

        try:
            data_list_approver = []
            data_list_stake = []
            data_list_attach = []
            data_list_detail = []

            form_header_data = {
                'requested_to_template_name': form_header['requested_to_template_name'],
                'requested_to_template_id': form_header['requested_to_template_id'],
                'requested_to_objective': form_header['requested_to_objective'],
                'requested_to_target_date': form_header['requested_to_target_date'],
                'requested_to_priority': form_header['requested_to_priority'],
                'description': form_header['description'],
                'status': form_header['status'],
                'company_desc': form_header['company_desc'],
                'department_desc': form_header['department_desc'],
                'requested_desc': form_header['requested_desc'],
                'requested_by_department': form_header['requested_by_department'],
                'requested_by_user': form_header['requested_by_user'],
                'requested_to_company': form_header['requested_to_company'],
                'requested_to_department': form_header['requested_to_department'],
                'requested_to_user': form_header['requested_to_user'],
                'template_no': form_header['template_no']
            }

            # sp1 = transaction.savepoint()  # nothing will save to db

            serializer = ChangeRequestFormHeaderSerializer(
                data=form_header_data)
            
            if serializer.is_valid(raise_exception=True):
                requestor = serializer.validated_data['requested_by_user'].code
                poc = serializer.validated_data['requested_to_user'].code
                if requestor == poc:
                    message = {
                        'code': 400,
                        'status': 'failed',
                        'message': 'Cannot assign same user on point of contact and requestor',
                    }
                    return Response(message, status=status.HTTP_400_BAD_REQUEST)
                else:
                    serializer.save()
                
            frm_id = serializer.data['form_code']

            # create form approvers
            frm_approvers = form_header['frm_approvers']
            counter = 0
            for frm_approver in frm_approvers:
                frm_approver['form_code'] = frm_id
                counter = counter + 1
                data_list_approver.append(frm_approver)

            serializerApprover = ChangeRequestFormApproversSerializer(
                data=data_list_approver, many=True)

            if serializerApprover.is_valid(raise_exception=True):
                serializerApprover.save()
            
            # create form stakes
            if form_header['frm_stakes']:
                frm_stakes = form_header['frm_stakes']
                for frm_stake in frm_stakes:
                    frm_stake['form_code'] = frm_id
                    data_list_stake.append(frm_stake)

                serializerStake = ChangeRequestFormStakeHoldersSerializer(
                    data=data_list_stake, many=True)

                if serializerStake.is_valid(raise_exception=True):
                    serializerStake.save()
            
            # create form attachments
            if form_header['frm_attachments']:
                frm_attachments = form_header['frm_attachments']
                for frm_attachment in frm_attachments:
                    frm_attachment['form_code'] = frm_id
                    data_list_attach.append(frm_attachment)

                serializerAttach = ChangeRequestFormAttachmentsSerializer(
                    data=data_list_attach, many=True)

                if serializerAttach.is_valid(raise_exception=True):
                    serializerAttach.save()

            # create form details
            if form_header['frm_details']:
                frm_details = form_header['frm_details']
                for frm_detail in frm_details:
                    frm_detail['form_code'] = frm_id
                    data_list_detail.append(frm_detail)

                serializerDetail = ChangeRequestFormDetailsSerializer(
                    data=data_list_detail, many=True)
                
                if serializerDetail.is_valid(raise_exception=True):
                    serializerDetail.save()

            # if status is pending send initial email and
            # generate template id
            if serializer.data['status'].lower() == 'pending':
                template_no = serializer.data['template_no']
                CR_Prefix = serializer.data['requested_to_template_id']
                
                # generate batchno history
                batchno = get_max_batchno("batch")

                # generate requested template id
                requested_to_template_id = generate_template_id(
                    CR_Prefix, frm_id, template_no
                )

                #save to history for request template id tracking
                crhistory_create_save(
                    batchno,
                    enums.CREnum.CREATE.value,
                    enums.CREnum.ADD.value,
                    enums.CREntitiesEnum.CR_FRM_HEADER.value,
                    frm_id,
                    '',
                    serializer.data,
                    requested_to_template_id,
                    template_no)
                
                min_level = models.ChangeRequestFormApprovers.objects.filter(
                    form_code=frm_id
                ).aggregate(Min('level'))
                
                min_level = min_level.get('level__min')
                
                next_approver_email(frm_id, min_level)

                date_now = datetime.now()

                # update next approver details
                models.ChangeRequestFormApprovers.objects.filter(
                    Q(form_code=frm_id) & Q(level=int(min_level))
                    ).update(
                        date_sent=date_now,
                        is_action=True
                    )

                # save details for overdue and auto cancellation
                overdue_trigger_save(frm_id)

                # save details for reminder for first approver
                reminder_trigger_save(frm_id, date_now,
                date_now)
            
            else:
                
                template_no = serializer.data['template_no']

                CR_Prefix = models.ChangeRequestTemplateHeader.objects.filter(
                    template_no=template_no
                ).values('requested_to_template_id')

                CR_Prefix = CR_Prefix.values_list('requested_to_template_id', flat=True)[0]

                models.ChangeRequestFormHeader.objects.filter(
                    form_code=frm_id
                ).update(requested_to_template_id=CR_Prefix)

                models.ChangeRequestFormApprovers.objects.filter(
                    form_code=frm_id
                    ).update(
                        is_action=False,
                        action=None,
                        remarks=None,
                        action_date=None,
                        date_sent=None
                )

            message = {
                'code': 201,
                'status': 'success',
                'message': 'Form Details successfully saved!',
                'results': serializer.data
            }

            return Response(message, status=status.HTTP_201_CREATED)

        except ValidationError as e:
            # transaction.savepoint_rollback(sp1)
            message = {
                'code': 400,
                'status': 'failed',
                'message': str(e),
            }
            return Response(message, status=status.HTTP_400_BAD_REQUEST)

        except Exception as e:
            # transaction.savepoint_rollback(sp1)
            message = {
                'code': 500,
                'status': 'failed',
                'message': 'Request was not able to process' + str(e),
            }
            return Response(message,
                            status=status.HTTP_500_INTERNAL_SERVER_ERROR)

