import datetime
import sys
import traceback

import pandas as pd
from django.db.models import Q, Sum

from npayroll.settings import logger
from payrollservice.data.response.emppaystructuredeductionsresponse import EmployeePaystructure_deductionsResponse
from payrollservice.models import EmployeePaystructure_deductions, EmployeePaystructure_details
from payrollservice.service.auditservice import AudtiService
from payrollservice.service.payrollmastersservice import PaycomponentFlagmasterService, EmployeePFService, \
    PayrollComponentService
from payrollservice.util.payrollutil import Activestatus, Advancetype, ModifyStatus, get_pf_type, payrolldeduction_val, \
    FlagRef_Type
# from userservice.models import Employee
from utilityservice.data.response.nwisefinerrorconstants import ErrorDescription, ErrorMessage
from utilityservice.data.response.nwisefinerror import NWisefinError
from utilityservice.data.response.nwisefinsuccess import NWisefinSuccess, SuccessStatus, SuccessMessage
from utilityservice.service.applicationconstants import ApplicationNamespace
from utilityservice.service.payroll_api_service import Payrollcommon_Apicall
from utilityservice.service.threadlocal import NWisefinThread
from django.utils import timezone
from hrmsmasterapiservice.hrmsmasterapi.masteranduserserviceapi import HrmsApiService

