import base64
import json
import logging
import time

import requests
from dateutil.relativedelta import relativedelta
from django.conf import settings
from django.db import transaction as db_transaction
from urllib.parse import quote_plus

from app.api import api
from app.aws.s3_accessor import S3Accessor
from app.bank.user_bank import UserBank
from app.credit_fair.credit_fair import CreditFair
from app.encryption.aes128 import AES128
from app.encryption.aes256 import AES256
from app.encryption.hashid import CFHashIds
from app.karza.karza import Karza
from app.loan_application.accessor.document_details_accessor import DocumentDetailsAccessor
from app.loan_application.accessor.user_geolocation_accessor import UserGeolocationAccessor
from app.loan_application.digital_sign_details_accessor import DigitalSignDetailsAccessor
from app.loan_application.employment_verification_accessor import EmploymentVerificationAccessor
from app.loan_application.loan_application_accessor import LoanApplicationAccessor
from app.loan_application.pincode_accessor import PincodeAccessor
from app.loan_application_files.loan_application_files_accessor import LoanApplicationFilesAccessor
from app.loan_offer.emi_calculation_accessor import EmiCalculationAccessor
from app.loan_offer.merchant_offers_accessor import MerchantOffersAccessor
from app.loan_offer.state import StateCodeAccessor
from app.user_agreement.user_agreement import UserAgreement
from app.bank.lender_bank_accessor import LenderBankAccessor
from app.loan_offer.pro_forma_invoice_accessor import ProFormaInvoiceAccessor
from app.merchant.merchant import Merchant
from app.merchant.merchant_center import MerchantCenter
from app.payment.accessor.paytm_accessor import PaytmAccessor
from app.payment.accessor.razorpay_accessor import RazorpayAccessor
from app.payment.mandate import Payment
from app.loan_application.job_accessor import JobAccessor
from app.user.user import User
from app.user.user_document import UserDocument
from app.user_employment.company import Company
from app.util import util, datetime_util
from app.util.datetime_util import get_now_ist_string, get_date_string_dd_mmm_yyyy, today
from app.util.file_util import FileUtil
from fuzzywuzzy import fuzz
from app.merchant.accessor.merchant_product_accessor import MerchantProductAccessor

log = logging.getLogger(__name__)


