from ajabsacco.core.models import (
    LoanTransactionEntry, 
    LoanProduct, 
    LoanAccount,
    Message as SMSMessage, 
)
from decimal import Decimal as D

from django.db.models import Q, Sum, F

from django.utils import timezone
from django.db import transaction as db_transaction

from ajabsacco.core import codes
from ajabsacco.core.sms import templates
from ajabsacco.core.utils import record_log, month_delta
from ajabsacco.core.exceptions import *

import ledger as ledger_facades
from ajabsacco.core.facades import transactions as transaction_facades
from ajabsacco.core.facades.loans import validations as validation_facades

import logging
logger = logging.getLogger('core.ajabsacco.loans')

import uuid

def allocate_repayment(loan_account, amount, *args, **kwargs):
    with db_transaction.atomic():
        fee_accrued = ledger_facades.loan_account_fees_due(loan_account)
        penalties_accrued = ledger_facades.loan_account_penalties_due(loan_account)
        interest_accrued = ledger_facades.loan_account_interest_due(loan_account)
        principal_accrued = ledger_facades.loan_account_principal_due(loan_account)

        #1. Align the order we will deduct the accruals
        accruals = {
            LoanProduct.ALLOCATION_CHOICE_FEE: (fee_accrued or D('0.0'), post_loan_fee),
            LoanProduct.ALLOCATION_CHOICE_PENALTY: (penalties_accrued or D('0.0'), post_penalty_fee),
            LoanProduct.ALLOCATION_CHOICE_INTEREST: (interest_accrued or D('0.0'), post_loan_interest),
        }

        amount = (amount or 0)
        #get a sum of all accruals
        total_accruals = sum(i[0] for i in accruals.values())

        #Ensure we have sane values
        if (amount > 0) and (principal_accrued > 0):
            transaction_id = uuid.uuid4()

            #setup allocation balance, to help us check to total allocation
            allocation_balance = amount

            if total_accruals > 0:
                items_allocated = 0
                #Loop through the allocation order
                for allocation_item in LoanProduct.ALLOCATION_ORDER:
                    #Loop through all the accruals we are expecting to collect
                    for accrued_item, allocation_tuple in accruals.iteritems():
                        #put aside the variables from the turple
                        accrued_amount, transaction_func = allocation_tuple

                        #if allocation item is equal to accrued item code, and accrued amount is more than 1
                        #Check to ensure we do not get to negative numbers
                        if (allocation_item == accrued_item) and (accrued_amount > 0) and (allocation_balance > 0):
                            #if amount accrued is sizable, deduct
                            transaction_func(loan_account, accrued_amount, transaction_id=transaction_id)

                            #stamp new allocation
                            items_allocated += 1

                            #deduct amount posted from balance
                            allocation_balance -= accrued_amount

            post_loan_principal(
                loan_account, 
                allocation_balance, 
                transaction_id=transaction_id
            )

            loan_account.last_repayment_date = timezone.now()
            loan_account.save()
        else:
            logger.debug("amount: %s and total_accruals %s" % (amount, total_accruals))
            raise AmountNotSufficientException()

def apply_accruals(loan_account, approved_by=None):
    with db_transaction.atomic():
        date_disbursed = loan_account.date_disbursed

        if date_disbursed is None:
            raise ActionOnUndisbursedLoanException(
                "You cannot apply accruals on Un-disbursed Loan %s" % loan_account.account_number)

        date_today = timezone.now()
        month_diff = month_delta(date_disbursed, date_today, days_of_the_month=30)
        
        grace_period = loan_account.grace_period
        grace_period_type = loan_account.product.grace_period_type

        within_grace_period = ((month_diff - grace_period) < 1)
        within_repayment_period = ((month_diff - loan_account.repayment_period) < 1)

        if (not grace_period == 0): 
            if (within_grace_period):
                if (grace_period_type == LoanProduct.FULL_GRACE_PERIOD):
                    #No need to proceed, we don't want to accrue anything 
                    interest_due = 0
                elif (grace_period_type == LoanProduct.PRINCIPAL_GRACE_PERIOD):
                    principal_due = loan_account.amount
                    interest_due = (
                        (principal_due * loan_account.interest_rate) /
                        loan_account.repayment_period
                    )
        else:
            if within_repayment_period:
                principal_due = ledger_facades.loan_account_principal_due(loan_account)
                interest_due = (
                    (principal_due * (loan_account.interest_rate / D('100.0'))) /
                    loan_account.repayment_period
                )
                
        if interest_due > 0:
            apply_interest_to_account(loan_account, interest_due)

