import logging
from datetime import date
import math

from django.conf import settings

from app.api import api
from app.aws.s3_accessor import S3Accessor
from app.loan_application.loan_application import LoanApplication
from app.loan_offer.accessor.disbursement_offers_accessor import DisbursementOffersAccessor
from app.loan_offer.accessor.disbursement_offers_disbursed_accessor import DisbursementOffersDisbursedAccessor
from app.loan_offer.emi_calculation_accessor import EmiCalculationAccessor
from app.loan_offer.loan_decision_table_accessor import LoanDecisionTableAccessor
from app.loan_offer.merchant_offers_accessor import MerchantOffersAccessor
from app.loan_offer.pro_forma_invoice_accessor import ProFormaInvoiceAccessor
from app.merchant.accessor.merchant_accessor import MerchantAccessor
from app.merchant.merchant import Merchant
from app.user.user import User
from app.util import util, datetime_util
from django.db import transaction as db_transaction
from app.encryption.aes128 import AES128

log = logging.getLogger(__name__)


class LoanOffer:
    def __init__(self, loan_offer):
        self.loan_offer = loan_offer

    @classmethod
    def get_applicable_offers(cls, loan_application):
        applicable_offers = LoanDecisionTableAccessor.get_applicable_loan_offers(
            LoanApplication(loan_application).get_loan_application_id())

        if not applicable_offers.exists():
            return api.LOAN_OFFERS_NOT_FOUND, None

        serialized_applicable_offers = []
        for applicable_offer in applicable_offers.values('tenure', 'offer_id', 'interest_rate', 'payable', 'emi',
                                                         'processing_fee', 'up_front_from_customer'):
            check_offer_valid = MerchantOffersAccessor.get_valid_merchant_offer(applicable_offer['offer_id'])

            if check_offer_valid:
                serialized_applicable_offers.append({
                    "offer_id": applicable_offer['offer_id'],
                    "tenure": applicable_offer['tenure'],
                    "emi": applicable_offer['emi'],
                    "interest_rate": applicable_offer['interest_rate'],
                    "total": applicable_offer['payable'],
                    "processing_fee": applicable_offer['processing_fee'],
                    "up_front_from_customer": applicable_offer['up_front_from_customer']
                })

        return api.SUCCESS, serialized_applicable_offers

    @classmethod
    def get_processing_fee(cls, merchant_offer, approved_amount):
        if merchant_offer.processing_fee_type == "fixed":
            return merchant_offer.processing_fee
        elif merchant_offer.processing_fee_type == "percentage":
            return (merchant_offer.processing_fee / 100) * approved_amount
        else:
            return merchant_offer.processing_fee

    def update_loan_offer(self, loan_app, origin):
        loan_application = LoanApplication(loan_app)
        if loan_application.is_agreement_signed():
            return api.AGREEMENT_ALREADY_SIGNED

        application_id = loan_app.id

        loan_decision = LoanDecisionTableAccessor.get_loan_offer(application_id, self.get_loan_offer_id())
        merchant_offer = MerchantOffersAccessor.get_merchant_offer(self.get_loan_offer_id())
        processing_fee = loan_decision.processing_fee
        upfront_from_customer = loan_decision.up_front_from_customer
        merchant_details = MerchantAccessor.get_merchant(loan_app.merchant_id)
        charge_subvention_remaining_amt = merchant_details.charge_subvention_remaining_amount

        upfront_from_cf = self.get_upfront_from_cf(
            loan_application, loan_decision, merchant_offer, upfront_from_customer, charge_subvention_remaining_amt)
        received_by_merchant = upfront_from_customer + upfront_from_cf
        payable_by_customer = loan_decision.payable

        subvention_cgst, subvention_sgst, subvention_igst = self.calculate_gst_on_subvention(
            upfront_from_customer, loan_application.get_amount(), merchant_offer, loan_application)
        # net_disbursement_amount = (
        #         upfront_from_cf - upfront_from_customer - subvention_igst + subvention_cgst + subvention_sgst)
        net_loan_amount = self.get_net_loan_amount(merchant_offer, loan_application)

        payment_frequency = merchant_offer.payment_frequency
        disbursement_method = merchant_offer.disbursement_method
        annualised_irr = merchant_offer.annualised_irr

        re_scheduled_by = loan_app.user_id
        approved_amount = loan_app.approved_amount

        emi_date = self.calculate_emi_date()
        with db_transaction.atomic():
            existing_saved_offer = EmiCalculationAccessor.get_emi_calculation(application_id)
            if existing_saved_offer:
                EmiCalculationAccessor.create_emi_calculation_lock_entry(existing_saved_offer, re_scheduled_by, approved_amount)

            status = EmiCalculationAccessor.update_or_create(
                application_id=application_id, loan_offer_id=self.get_loan_offer_id(), tenure=merchant_offer.tenure,
                interest_rate=loan_decision.interest_rate, processing_fee=processing_fee, emi=loan_decision.emi,
                remaining_emi=loan_decision.tenure, advance_emi=merchant_offer.advance_emi,
                subvention=merchant_offer.subvention, upfront_from_customer=upfront_from_customer, emi_date=emi_date,
                received_by_merchant=received_by_merchant, upfront_from_credit_fair=upfront_from_cf,
                payable_by_customer=payable_by_customer, subvention_cgst=subvention_cgst,
                subvention_sgst=subvention_sgst, subvention_igst=subvention_igst, annualised_irr=annualised_irr,
                net_loan_amount=net_loan_amount,
                payment_frequency=payment_frequency, disbursement_method=disbursement_method)

            if util.is_status_failed(status):
                return status

            status = loan_application.update_loan_offer(self.get_loan_offer_id(), loan_decision.tenure)
            if util.is_status_failed(status):
                db_transaction.set_rollback(True)
                return status

            status = self.update_pro_forma_invoice(loan_application)
            if util.is_status_failed(status):
                db_transaction.set_rollback(True)
                return status

            self.check_and_update_for_multiphase_offer(application_id)

            if origin in ['https://checkoutuat.creditfair.in', 'https://checkout.creditfair.in']:
                loan_application.add_job_for_da('emiCheckoutGenerateDocuments')
            else:
                loan_application.add_job_for_da('noTouchDocuments')

        return api.SUCCESS

    @classmethod
    def calculate_gst_on_subvention(cls, upfront_from_customer, approved_amount, offer, loan_application):
        merchant = Merchant.get_merchant(loan_application.get_merchant_id())
        merchant_location = Merchant.get_merchant_location(loan_application.get_merchant_id())

        subvention_cgst, subvention_sgst, subvention_igst = 0, 0, 0
        if merchant.charge_subvention_remaining_amount == 1:
            subvention_amount = ((approved_amount - upfront_from_customer) * offer.subvention) / 100
        else:
            subvention_amount = (approved_amount * offer.subvention) / 100
        if subvention_amount > 0:
            if merchant.is_gst == 1 and merchant.state:
                if merchant_location:
                    if merchant_location.gst_number and merchant_location.state:
                        state = merchant_location.state
                    else:
                        state = merchant.state
                else:
                    state = merchant.state

                gst_details = cls.get_gst_details(subvention_amount, state)

                if gst_details:
                    for gst in gst_details:
                        if gst['tax_type'] == "CGST":
                            subvention_cgst = gst['gst_amount']
                        elif gst['tax_type'] == "SGST":
                            subvention_sgst = gst['gst_amount']
                        else:
                            subvention_igst = gst['gst_amount']

        return subvention_cgst, subvention_sgst, subvention_igst

    @classmethod
    def get_gst_details(cls, amount, state):
        tax_detail = []
        if state == 'Maharashtra':
            taxes = {'CGST': 9, 'SGST': 9}
        else:
            taxes = {'IGST': 18}

        for tax in taxes.keys():
            # gst_amount = amount - (amount * (100 / (100 + (taxes[tax] * len(taxes)))))
            #
            # if len(taxes) > 1:
            #     d = 2 * round((gst_amount / len(taxes)), 2)
            # else:
            #     d = gst_amount / len(taxes)
            #
            # net_price = amount - d
            gst_amount = (amount * taxes[tax])/100
            tax_detail.append({
                'net_amount': round(amount, 2),
                'gst_amount': round(gst_amount),
                'amount': round(amount + gst_amount),
                'tax_type': tax
            })

        return tax_detail

    @classmethod
    def get_net_loan_amount(cls, merchant_offer, loan_application):
        net_loan_amount = 0
        approved_amount = loan_application.get_amount()
        if merchant_offer.advance_type == "emi":
            if approved_amount > 0 and approved_amount != "":
                net_loan_amount = (approved_amount * (
                        merchant_offer.tenure - merchant_offer.advance_emi)) / merchant_offer.tenure

        elif merchant_offer.advance_type == "fixed_amount":
            if approved_amount > 0 and approved_amount != "":
                net_loan_amount = approved_amount - merchant_offer.advance_emi

        elif merchant_offer.advance_type == "percentage":
            if approved_amount > 0 and approved_amount != "":
                net_loan_amount = (approved_amount * (100 - merchant_offer.advance_emi)) / 100

        return net_loan_amount

    @classmethod
    def get_upfront_from_customer(cls, loan_application, loan_decision, merchant_offer):
        if merchant_offer.advance_type == "percentage":
            upfront_from_customer = merchant_offer.advance_emi / 100 * loan_application.get_amount()
        elif merchant_offer.advance_type == "emi":
            upfront_from_customer = loan_decision.emi * merchant_offer.advance_emi
        else:
            upfront_from_customer = merchant_offer.advance_emi

        return upfront_from_customer

    @classmethod
    def get_upfront_from_cf(cls, loan_application, loan_decision, merchant_offer, upfront_from_customer, charge_subvention_remaining_amt):
        approved_amount = loan_application.get_amount()
        down_payment = 0
        if charge_subvention_remaining_amt == 1:
            down_payment = upfront_from_customer
        if merchant_offer.interest_type_id == 1:
            subvention = ((approved_amount - down_payment) * merchant_offer.subvention) / 100
            if merchant_offer.interest_rate:
                return approved_amount - upfront_from_customer - subvention
            else:
                upfront_from_credit_fair = (approved_amount * (loan_decision.tenure / merchant_offer.tenure)) - (
                        ((approved_amount - upfront_from_customer) * merchant_offer.subvention) / 100)

                if merchant_offer.advance_type == 'percentage' and upfront_from_customer and upfront_from_credit_fair:
                    return upfront_from_credit_fair - upfront_from_customer

                return upfront_from_credit_fair

        elif merchant_offer.interest_type_id == 2:
            return approved_amount - upfront_from_customer - (
                    merchant_offer.subvention * (approved_amount - down_payment)) / 100

        elif merchant_offer.interest_type_id == 3:
            if merchant_offer.interest_rate:
                return approved_amount - upfront_from_customer - (
                        (approved_amount - down_payment) * merchant_offer.subvention) / 100
            else:
                return (approved_amount * (loan_decision.tenure / merchant_offer.tenure) - (
                        (approved_amount - down_payment) * merchant_offer.subvention) / 100)

        elif merchant_offer.interest_type_id == 4:
            return approved_amount - upfront_from_customer - (
                    ((approved_amount - down_payment) * merchant_offer.subvention) / 100)

    @classmethod
    def get_loan_offer_details(cls, loan_application):
        loan_application = LoanApplication(loan_application)
        application_id = loan_application.get_loan_application_id()
        emi_calculation = EmiCalculationAccessor.get_emi_calculation(application_id)
        if not emi_calculation:
            log.error('EMI Calculation entry missing')
            return api.EMI_CALCULATIONS_NOT_FOUND, None

        product_details = loan_application.get_serialized_product_details()
        status, emi_summary = cls.get_emi_calculations(application_id, emi_calculation)
        if util.is_status_failed(status):
            return api.EMI_CALCULATIONS_NOT_FOUND, None

        fees = cls.get_fees(emi_calculation)

        _, download_link = cls.get_agreement_download_link(loan_application)

        return api.SUCCESS, {
            "emi_summary": emi_summary,
            "fees": fees,
            "product_details": product_details,
            "agreement_download_link": download_link
        }

    @classmethod
    def get_loan_offer(cls, loan_application_id, loan_offer_id):
        return LoanDecisionTableAccessor.get_loan_offer(loan_application_id, loan_offer_id)

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

    @classmethod
    def get_emi_calculations(cls, application_id, emi_calculation):
        loan_decision = LoanDecisionTableAccessor.get_loan_offer(application_id, emi_calculation.offer_id)
        if not loan_decision:
            log.error('Loan decision entry missing')
            return api.EMI_CALCULATIONS_NOT_FOUND, None

        return api.SUCCESS, {
            "tenure": loan_decision.tenure,
            "emi": loan_decision.emi,
            "interest_rate": loan_decision.interest_rate,
            "total": loan_decision.payable
        }

    @classmethod
    def get_fees(cls, emi_calculation):
        return [
            {
                "type": "processing_fee",
                "amount": emi_calculation.processing_fee,
            },
            {
                "type": "advance_fee",
                "amount": emi_calculation.upfront_from_customer,
            }
        ]

    @classmethod
    def get_emi_calculation(cls, loan_application_id):
        return EmiCalculationAccessor.get_emi_calculation(loan_application_id)

    @classmethod
    def calculate_emi_date(cls):
        today = datetime_util.today()
        if today.day > 20:
            if today.month + 1 > 12:
                return date(today.year + 1, 2, 5)
            elif today.month + 1 > 11:
                return date(today.year + 1, 1, 5)
            else:
                return date(today.year, today.month + 2, 5)
        else:
            if today.month + 1 > 12:
                return date(today.year + 1, 1, 5)
            return date(today.year, today.month + 1, 5)

    @classmethod
    def update_pro_forma_invoice(cls, loan_application):
        # TODO Optimize this
        user_name = User(loan_application.get_user()).get_name()
        center = loan_application.get_center()
        merchant = loan_application.get_merchant()
        the_merchant = Merchant(merchant)
        merchant_location = the_merchant.get_merchant_location(the_merchant.get_merchant_id())
        application_details = loan_application.get_loan_application(loan_application.get_loan_application_id())
        approved_amount = application_details.approved_amount
        if center and the_merchant.has_merchant_location_bank(merchant_location.id):
            gst_number = merchant_location.gst_number
            if merchant_location.gst_number:
                if len(merchant_location.gst_number) > 16:
                    gst_number = AES128.aes_cbc_decrypt(merchant_location.gst_number)
        else:
            gst_number = the_merchant.get_gst_number()
            if the_merchant.get_gst_number():
                if len(the_merchant.get_gst_number()) > 16:
                    gst_number = AES128.aes_cbc_decrypt(the_merchant.get_gst_number())

        center_name = merchant_location.name if center else None

        processing_fee_by_merchant = 1 if merchant.is_collected == 1 else 0
        source = application_details.updated_by
        merchant_collected_advance_amount = 0 if merchant.collect_advance_amount == 0 else 1
        insurance_amount_applicable_by_merchant, insurance_amount = 0, 0
        if source == "No touch":
            insurance_amount_applicable_by_merchant = 0 if merchant.insurance_premium == 0 else 1

            insurance_amount = cls.calculate_insurance_amount(
                approved_amount) if insurance_amount_applicable_by_merchant == 1 else 0
        return ProFormaInvoiceAccessor.update_pro_forma_invoice(
            loan_application.get_loan_application_id(), the_merchant.get_slug(), gst_number, center_name, user_name,
            merchant_collected_advance_amount, processing_fee_by_merchant, insurance_amount_applicable_by_merchant,
            insurance_amount)

    @classmethod
    def get_agreement_download_link(cls, loan_application):
        document_s3_path = loan_application.get_agreement_s3_path()
        if not document_s3_path:
            return None, None
        return S3Accessor.get_document_download_link(document_s3_path, settings.AGREEMENT_DOWNLOAD_EXPIRY_SECS)

    def check_and_update_for_multiphase_offer(self, application_id):
        disbursement_offers = DisbursementOffersAccessor.get_disbursement_offers(self.get_loan_offer_id())

        DisbursementOffersDisbursedAccessor.soft_delete_existing_entries(application_id=application_id)

        if disbursement_offers.count() <= 1:
            return

        log.debug('Creating disbursement offers disbursed')
        for disbursement_offer in disbursement_offers:
            DisbursementOffersDisbursedAccessor.create_disbursement_offers_disbursed(
                offer_id=self.get_loan_offer_id(), application_id=application_id, phase=disbursement_offer.phase,
                per_disbursement_amount=disbursement_offer.per_disbursement_amount, status=disbursement_offer.status,
                day_from_disbursement_date=disbursement_offer.day_from_disbursement_date)

    @classmethod
    def calculate_insurance_amount(cls, loan_amount):
        discounted_loan_amount = round((loan_amount * (0.125 / 100)), 2)
        vgst = round(discounted_loan_amount * 0.18, 2)
        insurance_amount = discounted_loan_amount + vgst
        return insurance_amount