class LoanApplication:
    ADDRESS_TYPE_PERMANENT_ADDRESS = 'PERMANENT_ADDRESS'
    ADDRESS_TYPE_CURRENT_ADDRESS = 'CURRENT_ADDRESS'

    REQUIRED_NAME_MATCH_RATIO = 80

    FREQUENCY_ADHOC = 'ADHO'
    FREQUENCY_MONTHLY = 'MNTH'

    def __init__(self, loan_application):
        self.IN_PROCESS = 'in-process'
        self.APPROVED = 'approved'
        self.loan_application = loan_application

    @classmethod
    def get_loan_application(cls, loan_application_id=None, hashed_loan_application_id=None, user_id=None):
        if hashed_loan_application_id:
            loan_application_id = LoanApplication.get_application_id_from_hash(hashed_loan_application_id)

        return LoanApplicationAccessor.get_loan_application(loan_application_id, user_id)

    @classmethod
    def get_loan_application_for_user(cls, user_id):
        return LoanApplicationAccessor.get_loan_application_for_user(user_id)

    def update_aadhar_and_save_aadhar_docs(
            self, aadhar_card, address, dob_string, name, share_code, aadhar_zip, aadhar_pdf_report, bearer_token,
            client_id, client_secret):
        encrypted_aadhar_card = self.encrypt(aadhar_card)

        address_street = self.get_street_from_address(address)
        LoanApplicationAccessor.update_permanent_address(
            loan_application_id=self.get_loan_application_id(), street_1=address_street, street_2=address['landmark'],
            state=address['state'], city=address['city'], pincode=address['pincode'])

        if self.get_dob():
            saved_dob = datetime_util.get_date_string_yyyy_mm_dd(self.get_dob())
        else:
            saved_dob = ""

        name_match_ratio = fuzz.ratio(self.get_name().lower(), name.lower())
        if name_match_ratio >= self.REQUIRED_NAME_MATCH_RATIO and saved_dob != dob_string:
            log.debug(f'Updating user dob from {saved_dob} to {dob_string}')
            LoanApplicationAccessor.update_dob(self.get_id(), dob_string)
        else:
            log.debug(f'Not updating dob as name match ratio: '
                      f'{name_match_ratio} is less than {self.REQUIRED_NAME_MATCH_RATIO}')

        status = LoanApplicationAccessor.update_aadhar_card(self.get_id(), encrypted_aadhar_card)
        if util.is_status_failed(status):
            return status

        status = UserDocument.upload_document(
            self.get_hashed_application_id(), UserDocument.AADHAR_ZIP, UserDocument.DOCUMENT_TYPE_ZIP, aadhar_zip,
            share_code, bearer_token, client_id, client_secret)
        if util.is_status_failed(status):
            return status

        status = UserDocument.upload_document(
            self.get_hashed_application_id(), UserDocument.AADHAR_CARD, UserDocument.PDF, aadhar_pdf_report,
            self.get_aadhar_pdf_password(name, dob_string), bearer_token, client_id, client_secret)
        if util.is_status_failed(status):
            return status

        return api.SUCCESS

    @classmethod
    def get_street_from_address(cls, address):
        address_fields = []

        if address['houseNumber']:
            address_fields.append(address['houseNumber'])

        if address['street'] and address['street'] != 'null':
            address_fields.append(address['street'])

        if address['location'] and address['location'] != 'null':
            address_fields.append(address['location'])

        if address['vtcName'] and address['vtcName'] != 'null':
            address_fields.append(address['vtcName'])

        return ", ".join(address_fields)

    def get_id(self):
        return self.loan_application.id

    def update_pan_details(self, pan, dob, name):
        dob_string = datetime_util.get_date_string_yyyy_mm_dd(datetime_util.get_date_object(dob))
        encrypted_pan = LoanApplication.encrypt(pan)

        status = LoanApplicationAccessor.update_pan_details(self.get_id(), encrypted_pan, dob_string, name)
        if util.is_status_failed(status):
            return status

        return api.SUCCESS

    def update_references(self, references):
        reference_name_1 = references[0]['name']
        reference_contact_1 = references[0]['phone_number']
        reference_relation_1 = references[0]['relationship']

        reference_name_2 = references[1]['name']
        reference_contact_2 = references[1]['phone_number']
        reference_relation_2 = references[1]['relationship']

        return LoanApplicationAccessor.update_references(
            self.get_id(), reference_name_1, reference_name_2, reference_contact_1, reference_contact_2,
            reference_relation_1, reference_relation_2)

    def add_current_address(self, street_1, street_2, pincode, city, state):
        return LoanApplicationAccessor.update_current_address(
            loan_application_id=self.get_id(), street_1=street_1, street_2=street_2, state=state, city=city,
            pincode=pincode)

    def add_permanent_address(self, street_1, street_2, pincode, city, state):
        return LoanApplicationAccessor.update_permanent_address(
            loan_application_id=self.get_id(), street_1=street_1, street_2=street_2, state=state, city=city,
            pincode=pincode)

    @classmethod
    def get_pincode_details(cls, pincode):
        status, pincode_details = PincodeAccessor.get_pincode_details(pincode)

        if util.is_status_failed(status):
            log.info('Unable to get pin code details')
            return api.PINCODE_NOT_FOUND, None

        return api.SUCCESS, {
            'pincode': pincode_details["pincode"],
            'city': pincode_details["districtname"].title(),
            'state': pincode_details["statename"].title(),
        }

    def update_bank_details(self, account_number, bank_name, ifsc_code, branch_name):
        user = self.get_user()
        # check for existing bank record details
        application = self.get_loan_application_for_user(user.id)
        bank_details = UserBank.get_bank(application.id)
        if bank_details:
            if User.decrypt(bank_details.account_number) == account_number:
                return api.SUCCESS
        # name = User(user).get_name()

        # status = 1
        # api_response = {
        #     'request_id': '2d6b72b9-f111-4921-9adf-8142a31de268',
        #     'result': {'accountName': 'ROHAN . ', 'accountNumber': '50100381112686',
        #                'bankResponse': 'Transaction Successful', 'bankTxnStatus': True, 'ifsc': 'HDFC0009471'},
        #     'status-code': '101'}

        status, api_response = Karza.verify_user_bank(account_number, ifsc_code)
        if util.is_status_failed(status):
            return status

        name = api_response['result']['accountName'].replace('Mr. ', '').replace('Mrs. ', '').replace('.',
                                                                                                      '').strip() if api_response else self.get_name()

        with db_transaction.atomic():
            user_id = User(user).get_user_id() if user else None

            status, bank = UserBank.add_update_bank_details(
                user_id=user_id, loan_application_id=self.get_id(), applicant_type=1,
                account_number=self.encrypt(account_number),
                bank_name=bank_name, branch_name=branch_name, ifsc_code=ifsc_code, beneficiary_id=0, name=name,
                karza_response=json.dumps(api_response))

            if util.is_status_failed(status):
                return status

            status = LoanApplicationAccessor.update_bank(self.get_id(), bank_id=UserBank(bank).get_id())
            if util.is_status_failed(status):
                db_transaction.set_rollback(True)

            return status

    def get_user(self):
        return User.get_user(self.loan_application.user_id)

    def update_employment_details(self, company_name, profession_type, salary, bearer_token, client_id, client_secret):
        company = Company.get_or_create(company_name.strip())

        employee_verification_status, api_response = Karza.verify_user_epf(
            company_name, User(self.get_user()).get_name())

        with db_transaction.atomic():
            if not util.is_status_failed(employee_verification_status):
                log.debug('Saving api response')

                status = EmploymentVerificationAccessor.update_employment_verification_data(
                    self.get_loan_application_id(), api_response, 1)
                if util.is_status_failed(status):
                    return status

                pdf_link = api_response.get('result', {}).get('pdfLink')
                status = self.save_employment_pdf(pdf_link, bearer_token, client_id, client_secret)
                if util.is_status_failed(status):
                    return status

            status = LoanApplicationAccessor.update_employment_details(
                self.get_loan_application_id(), company.id, profession_type, salary)

            if util.is_status_failed(status):
                db_transaction.set_rollback(True)
                return status

        return api.SUCCESS

    def get_hashed_application_id(self):
        return CFHashIds().encode(self.loan_application.id)

    @classmethod
    def get_application_id_from_hash(cls, hashed_application_id):
        return CFHashIds().decode(hashed_application_id)

    def get_user_application_link(self):
        data = {
            "application_id": self.get_hashed_application_id(),
            "timestamp": datetime_util.get_epoch_secs()
        }

        encrypted_data = AES256().aes_cbc_encrypt(json.dumps(data))

        return f'{settings.BACKEND_HOME_URL}/v1/customer/loan/application/?' \
               f'data={quote_plus(encrypted_data)}&version={settings.ENCRYPTED_LINK_DATA_VERSION}'

    @classmethod
    def get_decrypted_link_data(cls, data):
        data = AES256().aes_cbc_decrypt(data)

        if not data:
            log.warning('Unable to decrypt data')
            return api.INVALID_APPLICATION_DATA, None

        data = json.loads(data)

        if not cls.is_valid_application_data(data):
            log.warning('Invalid application data')
            return api.INVALID_APPLICATION_DATA, None

        return api.SUCCESS, data

    @classmethod
    def is_valid_application_data(cls, data):
        return (data and data.get('timestamp') and data.get(
            'timestamp') + settings.LINK_EXPIRY_SECS > datetime_util.get_epoch_secs())

    @classmethod
    def get_user_for_application_id(cls, application_id):
        loan_application = cls.get_loan_application(application_id)
        return LoanApplication(loan_application).get_user()

    def get_loan_application_id(self):
        return self.loan_application.id

    def get_applicant_id(self):
        return self.loan_application.applicant_id

    def get_co_applicant_name(self):
        return self.loan_application.co_applicant_name

    def sign_agreement(self, agreement, bearer_token, os, browser, full_version, major_version, app_name, user_agent,
                       user_ip, load_time, click_time_string, latitude, longitude, client_id, client_secret):

        if self.is_agreement_signed():
            return api.AGREEMENT_ALREADY_SIGNED

        agreement_pdf_path = None

        try:
            if ProFormaInvoiceAccessor.get_pro_forma_invoice(
                    self.get_loan_application_id()).insurance_premium_applicable:
                insurance_fee = ProFormaInvoiceAccessor.get_pro_forma_invoice(
                    self.get_loan_application_id()).insurance_premium_applicable
            else:
                insurance_fee = ""

            document_detail_set = DocumentDetailsAccessor.get_document_details_json(self.get_loan_application_id())

            permanent_address = document_detail_set.current_address_line_1 + ' ' + document_detail_set.current_address_landmark + ' ' + document_detail_set.current_address_state + ' ' + document_detail_set.current_address_city + ' ' + document_detail_set.current_address_pincode

            applicant_agreement, co_applicant_agreement = self.create_user_signatures()

            full_agreement = UserAgreement.update_agreement(agreement, latitude, longitude,
                                                            applicant_agreement, co_applicant_agreement)

            agreement_pdf_path = UserDocument.convert_agreement_to_pdf(full_agreement)

            document_content = FileUtil.convert_to_base64(agreement_pdf_path)
            status = CreditFair.upload_document(
                self.get_hashed_application_id(), UserDocument.AGREEMENT, UserDocument.PDF,
                document_content.decode("utf-8"), "", bearer_token, client_id, client_secret)

            if util.is_status_failed(status):
                return status

            with db_transaction.atomic():
                DigitalSignDetailsAccessor.update_digital_sign_details(
                    application_id=self.get_loan_application_id(), os=os, browser=browser, full_version=full_version,
                    major_version=major_version, app_name=app_name, language='ENGLISH', user_agent=user_agent,
                    user_ip=user_ip, load_time=load_time, click_time=click_time_string, is_agreement_signed=1,
                    type='consumer', esign_vendor='self', is_stamped=0, stamp_qty=0, applicant=1)

                UserGeolocationAccessor.update_user_geolocation(
                    applicant_id=self.get_applicant_id(), applicant_type=1, field_id=self.get_loan_application_id(),
                    id_type='application_id',
                    service='esign', latitude=latitude, longitude=longitude)

                DocumentDetailsAccessor.create_document_details(
                    consumer_file_id=LoanApplicationAccessor.get_agreement_file_id(self.get_loan_application_id()),
                    application_id=self.get_loan_application_id(), doc_type='LOAN_AGREEMENT',
                    name=document_detail_set.name,
                    tenure=document_detail_set.emicalculation.tenure,
                    emi_date=document_detail_set.emicalculation.emi_date.strftime("%Y-%m-%d %H:%M:%S"),
                    emi_amount=document_detail_set.emicalculation.emi, lender_name=document_detail_set.lender.name,
                    loan_amount=document_detail_set.loan_amount, insurance_fee=insurance_fee,
                    advance_amount=document_detail_set.emicalculation.upfront_from_customer,
                    processing_fee=document_detail_set.emicalculation.processing_fee,
                    rate_of_interest=document_detail_set.emicalculation.interest_rate,
                    permanent_address=permanent_address,
                    applicant_signed_date=click_time_string, co_applicant_signed_date="",
                    applicant_location_latitude=latitude, applicant_location_longitude=longitude,
                    co_applicant_location_latitude="",
                    co_applicant_location_longitude="")

                self.add_job_for_da('emiCheckoutGenerateDocuments')
        finally:
            if agreement_pdf_path:
                FileUtil.remove_file(agreement_pdf_path)

        return api.SUCCESS

    def get_agreement_s3_path(self):
        return LoanApplicationAccessor.get_agreement_s3_path(self.get_loan_application_id())

    def get_flow_application_details(self):
        return {
            "name": self.get_name(),
            "application_id": self.get_hashed_application_id(),
            "aadhaar_card": User.decrypt(self.get_aadhar_card()),
            "pan_card": User.decrypt(self.get_pan_card()),
            "product_details": self.get_serialized_product_details(),
            "address_details": self.get_serialized_address_details(),
            "reference_details": self.get_serialized_reference_details(),
            "employement_details": self.get_serialized_employement_details(),
            "state": self.get_state(),
            "is_agreement_signed": self.is_agreement_signed()
        }

    def get_name(self):
        return self.loan_application.name

    def get_aadhar_card(self):
        return self.loan_application.aadhaar_card

    def get_pan_card(self):
        return self.loan_application.pan_card

    def get_state(self):
        return self.loan_application.state

    def get_dob(self):
        return self.loan_application.dob

    @classmethod
    def encrypt(cls, data):
        return AES128.aes_cbc_encrypt(data)

    def get_amount(self):
        return self.loan_application.approved_amount

    def get_serialized_product_details(self):
        merchant = Merchant(self.get_merchant())

        loan_application_element = LoanApplicationAccessor.get_loan_application_element(self.get_loan_application_id())
        if loan_application_element:
            if loan_application_element.product_name.isnumeric():
                product = MerchantProductAccessor.get_merchant_product(None, loan_application_element.product_name).name
            else:
                product = loan_application_element.product_name
            return {
                # "product_name": loan_application_element.product_name,
                "product_name": product,
                "amount": self.get_amount(),
                "tenure": self.get_tenure(),
                "merchant_logo": merchant.get_merchant_logo(),
                "merchant_name": merchant.get_merchant_name()
            }

        return {}

    def get_serialized_address_details(self):
        return {
            "current_address_line_1": self.loan_application.current_address_line_1,
            "current_address_state": self.loan_application.current_address_state,
            "current_address_city": self.loan_application.current_address_city,
            "current_address_pincode": self.loan_application.current_address_pincode,
            "current_address_landmark": self.loan_application.current_address_landmark,
            "permanent_address_line_1": self.loan_application.permanent_address_line_1,
            "permanent_address_state": self.loan_application.permanent_address_state,
            "permanent_address_city": self.loan_application.permanent_address_city,
            "permanent_address_pincode": self.loan_application.permanent_address_pincode,
            "permanent_address_landmark": self.loan_application.permanent_address_landmark,
        }

    def get_serialized_reference_details(self):
        loan_application_element = LoanApplicationAccessor.get_loan_application_element(self.get_loan_application_id())
        return {
            "reference_name_1": loan_application_element.reference_name_1 if loan_application_element else None,
            "reference_name_2": loan_application_element.reference_name_2 if loan_application_element else None,
            "reference_contact_1": loan_application_element.reference_contact_1 if loan_application_element else None,
            "reference_contact_2": loan_application_element.reference_contact_2 if loan_application_element else None,
            "reference_relation_1": loan_application_element.reference_relation_1 if loan_application_element else None,
            "reference_relation_2": loan_application_element.reference_relation_2 if loan_application_element else None,
        }

    def get_serialized_employement_details(self):
        return {
            "company_name": self.loan_application.company_name,
            "profession_type": self.loan_application.employee_status,
            "salary": self.loan_application.monthly_income
        }

    def get_list_of_uploaded_documents(self):
        file_types_to_check = {
            "PAN": "pan_card",
            "APPLICANT_PHOTO": "customer_photo",
            "APPLICANT_AADHAAR_FRONT": "aadhar_card",
            "APPLICANT_AADHAAR_BACK": "aadhar_card_back",
            "PASSPORT_FRONT": "passport_front",
            "PASSPORT_BACK": "passport_back",
            "VOTAR_ID_FRONT": "votar_id_front",
            "VOTAR_ID_BACK": "votar_id_back",
            "DRIVING_LICENSE": "driving_license"
        }

        return LoanApplicationAccessor.get_uploaded_files(self.get_loan_application_id(), file_types_to_check)

    def get_tenure(self):
        return self.loan_application.tenure

    def create_user_signatures(self):
        name = self.get_name().title()
        now = datetime_util.now_ist()

        user_signature_text = "\n I have been read out and explained in the language known to me, the contents of " \
                              "the loan documents and all other documents incidental to availing the loan from K. M. " \
                              "Global Credit Private Limited and I have signed the said documents after having " \
                              "understood them and by signing the same I do hereby agree to abide by all the general " \
                              "terms and conditions of the loan and the articles of the same. {} " \
                              "({}) signed this agreement at {}" \
                              " on {}"

        applicant_agreement = user_signature_text.format(name, "Applicant",
                                                         datetime_util.get_time_string_hh_mm_am(now),
                                                         datetime_util.get_date_string_dd_mmm_yyyy(now.date()))

        if self.get_co_applicant_name():
            co_applicant_agreement = user_signature_text.format(self.get_co_applicant_name(), "Co-Applicant",
                                                                datetime_util.get_time_string_hh_mm_am(now),
                                                                datetime_util.get_date_string_dd_mmm_yyyy(now.date()))
        else:
            co_applicant_agreement = None

        return applicant_agreement, co_applicant_agreement

    def get_razorpay_payment_context(self, redirect_url=None):
        if not redirect_url:
            redirect_url = f"{settings.REDIRECT_FRONTEND_HOME_URL}/payment/redirect/url/"

        razorpay_accessor = RazorpayAccessor()
        # TODO fix this when using razorpay as gateway
        amount = self.get_payment_amount(True, True) * 100
        if not amount:
            log.debug(f"Invalid amount: {amount}")
            return api.UNABLE_TO_GENERATE_PAYMENT_LINK, None

        user = User(self.get_user())
        if not user.user:
            log.debug(f"Invalid user")
            return api.UNABLE_TO_GENERATE_PAYMENT_LINK, None

        email = user.get_plaintext_email()
        phone = user.get_plaintext_phone()
        name = user.get_name()

        order_id = razorpay_accessor.create_order(amount, util.get_char_uuid(10))
        if not order_id:
            log.debug(f"Unable to create order: {order_id}")
            return api.UNABLE_TO_GENERATE_PAYMENT_LINK, None

        return api.SUCCESS, razorpay_accessor.get_payment_context(
            name=name, email=email, phone=phone, order_id=order_id, amount=amount, description='Loan Payment',
            redirect_url=redirect_url)

    def get_paytm_payment_context(
            self, redirect_url, online_processing_id, online_advanced_id):
        paytm_accessor = PaytmAccessor()

        amount = self.get_payment_amount(online_processing_id is not None, online_advanced_id is not None)
        user = User(self.get_user())
        if not user.user:
            log.debug(f"Invalid user")
            return None, None, None

        invoice = ProFormaInvoiceAccessor.get_pro_forma_invoice(self.get_loan_application_id())
        if not invoice:
            log.error('Pro forma invoice not found')
            return api.PRO_FORMA_INVOICE_NOT_FOUND, None, None

        order_id = self.get_order_id(
            self.get_loan_application_id(), invoice.loan_application_no, online_advanced_id, online_processing_id)

        status, response = paytm_accessor.create_order(amount, order_id, user.get_hashed_user_id(), redirect_url)

        if util.is_status_failed(status):
            return status, None, None

        txn_token = response.get("body", {}).get("txnToken")

        return api.SUCCESS, paytm_accessor.get_payment_context(order_id, txn_token, amount, redirect_url), response

    def get_paytm_payment_context2(
            self, redirect_url, online_processing_id, online_advanced_id, online_insurance_id=None):
        paytm_accessor = PaytmAccessor()

        amount = self.get_payment_amount2(online_processing_id is not None, online_advanced_id is not None,
                                          online_insurance_id is not None)
        user = User(self.get_user())
        if not user.user:
            log.debug(f"Invalid user")
            return None, None, None

        invoice = ProFormaInvoiceAccessor.get_pro_forma_invoice(self.get_loan_application_id())
        if not invoice:
            log.error('Pro forma invoice not found')
            return api.PRO_FORMA_INVOICE_NOT_FOUND, None, None

        order_id = self.get_order_id2(
            self.get_loan_application_id(), invoice.loan_application_no, online_advanced_id, online_processing_id,
            online_insurance_id)

        status, response = paytm_accessor.create_order(amount, order_id, user.get_hashed_user_id(), redirect_url)

        if util.is_status_failed(status):
            return status, None, None

        txn_token = response.get("body", {}).get("txnToken")

        return api.SUCCESS, paytm_accessor.get_payment_context(order_id, txn_token, amount, redirect_url), response

    @classmethod
    def get_order_id(cls, loan_application_id, invoice_id, online_advanced_id, online_processing_id):

        # order_id = f"{invoice_id}_{loan_application_id}"
        order_id = f"{invoice_id}"
        if online_processing_id:
            order_id = f"{order_id}_{online_processing_id}"
        if online_advanced_id:
            order_id = f"{order_id}_{online_advanced_id}"

        order_id = f"{order_id}_{int(time.time())}"
        return order_id

    @classmethod
    def get_order_id2(cls, loan_application_id, invoice_id, online_advanced_id, online_processing_id,
                      online_insurance_fee=None):

        # order_id = f"{invoice_id}_{loan_application_id}"
        order_id = f"{invoice_id}"
        if online_processing_id:
            order_id = f"{order_id}_{online_processing_id}"
        if online_advanced_id:
            order_id = f"{order_id}_{online_advanced_id}"
        if online_insurance_fee:
            order_id = f"{order_id}_{online_insurance_fee}"

        order_id = f"{order_id}_{int(time.time())}"
        return order_id

    def record_payment_transactions(self):
        merchant = self.get_merchant()
        payment_url = self.get_payment_link('')

        lender_bank = LenderBankAccessor.get_lender_bank(self.loan_application.lender_id)
        if not lender_bank:
            log.error('Lender bank details missing')
            return api.UNABLE_TO_FETCH_LENDER_BANK, None

        emi_calculation = EmiCalculationAccessor.get_emi_calculation(self.get_loan_application_id())

        online_processing_payment, online_advanced_payment = None, None
        # for processing fee
        if self.is_collect_processing_fee(emi_calculation, merchant):
            online_processing_payment = Payment.create_online_payment(
                application_id=self.get_loan_application_id(), payment_id="", amount=emi_calculation.processing_fee,
                payment_for="processing_fee", payment_link=payment_url)

        # for down payment
        if self.is_collect_upfront_from_customer(emi_calculation, merchant):
            online_advanced_payment = Payment.create_online_payment(
                application_id=self.get_loan_application_id(), payment_id="", payment_for="advance_amount",
                amount=emi_calculation.upfront_from_customer, payment_link=payment_url)

        transactions = {
            "online_processing_id": online_processing_payment.id if online_processing_payment else None,
            "online_advanced_id": online_advanced_payment.id if online_advanced_payment else None,
        }

        return api.SUCCESS, transactions

    def record_payment_transactions2(self):
        merchant = self.get_merchant()
        payment_url = self.get_payment_link2('')

        lender_bank = LenderBankAccessor.get_lender_bank(self.loan_application.lender_id)
        if not lender_bank:
            log.error('Lender bank details missing')
            return api.UNABLE_TO_FETCH_LENDER_BANK, None

        emi_calculation = EmiCalculationAccessor.get_emi_calculation(self.get_loan_application_id())
        pro_forma = ProFormaInvoiceAccessor.get_pro_forma_invoice(self.get_loan_application_id())
        online_processing_payment, online_advanced_payment, online_insurance_fee = None, None, None
        # for processing fee
        if self.is_collect_processing_fee(emi_calculation, merchant):
            online_processing_payment = Payment.create_online_payment(
                application_id=self.get_loan_application_id(), payment_id="", amount=emi_calculation.processing_fee,
                payment_for="processing_fee", payment_link=payment_url)

        # for down payment
        if self.is_collect_upfront_from_customer(emi_calculation, merchant):
            online_advanced_payment = Payment.create_online_payment(
                application_id=self.get_loan_application_id(), payment_id="", payment_for="advance_amount",
                amount=emi_calculation.upfront_from_customer, payment_link=payment_url)

        # for insurance payment
        if self.is_collect_insurance_fee(pro_forma, merchant):
            online_insurance_fee = Payment.create_online_payment(
                application_id=self.get_loan_application_id(), payment_id="", payment_for="insurance_fee",
                amount=float(pro_forma.insurance_premium_amount), payment_link=payment_url)

        transactions = {
            "online_processing_id": online_processing_payment.id if online_processing_payment else None,
            "online_advanced_id": online_advanced_payment.id if online_advanced_payment else None,
            "online_insurance_id": online_insurance_fee.id if online_insurance_fee else None
        }

        return api.SUCCESS, transactions

    def get_payment_amount(self, is_collect_processing_fee, is_collect_upfront_from_customer):
        emi_calculation = EmiCalculationAccessor.get_emi_calculation(self.get_id())
        if not emi_calculation:
            return 0

        total_amount = 0
        if is_collect_processing_fee:
            total_amount += emi_calculation.processing_fee

        if is_collect_upfront_from_customer:
            total_amount += emi_calculation.upfront_from_customer

        return total_amount

    def get_payment_amount2(self, is_collect_processing_fee, is_collect_upfront_from_customer,
                            is_collected_insurance_fee=None):
        emi_calculation = EmiCalculationAccessor.get_emi_calculation(self.get_id())
        pro_forma = ProFormaInvoiceAccessor.get_pro_forma_invoice(self.get_id())

        if not emi_calculation:
            return 0

        total_amount = 0
        if is_collect_processing_fee:
            total_amount += emi_calculation.processing_fee

        if is_collect_upfront_from_customer:
            total_amount += emi_calculation.upfront_from_customer

        if is_collected_insurance_fee:
            total_amount += float(pro_forma.insurance_premium_amount)

        return total_amount

    def update_loan_offer(self, loan_offer_id, tenure):
        LoanApplicationAccessor.update_loan_offer(
            self.get_loan_application_id(), loan_offer_id, tenure, self.APPROVED)
        return LoanApplicationAccessor.update_state(self.get_loan_application_id(), self.APPROVED,
                                                    self.loan_application.user_id)

    def get_payment_link(self, redirect_url):
        payment_url = f"{settings.BACKEND_HOME_URL}/v1/order/payment/?application_id={self.get_hashed_application_id()}"
        if redirect_url:
            payment_url = f"{payment_url}&redirect_url={quote_plus(redirect_url)}"

        return payment_url

    def get_payment_link2(self, redirect_url):
        payment_url = f"{settings.BACKEND_HOME_URL}/v1/order_payment/?application_id={self.get_hashed_application_id()}"
        if redirect_url:
            payment_url = f"{payment_url}&redirect_url={quote_plus(redirect_url)}"

        return payment_url

    def get_mandate_setup_link(self, application_id, emi_calculation, redirect_url):
        user_bank = UserBank.get_bank(self.get_loan_application_id())
        if not user_bank:
            return api.MANDATE_SETUP_MISSING_BANK, None

        merchant_offers = self.get_merchant_offers_for_application()
        if not merchant_offers:
            log.error('Mandate setup failed, merchant offers not found')
            return api.MANDATE_SETUP_REQUEST_FAILED, None

        user_bank = UserBank(user_bank)
        approved_amount = self.get_amount() if self.get_amount() < 100000 else 100000

        emi_date = emi_calculation.emi_date
        date_final_collection = self.get_date_final_collection(emi_calculation)
        frequency = self.get_frequency_for_mandate_setup(merchant_offers=merchant_offers)

        status, mandate = Payment.create_mandate(
            application_id=application_id, approved_amount=approved_amount, emi_date=emi_date,
            date_final_collection=date_final_collection)

        transaction_id = CFHashIds().encode(mandate.id)

        status, url = Payment.get_mandate_setup_link(
            date_first_collection=emi_date, amount_maximum=approved_amount * 100, debtor_account_type='savings',
            ifsc_code=user_bank.get_bank_ifsc_code(), debtor_account_name=user_bank.get_account_name(),
            debtor_account_number=user_bank.get_plaintext_account_number(), frequency=frequency,
            redirect_url=redirect_url, transaction_id=transaction_id, date_final_collection=date_final_collection)

        Payment.update_mandate_url(self.get_loan_application_id(), url)

        if util.is_status_failed(status):
            return status, None

        return status, url

    @classmethod
    def get_date_final_collection(cls, emi_calculation):
        last_emi_date = emi_calculation.emi_date + relativedelta(months=emi_calculation.remaining_emi)
        return min(emi_calculation.emi_date + relativedelta(months=360), last_emi_date + relativedelta(months=36))

    def get_mandate_setup_status(self):
        return Payment.check_mandate_setup_status(self.get_loan_application_id())

    def get_payment_status(self):
        return Payment.check_payment_status(self.get_loan_application_id())

    def get_bank_statement_status(self):
        app_id = self.get_loan_application_id()
        return LoanApplicationAccessor.check_bank_statement_upload(app_id)

    def get_loan_offer_id(self):
        return self.loan_application.offer_id

    def fetch_bureau(self, bearer_token, client_id, client_secret):
        user = User(self.get_user())
        application_id = self.get_hashed_application_id()
        applicant_type = 1
        first_name = user.get_first_name()
        last_name = user.get_last_name()
        pan = User.decrypt(self.get_pan_card())
        dob = datetime_util.get_date_string_yyyy_mm_dd(self.get_dob())
        mobile_number = User.decrypt(user.get_phone_number())
        loan_amount = self.loan_application.loan_amount
        address_street, address_city, address_state, address_pincode = self.get_current_address()
        address_state_code = self.get_state_code(address_state)

        return CreditFair.fetch_bureau(
            application_id, applicant_type, first_name, last_name, pan, dob, mobile_number, address_street,
            address_city, address_state_code, address_pincode, loan_amount, bearer_token, client_id, client_secret
        )

    def run_auto_decision(self, bearer_token, client_id, client_secret):
        application_id = self.get_hashed_application_id()
        return CreditFair.run_auto_decision(application_id, bearer_token, client_id, client_secret)

    def add_job_for_da(self, from_value):
        payload = {
            "id": str(self.get_loan_application_id()),
            "status": "approved",
            "previous_status": "cf approved",
            "auth_user_id": 107,
            "lender_id": self.loan_application.lender_id,
            "documents": "[pro_forma_invoice, sanction_letter, cam]",
        }
        return JobAccessor.create_job(application_id=self.get_loan_application_id(),
                                      from_field=from_value,
                                      payload=json.dumps(payload), is_available=0)

    def get_current_address(self):
        return self.loan_application.current_address_line_1, self.loan_application.current_address_city, \
            self.loan_application.current_address_state, self.loan_application.current_address_pincode

    @classmethod
    def get_state_code(cls, state):
        return StateCodeAccessor.get_state_code(state).code_number

    def update_consent(self, latitude, longitude, status, user_ip, request):
        application_id = self.get_loan_application_id()
        status = LoanApplicationAccessor.update_consent(
            application_id, latitude=latitude, longitude=longitude, status=status, user_ip=user_ip)
        if util.is_status_failed(status):
            return status
        application = self.get_loan_application(loan_application_id=application_id,
                                                user_id=self.loan_application.user_id)
        if application.state == 'form completed' and request.path != '/v1/save-consent/':
            return LoanApplicationAccessor.update_state(application_id, self.IN_PROCESS,
                                                        self.loan_application.user_id)
        return api.SUCCESS

    def generate_consent_document(self, application_id):
        application = self.get_loan_application(loan_application_id=application_id,
                                                user_id=self.loan_application.user_id)
        consent_log_object = LoanApplicationAccessor.get_consent_log(application_id)
        merchant = Merchant.get_merchant(application.merchant_id)
        data = {
            'lender_id': application.lender_id,
            'name': application.name,
            'loan_application_no': f"{merchant.slug}0{application_id}",
            'mobile': User.decrypt(application.mobile_number),
            'ip': consent_log_object.ips if consent_log_object is not None else None,
            'consent_timestamp': get_now_ist_string(),
            'date': get_date_string_dd_mmm_yyyy(today()),
            'logo_url': 'http://creditfair-customer-documents.s3.ap-south-1.amazonaws.com/rebranded-logos/logo1.png'
        }
        consent_log_path = UserDocument.generate_pdf_from_html(
            f"""<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
                <html>
                <head>
                    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
                    <meta http-equiv="X-UA-Compatible" content="IE=edge">
                    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
                    <meta http-equiv="X-UA-Compatible" content="IE=8">
                    <style>
                        table.out {{
                            width: 100%;
                            margin-bottom: 30px;
                            font-size: 14px;
                            border-collapse: collapse;
                        }}

                        table.out-joint {{
                            width: 99.4%;
                            font-size: 23px;
                            border-collapse: collapse;
                            margin-bottom: -33px;
                            margin-left: 3px;
                            text-align: center;
                            font-weight: 800;
                        }}

                        table.out-joint > tbody > tr > td {{
                            padding: 2px 2px 2px 8px;
                            font-size: 20px;
                            border: 1px solid black;
                        }}

                        table.out-joint-loan {{
                            width: 100%;
                            font-size: 23px;
                            border-collapse: collapse;
                            margin-bottom: -31px;
                            text-align: center;
                            font-weight: 800;
                            margin-top: 25px;
                        }}

                        table.out-joint-loan > tbody > tr > td {{
                            padding: 2px 2px 2px 8px;
                            font-size: 20px;
                            border: 1px solid black;
                        }}

                        table.out > tbody > tr > td {{
                            padding: 10px 10px 10px 10px;
                            font-size: 15px;
                            border: 1px solid black;
                        }}

                        table.out > tbody > tr > th {{
                            padding: 2px 2px 2px 8px;
                            font-size: 15px;
                            border: 1px solid black;
                        }}

                        li {{
                            padding-top: 8px;
                        }}
                        .header{{
                            background-color: #0fc3cc;
                            padding: 5px;
                            color: white;
                            margin-top: 23px;
                        }}
                        .ft8{{
                            font-weight: 700;
                        }}
                        p.ft9{{
                            font-weight: 700;
                        }}
                        p.ft9 > span{{
                            font-weight: 100;
                        }}
                        p.ft10{{
                            font-weight: 700;
                        }}
                        p.ft10 > span{{
                            font-weight: 100;
                        }}
                    </style>
                </head>

                <body style="background-color: whitesmoke;">
                <div id="page_1">
                    <div id="p1dimg1">
                        <img src="{data['logo_url']}" style="outline:none;text-decoration:none;width:150px;padding-top:16px;padding-bottom:16px;margin-left:auto;margin-right:auto">
                        <div style="text-align: center;font-weight: 800;padding-top: 10px;padding-bottom: 10px; ">Consent Logs {data['name']} {data['date']}</div>

                            <table class="out">
                                    <tbody>
                                    <tr>
                                        <td><b>Loan Application No.</b></td>
                                        <td>{data['loan_application_no']}</td>
                                    </tr>
                                    <tr>
                                        <td><b>Name</b></td>
                                        <td>{data['name']}</td>
                                    </tr>
                                    <tr>
                                        <td><b>Mobile</b></td>
                                        <td>{data['mobile']}</td>
                                    </tr>
                                    <tr>
                                        <td><b>IP</b></td>
                                        <td>{data['ip']}</td>
                                    </tr>
                                    <tr>
                                        <td><b>Consent Timestamp</b></td>
                                        <td>{data['consent_timestamp']}</td>
                                    </tr>
                                    </tbody>
                            </table>
                    </div>
                </div>
                </body>
                </html>""", 'consent_logs')
        document_content = FileUtil.convert_to_base64(consent_log_path)
        s3_status, path = S3Accessor.upload_file(str(application_id) + '/consent_logs.pdf', document_content.decode("utf-8"))
        status = LoanApplicationFilesAccessor.create_loan_application_files(application_id, 'consent_logs', path, 1)
        if util.is_status_failed(status):
            return status
        return api.SUCCESS

    def get_vendor_id(self):
        return LoanApplicationAccessor.get_vendor_id(self.get_loan_application_id())

    def get_merchant_id(self):
        return self.loan_application.merchant_id

    def get_center(self):
        consumer_application_element = LoanApplicationAccessor.get_loan_application_element(
            self.get_loan_application_id())
        return MerchantCenter.get_center(
            self.get_merchant_id(), consumer_application_element.center_id)

    def get_merchant(self):
        return Merchant.get_merchant(self.get_merchant_id())

    def is_agreement_signed(self):
        return DigitalSignDetailsAccessor.is_application_signed(self.get_loan_application_id())

    @classmethod
    def get_aadhar_pdf_password(cls, name, dob_string):
        dob = datetime_util.get_date_object(dob_string)
        return f"{name[:4].upper()}{dob.year}"

    @classmethod
    def get_frequency_for_mandate_setup(cls, merchant_offers):
        if merchant_offers.processing_fee_first_emi == 1:
            return cls.FREQUENCY_ADHOC
        elif merchant_offers.processing_fee_first_emi == 0:
            return cls.FREQUENCY_MONTHLY

    def get_merchant_offers_for_application(self):
        return MerchantOffersAccessor.get_merchant_offer(self.get_loan_offer_id())

    def save_employment_pdf(self, pdf_link, bearer_token, client_id, client_secret):
        if not pdf_link:
            return api.UNABLE_TO_UPLOAD_EMPLOYMENT_DOCUMENT

        response = requests.get(pdf_link)
        if not (200 <= response.status_code < 300):
            return api.UNABLE_TO_UPLOAD_EMPLOYMENT_DOCUMENT

        status = UserDocument.upload_document(
            self.get_hashed_application_id(), UserDocument.EPF_REPORT, UserDocument.PDF,
            base64.b64encode(response.content).decode('utf-8'), '', bearer_token, client_id, client_secret)

        if util.is_status_failed(status):
            log.error('Unable to upload employment pdf')
            return api.UNABLE_TO_UPLOAD_EMPLOYMENT_DOCUMENT

        return api.SUCCESS

    def is_payment_required(self):
        emi_calculation = EmiCalculationAccessor.get_emi_calculation(self.get_loan_application_id())
        merchant = self.get_merchant()
        if not emi_calculation or not (self.is_collect_upfront_from_customer(
                emi_calculation, merchant) or self.is_collect_processing_fee(emi_calculation, merchant)):
            return False

        return True

    def is_payment_required2(self):
        emi_calculation = EmiCalculationAccessor.get_emi_calculation(self.get_loan_application_id())
        pro_forma = ProFormaInvoiceAccessor.get_pro_forma_invoice(self.get_loan_application_id())
        merchant = self.get_merchant()
        if not emi_calculation or not (self.is_collect_upfront_from_customer(
                emi_calculation, merchant) or self.is_collect_processing_fee(emi_calculation, merchant) or
                                       self.is_collect_insurance_fee(pro_forma, merchant)):
            return False

        return True

    @classmethod
    def is_collect_insurance_fee(cls, pro_forma, merchant):
        if float(pro_forma.insurance_premium_amount) != 0 and merchant.insurance_premium == 1:
            return True
        if float(pro_forma.insurance_premium_amount) == 0 and merchant.insurance_premium == 0:
            return False

    @classmethod
    def is_collect_upfront_from_customer(cls, emi_calculation, merchant):
        if emi_calculation.upfront_from_customer != 0 and merchant.collect_advance_amount == 1:
            return True
        if merchant.collect_advance_amount == 0 and emi_calculation.upfront_from_customer != 0:
            return False

    @classmethod
    def is_collect_processing_fee(cls, emi_calculation, merchant):
        if emi_calculation.processing_fee != 0 and merchant.is_collected == 0:
            return True
        if emi_calculation.processing_fee != 0 and merchant.is_collected == 1:
            return False

    @classmethod
    def get_lat_long(cls, loan_application_id):
        loan_application = LoanApplication.get_loan_application(hashed_loan_application_id=loan_application_id)
        geo_location = UserGeolocationAccessor.get_lat_long(loan_application.id)
        return geo_location.latitude, geo_location.longitude