def disburse_loan(loan_account, *args, **kwargs):
    with db_transaction.atomic():
        validation_facades.validate_disbursement(loan_account)

        debit_entry, credit_entry = transaction_facades.create_transaction(
            LoanTransactionEntry, loan_account, loan_account.amount, 
            transaction_type=codes.TRANSACTION_TYPE_LOAN_DISBURSAL, *args, **kwargs
        )

        loan_account.status = LoanAccount.ACTIVE
        loan_account.date_disbursed = timezone.now()

        loan_account.save()

        return (debit_entry, credit_entry)

def apply_interest_to_account(loan_account, amount, *args, **kwargs):
    return transaction_facades.create_transaction(LoanTransactionEntry, loan_account, amount, 
        transaction_type=codes.TRANSACTION_TYPE_INTEREST_APPLY, *args, **kwargs)

def apply_fee_to_account(loan_account, amount, *args, **kwargs):
    return transaction_facades.create_transaction(LoanTransactionEntry, loan_account, amount, 
        transaction_type=codes.TRANSACTION_TYPE_FEE_APPLY, *args, **kwargs)

def apply_penalty_to_account(loan_account, amount, *args, **kwargs):
    return transaction_facades.create_transaction(LoanTransactionEntry, loan_account, amount, 
        transaction_type=codes.TRANSACTION_TYPE_PENALTY_APPLY, *args, **kwargs)

def write_off_loan_principal(loan_account, amount, *args, **kwargs):
    return transaction_facades.create_transaction(LoanTransactionEntry, loan_account, amount, 
        transaction_type=codes.TRANSACTION_TYPE_PRINCIPAL_WRITE_OFF, *args, **kwargs)

def write_off_loan_interest(loan_account, amount, *args, **kwargs):
    return transaction_facades.create_transaction(LoanTransactionEntry, loan_account, amount, 
        transaction_type=codes.TRANSACTION_TYPE_INTEREST_WRITE_OFF, *args, **kwargs)

def write_off_loan_penalty(loan_account, amount, *args, **kwargs):
    return transaction_facades.create_transaction(LoanTransactionEntry, loan_account, amount, 
        transaction_type=codes.TRANSACTION_TYPE_PENALTY_WRITE_OFF, *args, **kwargs)

def write_off_loan_fee(loan_account, amount, *args, **kwargs):
    return transaction_facades.create_transaction(LoanTransactionEntry, loan_account, amount, 
        transaction_type=codes.TRANSACTION_TYPE_FEE_WRITE_OFF, *args, **kwargs)
    
def post_loan_principal(loan_account, amount, *args, **kwargs):
    return transaction_facades.create_transaction(LoanTransactionEntry, loan_account, amount, 
        transaction_type=codes.TRANSACTION_TYPE_PRINCIPAL_POSTING, *args, **kwargs)

def post_loan_interest(loan_account, amount, *args, **kwargs):
    return transaction_facades.create_transaction(LoanTransactionEntry, loan_account, amount, 
        transaction_type=codes.TRANSACTION_TYPE_INTEREST_POSTING, *args, **kwargs)

def post_loan_fee(loan_account, amount, *args, **kwargs):
    return transaction_facades.create_transaction(LoanTransactionEntry, loan_account, amount, 
        transaction_type=codes.TRANSACTION_TYPE_FEE_POSTING, *args, **kwargs)

def post_penalty_fee(loan_account, amount, *args, **kwargs):
    return transaction_facades.create_transaction(LoanTransactionEntry, loan_account, amount, 
        transaction_type=codes.TRANSACTION_TYPE_PENALTY_POSTING, *args, **kwargs)