class EmployeePaystructure_deductionsService(NWisefinThread):
    def __init__(self, scope):
        super().__init__(scope)
        self._set_namespace(ApplicationNamespace.PAYROLL_SERVICE)



    def create_employeepay_structdeduct(self, deduct_obj,user_id, employee_id):
        success_obj = NWisefinSuccess()
        success_obj.set_status(SuccessStatus.SUCCESS)
        if deduct_obj.get_id() is not None:
            deduct_data = EmployeePaystructure_deductions.objects.using(self._current_app_schema()).filter(id=deduct_obj.get_id()).update(
                                                                                                            employee_id=employee_id,
                                                                                                            paycomponent_id=deduct_obj.get_paycomponent_id(),
                                                                                                            type=deduct_obj.get_type(),
                                                                                                            from_date=deduct_obj.get_from_date(),
                                                                                                            to_date=deduct_obj.get_to_date(),
                                                                                                            amount=deduct_obj.get_amount(),
                                                                                                            updated_by=user_id,
                                                                                                            updated_date=timezone.now(),
                                                                                                            entity_id=self._entity_id())
            deduct_data = EmployeePaystructure_deductions.objects.using(self._current_app_schema()).get(id=deduct_obj.get_id())
            audit_insert_data = AudtiService(self._scope()).audit_function(deduct_data, deduct_data.id,
                                                                           deduct_data.employee_id,
                                                                           Advancetype.employeepaystructure_deduction,
                                                                           ModifyStatus.UPDATE)

            success_obj.set_message(SuccessMessage.UPDATE_MESSAGE)
        else:
            deduct_data = EmployeePaystructure_deductions.objects.using(self._current_app_schema()).create(employee_id=employee_id,
                                                                                                            paycomponent_id=deduct_obj.get_paycomponent_id(),
                                                                                                            type=deduct_obj.get_type(),
                                                                                                            from_date=deduct_obj.get_from_date(),
                                                                                                            to_date=deduct_obj.get_to_date(),
                                                                                                            amount=deduct_obj.get_amount(),
                                                                                                            created_by=user_id,
                                                                                                            entity_id=self._entity_id())
            audit_insert_data = AudtiService(self._scope()).audit_function(deduct_data, deduct_data.id,
                                                                           deduct_data.employee_id,
                                                                           Advancetype.employeepaystructure_deduction,
                                                                           ModifyStatus.CREATE)


            success_obj.set_message(SuccessMessage.CREATE_MESSAGE)
        return success_obj

    def employee_fetch(self, emp_id):
        emp_deduct = EmployeePaystructure_deductions.objects.using(self._current_app_schema()).filter(status=Activestatus.active, employee_id=emp_id)
        return emp_deduct

    def employeepaystruct_deduct(self, emp_id):
        condition = Q(entity_id=self._entity_id(), status=Activestatus.active, employee_id=emp_id)
        emp_deduct = EmployeePaystructure_deductions.objects.using(self._current_app_schema()).filter(condition)
        # emp = Employee.objects.using(self._current_app_schema()).get(id=emp_id)
        emp = HrmsApiService(self._scope()).get_single_emp_by_id(emp_id)
        emp_name = emp.get('name') if emp else None
        list = []
        total = 0
        for each in emp_deduct:
                total = total + each.amount
        paycomponent = [i.paycomponent_id for i in emp_deduct]
        # pay_com_obj = PayrollComponentService(self._scope()).get_multiple_payroll_component(paycomponent)
        # pay_com_obj = EmployeePFService(self._scope()).get_multiple_emp_pf(paycomponent)
        # pay_com_obj = Payrollcommon_Apicall(self._scope()).get_multiple_emp_pf_struct(paycomponent)
        pay_com_obj = EmployeePFService(self._scope()).get_multiple_emp_pf(paycomponent)
        pay_com_obj1 = PayrollComponentService(self._scope()).get_multiple_payroll_component_val(paycomponent)
        for empmonth in emp_deduct:
            resp = EmployeePaystructure_deductionsResponse()
            resp.set_id(empmonth.id)
            resp.set_type(empmonth.type)
            resp.set_from_date(empmonth.from_date)
            resp.set_to_date(empmonth.to_date)
            resp.set_amount(empmonth.amount)
            resp.set_emp_name(emp_name)
            resp.set_total(total)
            resp.set_YEARLY_total(total)
            resp.set_YEARLY_amount(empmonth.amount)
            if empmonth.is_customdeduct == 1:
                resp.set_paycomponent_val(empmonth.paycomponent_id, pay_com_obj1)
            else:
                resp.set_paycomponent_val(empmonth.paycomponent_id, pay_com_obj)
            list.append(resp)
            # list.append(json.load(resp.get()))
        return list


    def fetch_emppaystruct_deduct(self, id):
        try:
            deduct_data = EmployeePaystructure_deductions.objects.using(self._current_app_schema()).filter(status=Activestatus.active,id=id).first()
            if not deduct_data:
                error_obj = NWisefinError()
                error_obj.set_code(ErrorMessage.UNEXPECTED_ERROR)
                error_obj.set_description(ErrorMessage.INVALID_DATA)
                return error_obj
            # pay_com_obj = PayrollComponentService(self._scope()).get_multiple_payroll_component([deduct_data.paycomponent_id])
            # pay_com_obj = Payrollcommon_Apicall(self._scope()).get_multiple_emp_pf_struct([deduct_data.paycomponent_id])
            pay_com_obj =EmployeePFService(self._scope()).get_multiple_emp_pf([deduct_data.paycomponent_id])
            resp = EmployeePaystructure_deductionsResponse()
            resp.set_id(deduct_data.id)
            resp.set_type(deduct_data.type)
            resp.set_from_date(deduct_data.from_date)
            resp.set_to_date(deduct_data.to_date)
            resp.set_amount(deduct_data.amount)
            resp.set_paycomponent_val(deduct_data.paycomponent_id, pay_com_obj)
            apifunction = Payrollcommon_Apicall(self._scope())
            emp_data = apifunction.emp_details_payroll(deduct_data.employee_id)
            # emp_data = EmployeemonthlypayService(self._scope()).employee_details(deduct_data.employee_id)
            resp.employee = emp_data
        except Exception as excep:
            exception_type, exception_object, exception_traceback = sys.exc_info()
            filename = exception_traceback.tb_frame.f_code.co_filename
            line_number = exception_traceback.tb_lineno
            traceback.print_exc()
            error_obj = NWisefinError()
            error_obj.set_code(ErrorMessage.INVALID_DATA)
            error_obj.set_description(str(excep))
            error_obj.error = str(excep) + " - " + str(filename) + ", line_no: " + str(line_number)
            resp = error_obj
        return resp

    def inactive_emppaystruct_deduct(self, id, user_id):
        deduct_data = EmployeePaystructure_deductions.objects.using(self._current_app_schema()).filter(id=id).update(status=Activestatus.inactive, updated_by=user_id,
                                                                      updated_date=timezone.now())
        if deduct_data > 0:
            success_obj = NWisefinSuccess()
            success_obj.set_status(SuccessStatus.SUCCESS)
            success_obj.set_message(SuccessMessage.DELETE_MESSAGE)
        else:
            success_obj = NWisefinError()
            success_obj.set_code(ErrorMessage.UNEXPECTED_ERROR)
            success_obj.set_description(ErrorDescription.INVALID_DATA)
        return success_obj

    def Amount_calculation(self, empid):
        Deductions_data = EmployeePaystructure_deductions.objects.using(self._current_app_schema()).filter(employee_id=empid,status=Activestatus.active).values('paycomponent_id')
        # pf_info = Payrollcommon_Apicall(self._scope()).get_employee_pfstruc_data(Deductions_data)
        pf_info = PaycomponentFlagmasterService(self._scope()).get_employee_pf_data(Deductions_data)
        # pf_info=EmployeePFStructure.objects.using(self._current_app_schema()).filter(id__in=Deductions_data,status=Activestatus.active).values('name','code','percentage','amount','id')
        pf_info_df=pd.DataFrame.from_records(pf_info)
        # component_info = Payrollcommon_Apicall(self._scope()).get_paycomponent_flag_data(pf_info_df['id'])
        component_info = PaycomponentFlagmasterService(self._scope()).get_paycomponent_flag_val(pf_info_df['id'])
        # component_info=PaycomponentFlagmaster.objects.using(self._current_app_schema()).filter(status=Activestatus.active,map_id__in=pf_info_df['id']).values('map_id','ref_id')
        component_df = pd.DataFrame.from_records(component_info)

    def Structure_deductioninfo(self,emp_list):
        obj = EmployeePaystructure_deductions.objects.using(self._current_app_schema()).filter(status=Activestatus.active,employee_id__in=emp_list).values('employee_id', 'from_date', 'to_date', 'paycomponent_id', 'amount', 'type')
        return obj


    def struct_deduct_excel_upload(self,deduct_obj,user_id):
        success_obj = NWisefinSuccess()
        success_obj.set_status(SuccessStatus.SUCCESS)
        for deduct_data in deduct_obj:
            # paycomponent = Payrollcommon_Apicall(self._scope()).get_pf_type(deduct_data['PAYCOMPONENT_ID'])
            paycomponent = EmployeePFService(self._scope()).get_pf_type(deduct_data['PAYCOMPONENT_ID'])
            employee_id = Payrollcommon_Apicall(self._scope()).emp_code(deduct_data['EMPLOYEE_CODE'])
            type = payrolldeduction_val(deduct_data['TYPE'])
            data = EmployeePaystructure_deductions.objects.using(self._current_app_schema()).create(
                                                                                employee_id=employee_id.get('id'),
                                                                                paycomponent_id=paycomponent,
                                                                                type=type['id'],
                                                                                amount=deduct_data['AMOUNT'],
                                                                                created_by=user_id,
                                                                                entity_id=self._entity_id())

        success_obj.set_message(SuccessMessage.CREATE_MESSAGE)
        return success_obj

    def struct_deduct_excel_upload1(self,user_id,employee_id,pf_type,is_esi):
        try:
            logger.info("upload EmployeePaystructure_deduction employee contribution calcultion end (employee_id)" + str( employee_id))
            success_obj = NWisefinSuccess()
            success_obj.set_status(SuccessStatus.SUCCESS)
            data = ['PF', 'ESI']
            for i in data:
                # paycomponent_id = Payrollcommon_Apicall(self._scope()).get_pf_type(i)
                paycomponent_id = EmployeePFService(self._scope()).get_pf_type(i)
                # pf_data_calculation = Payrollcommon_Apicall(self._scope()).cal_employeepf_data(paycomponent_id)
                pf_data_calculation = EmployeePFService(self._scope()).cal_employeepf_data(paycomponent_id)
                if is_esi == (1 or 0) or get_pf_type(pf_type)['name'] == ('NORMAL_PF' or 'VIRTUAL_PF'):
                    # if is_esi is True:
                        # flag_master = Payrollcommon_Apicall(self._scope()).cc_epf_based_paycom(pf_data_calculation['id'], FlagRef_Type.PF)
                        flag_master = PaycomponentFlagmasterService(self._scope()).cc_epf_based_paycom(pf_data_calculation['id'], FlagRef_Type.PF)
                        emp_details = EmployeePaystructure_details.objects.using(self._current_app_schema()).filter(paycomponent__in=flag_master, emp_pay_id__employee_id=employee_id, company_contribution=False,status=Activestatus.active)
                        amount = [i.amount for i in emp_details]
                        sum_amount = sum(amount)
                        pf_data_calculation_values = float(pf_data_calculation['percentage'])
                        final_calculation = float(sum_amount) * pf_data_calculation_values / 100
                        if get_pf_type(pf_type)['name'] == 'NORMAL_PF' or is_esi == (1 or 0):
                            if pf_data_calculation['sal_amount'] == 0.00 or None:
                                if pf_data_calculation['amount'] < final_calculation:
                                    amount = round(pf_data_calculation['amount'])
                                else:
                                    amount = round(final_calculation)
                            else:
                                if is_esi == 0:
                                    amount = 0.00
                                else:
                                    if sum_amount <= pf_data_calculation['sal_amount']:
                                        amount = round(final_calculation)
                                    else:
                                        amount = 0.00
                        else:
                            amount = round(final_calculation)
                    # else:
                    #     amount = 0.00
                else:
                    amount = round(pf_data_calculation['amount'])

                data = EmployeePaystructure_deductions.objects.using(self._current_app_schema()).create(
                                                                                    employee_id=employee_id,
                                                                                    paycomponent_id=paycomponent_id,
                                                                                    type=None,
                                                                                    amount=amount,
                                                                                    created_by=user_id,
                                                                                    entity_id=self._entity_id())

            success_obj.set_message(SuccessMessage.CREATE_MESSAGE)
            logger.info("upload EmployeePaystructure_duction employee contribution calcultion end (employee_id)" + str(employee_id))
            logger.info( "upload EmployeePaystructure_duction employee contribution calcultion end (deduct_id)" + str( data.id))
            return success_obj
        except Exception as excep:
            obj = NWisefinError()
            obj.set_code(ErrorMessage.INVALID_DATA)
            obj.set_description(str(excep))
            return obj

            # return amount
            # if pf1 == 1 and i == 'PF' or esi1 == 1 and i == 'ESI':
            #     paycomponent_id=Payrollcommon_Apicall(self._scope()).get_pf_type(i)
            #     # pf_data_calculation = Payrollcommon_Apicall(self._scope()).paycomponent_data(paycomponent_id)
            #     pf_data_calculation = Payrollcommon_Apicall(self._scope()).cal_employeepf_data(paycomponent_id)
            #     flag_master = Payrollcommon_Apicall(self._scope()).cc_epf_based_paycom(pf_data_calculation['id'], FlagRef_Type.PF)
            #     emp_details = EmployeePaystructure_details.objects.using(self._current_app_schema()).filter(paycomponent__in=flag_master,emp_pay_id__employee_id=employee_id, company_contribution=False, status=Activestatus.active)
            #     amount = [i.amount for i in emp_details]
            #     sum_amount = sum(amount)
            #     pf_data_calculation_values = float(pf_data_calculation['percentage'])
            #     final_calculation = float(sum_amount) * pf_data_calculation_values / 100
            #     if pf_data_calculation['sal_amount'] == 0.00 or None:
            #         if pf_data_calculation['amount'] < final_calculation:
            #             amount = round(pf_data_calculation['amount'])
            #         else:
            #             amount = round(final_calculation)
            #     else:
            #         if pf_data_calculation['sal_amount'] < sum_amount:
            #             amount = '0.0'
            #         elif pf_data_calculation['sal_amount'] > sum_amount:
            #             amount = round(final_calculation)
            # else:
            #     paycomponent_id = Payrollcommon_Apicall(self._scope()).get_pf_type(i)
            #     amount=0.00

            # data = EmployeePaystructure_deductions.objects.using(self._current_app_schema()).create(
        #                                                                     employee_id=employee_id,
        #                                                                     paycomponent_id=paycomponent_id,
        #                                                                     type=1,
        #                                                                     amount=amount,
        #                                                                     created_by=user_id,
        #                                                                     entity_id=self._entity_id())
        #
        # success_obj.set_message(SuccessMessage.CREATE_MESSAGE)
        # return success_obj

    def employee_deduction(self, employee_id):
        obj = EmployeePaystructure_deductions.objects.using(self._current_app_schema()).filter(employee_id=employee_id,
                                                                                               status=Activestatus.active)
        paycomponent_id = [i.paycomponent_id for i in obj]
        # paycomponent_data = Payrollcommon_Apicall(self._scope()).segment_wise_employee_pf_data(paycomponent_id)
        paycomponent_data = EmployeePFService(self._scope()).segment_wise_employee_pf(paycomponent_id)
        list_data = []
        for i in obj:
            paycomponent_datas = self.set_paycomponent_val(i.paycomponent_id, paycomponent_data)
            data = {"deduction_amount": str(i.amount), "deduction_id": i.id,
                    "id": paycomponent_datas['id'],"name":paycomponent_datas['name'],"percentage": paycomponent_datas['percentage'],
                    "sal_amount":paycomponent_datas['sal_amount'], "max_amount":paycomponent_datas['max_amount'], "type": payrolldeduction_val(i.type)}
            list_data.append(data)
        return list_data

    def employee_deduction1(self, employee_id):
        obj = EmployeePaystructure_deductions.objects.using(self._current_app_schema()).filter(employee_id=employee_id,
                                                                                               status=Activestatus.active)
        list_data = []
        for i in obj:
            if i.is_customdeduct == 0:
                validcustom = obj.filter(is_customdeduct=0)
                paycomponent_id = [i.paycomponent_id for i in validcustom]
                paycomponent_data = EmployeePFService(self._scope()).segment_wise_employee_pf(paycomponent_id)
                paycomponent_datas = self.set_paycomponent_val(i.paycomponent_id, paycomponent_data)
            else:
                validcustom1 = obj.filter(is_customdeduct=1)
                paycomponent_id1 = [i.paycomponent_id for i in validcustom1]
                paycomponent_data = PayrollComponentService(self._scope()).paystruct_details_data(paycomponent_id1)
                paycomponent_datas = self.set_paycomponent_val1(i.paycomponent_id, paycomponent_data)
            data = {"deduction_amount": str(i.amount), "deduction_id": i.id,
                    "id": paycomponent_datas['id'], "name": paycomponent_datas['name'],
                    "percentage": paycomponent_datas['percentage'],
                    "sal_amount": paycomponent_datas['sal_amount'], "max_amount": paycomponent_datas['max_amount'],
                    "type": payrolldeduction_val(i.type)}
            list_data.append(data)
        return list_data

    def set_paycomponent_val(self, paycomponent, arr):
        for i in arr:
            if i.id == paycomponent:
                data = {"max_amount": str(i.max_amount), "cat_id": i.cat_id, "glno": i.glno, "id": i.id, "name": i.name,
                        "percentage": str(i.percentage), "sal_amount": i.sal_amount, "subcat_id": i.subcat_id}
                return data

    def set_paycomponent_val1(self, paycomponent, arr):
        for i in arr:
            if i.paycomponent == paycomponent:
                data = {"max_amount": str(0.00), "cat_id": None, "glno": i.glno, "id": i.paycomponent, "name": i.paycomponent_name,
                        "percentage": str(i.paycomponent_percentage), "sal_amount": 0.00, "subcat_id": 0.00}
                return data

    def create_employeepay_structdeduct1(self, deduct_obj,user_id, employee_id):
        if deduct_obj.get_is_customdeduct() == 0:
            paycomponent_id = deduct_obj.get_paycomponent_id()
        else:
            paycomponent_id = deduct_obj.get_paycomponent()
        success_obj = NWisefinSuccess()
        success_obj.set_status(SuccessStatus.SUCCESS)
        if deduct_obj.get_deduction_id() is not None:
            deduct_data = EmployeePaystructure_deductions.objects.using(self._current_app_schema()).filter(id=deduct_obj.get_deduction_id()).update(
                                                                                                            employee_id=employee_id,
                                                                                                            paycomponent_id=paycomponent_id,
                                                                                                            type=deduct_obj.get_type(),
                                                                                                            from_date=deduct_obj.get_from_date(),
                                                                                                            to_date=deduct_obj.get_to_date(),
                                                                                                            amount=deduct_obj.get_amount(),
                                                                                                            updated_by=user_id,
                                                                                                            updated_date=timezone.now(),
                                                                                                            entity_id=self._entity_id(),
                                                                                                            is_customdeduct=deduct_obj.get_is_customdeduct())
            deduct_data = EmployeePaystructure_deductions.objects.using(self._current_app_schema()).get(id=deduct_obj.get_deduction_id())
            audit_insert_data = AudtiService(self._scope()).audit_function(deduct_data, deduct_data.id,
                                                                           deduct_data.employee_id,
                                                                           Advancetype.employeepaystructure_deduction,
                                                                           ModifyStatus.UPDATE)

            success_obj.set_message(SuccessMessage.UPDATE_MESSAGE)
        else:
            deduct_data = EmployeePaystructure_deductions.objects.using(self._current_app_schema()).create(employee_id=employee_id,
                                                                                                            paycomponent_id=paycomponent_id,
                                                                                                            type=deduct_obj.get_type(),
                                                                                                            from_date=deduct_obj.get_from_date(),
                                                                                                            to_date=deduct_obj.get_to_date(),
                                                                                                            amount=deduct_obj.get_amount(),
                                                                                                            created_by=user_id,
                                                                                                            entity_id=self._entity_id(),
                                                                                                            is_customdeduct=deduct_obj.get_is_customdeduct())
            audit_insert_data = AudtiService(self._scope()).audit_function(deduct_data, deduct_data.id,
                                                                           deduct_data.employee_id,
                                                                           Advancetype.employeepaystructure_deduction,
                                                                           ModifyStatus.CREATE)


            success_obj.set_message(SuccessMessage.CREATE_MESSAGE)
        return success_obj
