from django.db.models import Q
from .models import OrganisationSubAccount, SystemTransactions, CashAccounts, BankAccounts, MobileMoneyAccounts, SafeAccounts, InterBranchTransactions, InterBranchMapping, CashTransfers
from savings.models import SavingAccountTransactions, GroupSavingTransaction, FixedDeposit, FixedDepositSchedule, TransferTransactions
from shares.models import SharesTransaction
from django.db.models import Sum
from datetime import datetime
from django.utils import timezone
from organisations.models import OrganisationBranch
from .data import system_define_ledgers
from users.serializers import AuditTransactionsSerializer
from users.audit_log_helper import add_system_audit_trail

def _audit(user_id, branch, action, message):
    """Fire audit trail silently for a deletion."""
    try:
        if user_id and branch:
            from users.models import User
            user = User.objects.filter(pk=user_id).first()
            if user:
                add_system_audit_trail('transaction_management', action, message, '', {}, {}, user, branch)
    except Exception:
        pass

def generate_chart_of_account_code(parent_id, account_line, organisation_id):
    account_code = ''
    if(parent_id > 0):
        last_child = OrganisationSubAccount.objects.filter(parent_id=parent_id).order_by('-id')[:1]
        if(len(last_child) > 0):
            temp_code = last_child[0].account_code.split('-')
            if len(temp_code) > 1:
                account_code = int(temp_code[1]) + 1
            else:
                account_code = int(temp_code[0]) + 1
            account_code = change_code_octate(account_code)
        else:
            parent = OrganisationSubAccount.objects.get(pk=parent_id)
            temp_code = parent.account_code.split('-')
            if len(temp_code) > 1:
                account_code = str(temp_code[1]) + '1'
            else:
                account_code = str(temp_code[0]) + '1'
    else:
        children = OrganisationSubAccount.objects.filter(account_line=account_line, account_organisation=organisation_id, parent_id__isnull=True).order_by('-id')[:1]
        if(len(children) > 0):
            temp_code = children[0].account_code.split('-')
            if len(temp_code) > 1:
                account_code = int(temp_code[1]) + 1
            else:
                account_code = int(temp_code[0]) + 1
            account_code = change_code_octate(account_code)
        else:
            account_line_code = get_account_line_code(account_line)
            account_code = str(account_line_code) + '1'
    return account_code

#Change code octate if ending with zero to avoid duplication
def change_code_octate(code):
    if code % 10 == 0:
        code = code - 10
        code = code * 10 + 1
    return code

def get_account_lines():
    return ['assets', 'liabilities', 'capital', 'income', 'expenses']

def get_income_statement_lines():
    return ['income', 'expenses']

def get_balance_sheet_lines():
    return ['assets', 'capital', 'liabilities']

def get_account_line_code(account_line):
    codes = {
        'assets': 1,
        'liabilities': 2,
        'capital': 3,
        'income': 4,
        'expenses': 5
    }

    return codes[account_line]

def get_chart_of_account_by_code(account_code, organisation):
    account = None
    accounts = OrganisationSubAccount.objects.filter(account_code=account_code, account_organisation=organisation)
    if(len(accounts) > 0):
        account = accounts[0]

    return account


def populate_system_generalted_charts_of_accounts(organisation, user_id):
    for key in system_define_ledgers:
        for ledger in system_define_ledgers[key]:
            account_name = ledger[0]
            account_code = ledger[1]
            account_parent = ledger[2]

            account = get_chart_of_account_by_code(account_code, organisation)
            if account:
                parent = get_chart_of_account_by_code(account_parent, organisation)
                account.account_code = account_code
                account.account_name = account_name
                if parent:
                    account.parent_id = parent
            else:
                parent = get_chart_of_account_by_code(account_parent, organisation)
                account = OrganisationSubAccount(account_organisation=organisation, status='active', account_code=account_code, account_name=account_name, account_line=key, added_by=user_id)
                if parent:
                    account.parent_id=parent

            account.save()

def get_account_line_keys(account_line):
    keys = {
        'assets': 'ast',
        'liabilities': 'lbt',
        'capital': 'cpt',
        'income': 'inc',
        'expenses': 'exp'
    }

    return keys[account_line]

def pad_reference_number(number):
    number = str(number)
    leading_zeros = 5 - len(number)
    if leading_zeros > 0:
        number = '0' * leading_zeros + number
    
    return number

def generate_reference_no(line, organisation_id, prefix = None):
    now = datetime.now()
    if not prefix:
        base = get_account_line_keys(line) + '-' + now.strftime("%y%m") + '-'
    else:
        base = prefix + '-' + now.strftime("%y%m") + '-'
    
    last_reference = SystemTransactions.objects.filter(branch__branch_organisation__id=organisation_id, reference_no__icontains=base).order_by('-id')
    new_ref = 1
    if(len(last_reference) > 0):
        new_ref = int(last_reference[0].reference_no.split('-')[-1]) + 1

    return base + pad_reference_number(new_ref)

def sort_key(sub_account):
    sort_criteria = ['assets', 'liabilities', 'capital', 'income', 'expenses']
    return sort_criteria.index(sub_account.account_line)

def get_transactional_charts(organisation_id, account_line=None, parent_code=None):
    if account_line:
        lines = account_line.split(',')
        transactional_charts = OrganisationSubAccount.objects.filter(
            account_line__in=lines,
            account_organisation_id=organisation_id,
            parent_id_id__gte=1,
            child_accounts__isnull=True,
            deleted=False,
        ).order_by('id')
    else:
        transactional_charts = OrganisationSubAccount.objects.filter(
            account_organisation_id=organisation_id,
            parent_id_id__gte=1,
            child_accounts__isnull=True,
            deleted=False,
        ).order_by('id')
        
        # Sort charts.
        transactional_charts = sorted(transactional_charts, key=sort_key)

    if parent_code:
        code_split = parent_code.split('-')
        transactional_charts = transactional_charts.filter(Q(account_code__startswith=code_split[-1]) | Q(account_code__startswith=parent_code))

    return transactional_charts
    
def get_chart_of_account_balance_at(chart, branch_id, as_at=None):
    balances = {
        'debits': 0,
        'credits': 0
    }
    branch_id =  str(branch_id).split(',')
    if isinstance(chart, OrganisationSubAccount):
        account = chart
    else:
        account = OrganisationSubAccount.objects.get(pk=chart)

    if as_at:
        as_at = as_at.split(' ')[0]
        debits = SystemTransactions.objects.filter(transaction_type='normal', deleted=False, branch_id__in=branch_id, debit_chart=account, record_date__date__lt=as_at).aggregate(total=Sum('amount'))['total']
    else:
        debits = SystemTransactions.objects.filter(transaction_type='normal', deleted=False, branch_id__in=branch_id, debit_chart=account).aggregate(total=Sum('amount'))['total']
    if debits:
        balances['debits'] = round(debits, 3)

    if as_at:
        as_at = as_at.split(' ')[0]
        credits = SystemTransactions.objects.filter(transaction_type='normal', deleted=False, branch_id__in=branch_id, credit_chart=account, record_date__date__lt=as_at).aggregate(total=Sum('amount'))['total']
    else:
        credits = SystemTransactions.objects.filter(transaction_type='normal', deleted=False, branch_id__in=branch_id, credit_chart=account).aggregate(total=Sum('amount'))['total']
    if credits:
        balances['credits'] = round(credits, 3)

    if account.account_line in ['income', 'liabilities', 'capital']:
        balances['balance'] = f"{round(balances['credits'] - balances['debits'], 3):,}"
        balances['balance_raw'] = round(balances['credits'] - balances['debits'], 3)
    else:
        balances['balance'] = f"{round(balances['debits'] - balances['credits'], 3):,}"
        balances['balance_raw'] = round(balances['debits'] - balances['credits'], 3)

    return balances

def get_chart_of_account_balance_between(chart, start, end, branch_id):
    end = end.split(' ')[0]
    start = start.split(' ')[0]
    branch_id =  str(branch_id).split(',')
    balances = {
        'debits': 0,
        'credits': 0
    }
    if isinstance(chart, OrganisationSubAccount):
        account = chart
    else:
        account = OrganisationSubAccount.objects.get(pk=chart)

    debits = SystemTransactions.objects.filter(transaction_type='normal', deleted=False, branch_id__in=branch_id, debit_chart=account, record_date__date__gte=start, record_date__date__lte=end).aggregate(total=Sum('amount'))['total']
    if debits:
        balances['debits'] = round(debits, 3)
    
    credits = SystemTransactions.objects.filter(transaction_type='normal', deleted=False, branch_id__in=branch_id, credit_chart=account, record_date__date__gte=start, record_date__date__lte=end).aggregate(total=Sum('amount'))['total']
    if credits:
        balances['credits'] = round(credits, 3)

    if account.account_line in ['income', 'liabilities', 'capital']:
        balances['balance'] = f"{round(balances['credits'] - balances['debits'], 3):,}"
        balances['balance_raw'] = round(balances['credits'] - balances['debits'], 3)
    else:
        balances['balance'] = f"{round(balances['debits'] - balances['credits'], 3):,}"
        balances['balance_raw'] = round(balances['debits'] - balances['credits'], 3)

    return balances

def get_account_branch(account_id):

    # Check if cash account.
    account = CashAccounts.objects.filter(chart_id=account_id)
    if len(account) > 0:
        return account[0].teller.user_organisation_branch.id

    # Check if bank account.
    account = BankAccounts.objects.filter(chart_id=account_id)
    if len(account) > 0:
        return account[0].branch.id

    # Check if mobile account.
    account = MobileMoneyAccounts.objects.filter(chart_id=account_id)
    if len(account) > 0:
        return account[0].branch.id

    # Check if safe account.
    account = SafeAccounts.objects.filter(chart_id=account_id)
    if len(account) > 0:
        return account[0].branch.id

    return 'non_cash_account'

def get_moeny_account_type(account_id):

    # Check if cash account.
    account = CashAccounts.objects.filter(chart_id=account_id)
    if len(account) > 0:
        return 'cash'

    # Check if bank account.
    account = BankAccounts.objects.filter(chart_id=account_id)
    if len(account) > 0:
        return 'bank'

    # Check if mobile account.
    account = MobileMoneyAccounts.objects.filter(chart_id=account_id)
    if len(account) > 0:
        return 'mobile_money'

    # Check if safe account.
    account = SafeAccounts.objects.filter(chart_id=account_id)
    if len(account) > 0:
        return 'cash'

    return 'non_money_account'

def post_transaction(details):
    credit_chart = OrganisationSubAccount.objects.get(pk=details['credit_chart_id'])

    # Generate reference number
    reference_no = generate_reference_no(credit_chart.account_line, details['organisation_id'], details['ref_no_prefix'])

    # Save transaction
    transaction = SystemTransactions(amount=details['amount'], heading=details['heading'], record_date=details['record_date'], payment_method=details['payment_method'],voucher_no=details['voucher_no'], reference_no=reference_no, debit_chart_id=details['debit_chart_id'], credit_chart_id=details['credit_chart_id'], branch_id=details['branch_id'], added_by_id=details['user_id'])
    transaction.save()

    return transaction



def reverse_transaction(transaction, user_id,comment=None):
    if not isinstance(transaction, SystemTransactions):
        transaction = SystemTransactions.objects.get(pk=transaction)
    
    if transaction.transaction_type != 'normal':
        return transaction
    
    # Update original transaction status
    transaction.transaction_type = 'reversed'
    transaction.save()

    # Create reversal transaction
    transaction_details = {
        "heading": transaction.heading + ' - reversal',
        "amount": transaction.amount,
        "debit_chart_id": transaction.credit_chart.id,
        "credit_chart_id": transaction.debit_chart.id,
        "payment_method": transaction.payment_method,
        "voucher_no": transaction.voucher_no,
        "branch_id": transaction.branch.id,
        "reference_no": 'rev-' + transaction.reference_no,
        "transaction_type": 'reversal',
        "added_by_id": user_id,
        "coment": comment,
        "record_date": transaction.record_date
    }

    # Save general transaction
    transaction = SystemTransactions(**transaction_details)
    transaction.save()

    return transaction

def update_transaction(transaction, details):
    if not isinstance(transaction, SystemTransactions):
        transaction = SystemTransactions.objects.get(pk=transaction)
    
    if transaction.transaction_type != 'normal':
        return transaction
    
    # Update original transaction status
    if 'amount' in details.keys():
        transaction.amount = details['amount']

    if 'record_date' in details.keys():
        transaction.record_date = details['record_date']

    if 'coment' in details.keys():
        transaction.coment = details['coment']
        transaction.heading = details['coment']
        
    transaction.save()

    return transaction

def update_interbranch_transaction(transaction_id, details):
    destination_transaction = InterBranchTransactions.objects.filter(source_transaction_id=transaction_id)
    if len(destination_transaction) > 0:
        update_transaction(destination_transaction[0].destination_transaction, details)
    else:
        source_transaction = InterBranchTransactions.objects.filter(destination_transaction_id=transaction_id)
        if len(source_transaction) > 0:
            update_transaction(source_transaction[0].source_transaction, details)

def delete_transaction(transaction, user_id=None, branch=None):
    from django.utils import timezone
    from users.audit_log_helper import add_system_audit_trail

def _audit(user_id, branch, action, message):
    """Fire audit trail silently for a deletion."""
    try:
        if user_id and branch:
            from users.models import User
            user = User.objects.filter(pk=user_id).first()
            if user:
                add_system_audit_trail('transaction_management', action, message, '', {}, {}, user, branch)
    except Exception:
        pass
    if not isinstance(transaction, SystemTransactions):
        transaction = SystemTransactions.objects.get(pk=transaction)
    transaction.deleted = True
    transaction.deleted_at = timezone.now()
    if user_id:
        transaction.deleted_by_id = user_id
    transaction.save()
    if user_id and branch:
        try:
            from users.models import User
            user = User.objects.filter(pk=user_id).first()
            if user:
                add_system_audit_trail('transaction_management', 'delete_transaction', f'Deleted transaction: {transaction.heading}', '', {}, {}, user, branch)
        except Exception:
            pass
    return 'deleted'

def reverse_interbranch_transaction(transaction_id, user_id):
    destination_transaction = InterBranchTransactions.objects.filter(source_transaction_id=transaction_id)
    
    if len(destination_transaction) > 0:
        reverse_transaction(destination_transaction[0].destination_transaction, user_id)
    else:
        source_transaction = InterBranchTransactions.objects.filter(destination_transaction_id=transaction_id)
        if len(source_transaction) > 0:
            reverse_transaction(source_transaction[0].source_transaction, user_id)

def delete_interbranch_transaction(transaction_id, user_id=None, branch=None):
    from django.utils import timezone
    now = timezone.now()
    df = dict(deleted=True, deleted_at=now)
    if user_id:
        df['deleted_by_id'] = user_id

    source_transaction = InterBranchTransactions.objects.filter(destination_transaction_id=transaction_id)
    if len(source_transaction) > 0:
        trans = source_transaction[0].source_transaction
        SystemTransactions.objects.filter(id=trans.id).update(**df)
        source_transaction[0].deleted = True
        source_transaction[0].deleted_at = now
        if user_id:
            source_transaction[0].deleted_by_id = user_id
        source_transaction[0].save()
        _audit(user_id, branch, 'delete_transaction', f'Deleted inter-branch transaction: {trans.heading}')
    else:
        destination_transaction = InterBranchTransactions.objects.filter(source_transaction_id=transaction_id)
        if len(destination_transaction) > 0:
            trans = destination_transaction[0].destination_transaction
            SystemTransactions.objects.filter(id=trans.id).update(**df)
            destination_transaction[0].deleted = True
            destination_transaction[0].deleted_at = now
            if user_id:
                destination_transaction[0].deleted_by_id = user_id
            destination_transaction[0].save()
            _audit(user_id, branch, 'delete_transaction', f'Deleted inter-branch transaction: {trans.heading}')

def reverse_savings_transaction(transaction_data, user_id, branch, comment, reverse_interbranch=True):
    # reverse account related transactions
    account_transaction = SavingAccountTransactions.objects.get(pk=transaction_data['transaction_details']['id'])
    account_reversal = SavingAccountTransactions()

    # Reverse charges
    transaction_charges = SavingAccountTransactions.objects.filter(parent_id=account_transaction.id)
    for charge in transaction_charges:
        charge_reversal = SavingAccountTransactions()
        charge_reversal.transaction = reverse_transaction(charge.transaction, user_id, comment)
        charge_reversal.customer_account = account_transaction.customer_account
        charge_reversal.transaction_type = charge.transaction_type + '_reversal'
        charge_reversal.save()
        message = f'Reversed Saving {charge.transaction_type.replace("_", " ")} For {charge.customer_account.account_customer.name.capitalize()} Mem No: {charge.customer_account.account_customer.member_number}'
        old_details = AuditTransactionsSerializer(charge.transaction).data
        add_system_audit_trail('transaction_management','reverse_saving_transaction_charge',message,comment,old_details,{},user_id,branch)
                
        charge.transaction_type = charge.transaction_type + '_reversed'
        reversed_charge = charge.save()
        if reversed_charge:
            group_savings = GroupSavingTransaction.objects.filter(savings=reversed_charge).first()
            if group_savings:
                group_savings.payment_status = 'reversed'
                group_savings.save()


        # Reverse InterBranch Charge
        reverse_interbranch_transaction(charge.transaction.id, user_id)

    # Reverse main transaction
    
    account_reversal.transaction_type = account_transaction.transaction_type + '_reversal'
    account_reversal.transaction = reverse_transaction(account_transaction.transaction, user_id, comment)
    account_reversal.customer_account = account_transaction.customer_account
    account_reversal.save()

    # Update original account type
    message = f'Reversed Saving {account_transaction.transaction_type.replace("_", " ")} For {account_transaction.customer_account.account_customer.name.capitalize()} Mem No: {account_transaction.customer_account.account_customer.member_number}' 
    old_details = AuditTransactionsSerializer(account_transaction.transaction).data
    add_system_audit_trail('transaction_management','reverse_saving_transaction',message,comment,old_details,{},user_id,branch)
         
    account_transaction.transaction_type = account_transaction.transaction_type + '_reversed'
    reversed_transaction = account_transaction.save()
    if reversed_transaction:
        group_savings = GroupSavingTransaction.objects.filter(savings=reversed_transaction).first()
        if group_savings:
            group_savings.payment_status = 'reversed'
            group_savings.save()

    # Reverse InterBranch Related Transaction
    if reverse_interbranch:
        reverse_interbranch_transaction(account_transaction.transaction.id, user_id)

    return account_reversal.transaction

def reverse_shares_transaction(transaction_data, user_id, branch, comment, reverse_interbranch=True):
    from django.utils import timezone
    df = dict(deleted=True, deleted_by_id=user_id, deleted_at=timezone.now())
    shares_transaction = SharesTransaction.objects.get(pk=transaction_data['transaction_details']['id'])
    if shares_transaction:
        message = f'Reverse Share For {shares_transaction.shareholder.customer.name} Mem No: {shares_transaction.shareholder.customer.member_number}'
        old_details = AuditTransactionsSerializer(shares_transaction.system_transaction).data
        add_system_audit_trail('transaction_management', 'reverse_share_transaction', message, comment, old_details, {}, user_id, branch)
    account_transaction = SavingAccountTransactions.objects.filter(transaction_id=transaction_data['id']).first()
    if account_transaction:
        SharesTransaction.objects.filter(pk=shares_transaction.pk).update(**df)
        transaction_data['transaction_details']['id'] = account_transaction.id
        return reverse_savings_transaction(transaction_data, user_id, branch, comment)

    if reverse_interbranch:
        reverse_interbranch_transaction(transaction_data['id'], user_id)

    reversal = reverse_transaction(shares_transaction.system_transaction, user_id, comment)
    SharesTransaction.objects.filter(pk=shares_transaction.pk).update(**df)
    return reversal

def delete_shares_transaction(transaction_data, user_id, comment, branch):
    from django.utils import timezone
    now = timezone.now()
    df = dict(deleted=True, deleted_by_id=user_id, deleted_at=now)

    share_transaction = SharesTransaction.objects.get(pk=transaction_data['transaction_details']['id'])
    if share_transaction:
        message = f'Deleted Share For {share_transaction.shareholder.customer.name} Mem No: {share_transaction.shareholder.customer.member_number}'
        old_details = AuditTransactionsSerializer(share_transaction.system_transaction).data
        add_system_audit_trail('transaction_management', 'delete_share_transaction', message, comment, old_details, {}, user_id, branch)
        SharesTransaction.objects.filter(pk=share_transaction.pk).update(**df)

    delete_interbranch_transaction(transaction_data['id'], user_id=user_id, branch=branch)

    account_transaction = SavingAccountTransactions.objects.filter(transaction_id=transaction_data['id']).first()
    if account_transaction:
        transaction_data['transaction_details']['id'] = account_transaction.id
        delete_savings_transaction(transaction_data, user_id, comment, branch)
        return 'deleted'

    SystemTransactions.objects.filter(pk=transaction_data['id']).update(**df)
    return 'deleted'

# def reverse_transfer_transaction
def update_shares_transaction(transaction_data, details, branch, update_interbranch=True):
    share_transaction = SharesTransaction.objects.select_related(
        'shareholder__customer',
        'system_transaction',
    ).get(pk=transaction_data['transaction_details']['id'])
    old_transaction = share_transaction.system_transaction
    old_details = AuditTransactionsSerializer(old_transaction).data

    amount = details.get('amount')
    if amount is not None:
        new_amount = float(amount)
        current_share_value = float(share_transaction.current_share_value or 0)
        current_no_of_shares = float(share_transaction.no_of_shares or 0)
        old_amount = float(old_transaction.amount or 0)

        if current_share_value > 0:
            share_transaction.no_of_shares = round(new_amount / current_share_value, 2)
        elif old_amount > 0 and current_no_of_shares > 0:
            share_transaction.no_of_shares = round(
                (new_amount / old_amount) * current_no_of_shares,
                2,
            )

    share_transaction.last_updated = timezone.now()
    share_transaction.save()

    transaction = update_transaction(share_transaction.system_transaction, details)
    if update_interbranch:
        update_interbranch_transaction(share_transaction.system_transaction.id, details)

    customer = share_transaction.shareholder.customer
    message = (
        f'Updated Share {share_transaction.transaction_type} For '
        f'{customer.name}({customer.member_number})'
    )
    new_details = AuditTransactionsSerializer(transaction).data
    add_system_audit_trail(
        'transaction_management',
        'update_share_transaction',
        message,
        details['coment'],
        old_details,
        new_details,
        details['user_id'],
        branch,
    )

    return transaction


def update_savings_transaction(transaction_data, details,branch, update_interbranch = True):
    account_transaction = SavingAccountTransactions.objects.get(pk=transaction_data['transaction_details']['id'])
    transaction_charges = SavingAccountTransactions.objects.filter(parent_id=account_transaction.id)
    old_transaction = account_transaction.transaction
    old_details     = AuditTransactionsSerializer(old_transaction).data
    for charge in transaction_charges:
        charge_data = {
            'coment': details['coment'],
            'record_date': details['record_date'],
            'user_id': details['user_id']
        }
        update_transaction(charge.transaction, charge_data)
        # Update InterBranch Charge
        update_interbranch_transaction(charge.transaction.id, charge_data)

    # Update original account type
    transaction      = update_transaction(account_transaction.transaction, details)
    transaction_type = transaction_data['transaction_details']['type'].replace("_", " ")
    message     = f'Updated Saving {transaction_type} For {account_transaction.customer_account.account_customer.name}({account_transaction.customer_account.account_customer.member_number})' 
    new_details = AuditTransactionsSerializer(transaction).data
    add_system_audit_trail('transaction_management','update_saving_transaction',message,details['coment'],old_details,new_details,details['user_id'],branch)

    # Update InterBranch Related Transaction
    if update_interbranch:
        update_interbranch_transaction(account_transaction.transaction.id, details)

    return transaction

def reverse_fixed_deposit_transaction(transaction_data, user_id,branch, comment):
    account_transaction = SavingAccountTransactions.objects.get(pk=transaction_data['transaction_details']['id'])
    account_reversal = SavingAccountTransactions()

    # Retrieve FD Record
    fixed_deposit = account_transaction.transaction.fixed_deposit_transaction.first()
    fixed_deposit.status = 'reversed'
    fixed_deposit.save()

    # Reverse Fixed Deposit Completed Payments
    fixed_deposit_payments = FixedDepositSchedule.objects.active().filter(fixed_deposit=fixed_deposit, status='paid')
    for payment in fixed_deposit_payments:
        fd_payment_reversal = SavingAccountTransactions()
        fd_payment_reversal.transaction = reverse_transaction(payment.reference_transaction, user_id, comment)
        fd_payment_reversal.customer_account = account_transaction.customer_account
        
        message = f'Reversed Saving {fd_payment_reversal.transaction_type} For {account_transaction.customer_account.account_customer.name} Mem No: {account_transaction.customer_account.account_customer.member_number}' 
        old_details = AuditTransactionsSerializer(fd_payment_reversal.transaction).data
        add_system_audit_trail('transaction_management','reverse_saving_transaction',message,comment,old_details,{},user_id,branch)

        fd_payment_reversal.transaction_type = payment.transaction_type + '_reversal'
        fd_payment_reversal.save()

        payment.status = 'reversed'
        payment.save()

    # Reverse main transaction
    account_reversal.transaction_type = account_transaction.transaction_type + '_reversal'
    account_reversal.transaction = reverse_transaction(transaction_data['id'], user_id, comment)
    account_reversal.customer_account = account_transaction.customer_account
    account_reversal.save()

    # reverse inter-branch
    inter_branch = InterBranchTransactions.objects.filter( source_transaction__id=transaction_data['id'] ).first()
    if inter_branch:
        reverse_transaction(inter_branch.destination_transaction.id, user_id, comment)
        
    # Update original account type
    message = f'Reversed Saving {account_transaction.transaction_type} For {account_transaction.customer_account.account_customer.name} Mem No: {account_transaction.customer_account.account_customer.member_number}'
    old_details = AuditTransactionsSerializer(account_transaction.transaction).data
    add_system_audit_trail('transaction_management','reverse_saving_transaction',message,comment,old_details,{},user_id,branch)
        
    account_transaction.transaction_type = account_transaction.transaction_type + '_reversed'
    account_transaction.save()

    return account_reversal.transaction

def delete_fixed_deposit_transaction(transaction_data, user_id, comment, branch):
    from django.utils import timezone
    now = timezone.now()
    df = dict(deleted=True, deleted_by_id=user_id, deleted_at=now)

    account_transaction = SavingAccountTransactions.objects.get(pk=transaction_data['transaction_details']['id'])
    fixed_deposit = account_transaction.transaction.fixed_deposit_transaction.first()
    if fixed_deposit:
        add_system_audit_trail(
            'transaction_management', 'delete_fixed_deposit_transaction',
            f'Deleted Fixed Deposit transaction: {account_transaction.transaction.heading}',
            comment, {}, {}, user_id, branch
        )

    transaction_1 = None
    transaction_2 = None
    inter_branch = InterBranchTransactions.objects.filter(
        Q(source_transaction__id=account_transaction.transaction.id)
        | Q(destination_transaction__id=account_transaction.transaction.id)
    ).first()
    if inter_branch:
        transaction_1 = inter_branch.source_transaction.id
        transaction_2 = inter_branch.destination_transaction.id

    fixed_deposit_payments = FixedDepositSchedule.objects.active().filter(fixed_deposit=fixed_deposit, status='paid')
    for payment in fixed_deposit_payments:
        payment_account_transaction = payment.reference_transaction.system_transactions.first()
        payment_data = {"transaction_details": {"id": payment_account_transaction.id}}
        delete_savings_transaction(payment_data, user_id, comment, branch)
        FixedDepositSchedule.objects.filter(pk=payment.pk).update(**df)

    FixedDepositSchedule.objects.filter(fixed_deposit=fixed_deposit).update(**df)
    SavingAccountTransactions.objects.filter(pk=account_transaction.pk).update(**df)
    SystemTransactions.objects.filter(pk=fixed_deposit.reference_transaction.pk).update(**df)
    FixedDeposit.objects.filter(pk=fixed_deposit.pk).update(**df)

    if transaction_1:
        SystemTransactions.objects.filter(id=transaction_1).update(**df)
    if transaction_2:
        SystemTransactions.objects.filter(id=transaction_2).update(**df)

    return 'deleted'

def reverse_fixed_deposit_payment_transaction(transaction_data, user_id, branch, comment):
    reversal = reverse_savings_transaction(transaction_data, user_id, branch, comment)

    # Reverse Fixed Deposit Completed Payments
    fixed_deposit_payment = FixedDepositSchedule.objects.active().filter(reference_transaction_id=transaction_data['id'])
    if len(fixed_deposit_payment) > 0:
        fixed_deposit_payment[0].status = 'reversed'
        fixed_deposit_payment[0].save()

    return reversal

def delete_fixed_deposit_payment_transaction(transaction_data,user_id,comment,branch):
    # Reverse Fixed Deposit Completed Payments
    fixed_deposit_payment = FixedDepositSchedule.objects.active().filter(reference_transaction_id=transaction_data['id'])
    if len(fixed_deposit_payment) > 0:
        fixed_deposit_payment[0].status = 'pending'
        fixed_deposit_payment[0].reference_transaction = None
        fixed_deposit_payment[0].save()

    delete_savings_transaction(transaction_data,user_id,comment,branch)

    return 'deleted'

def delete_savings_transaction(transaction_data, user_id, comment, branch):
    from django.utils import timezone
    now = timezone.now()
    df = dict(deleted=True, deleted_by_id=user_id, deleted_at=now)

    account_transaction = SavingAccountTransactions.objects.get(pk=transaction_data['transaction_details']['id'])
    add_system_audit_trail(
        'transaction_management', 'delete_saving_transaction',
        f'Deleted Saving {account_transaction.transaction_type.replace("_", " ")} For {account_transaction.customer_account.account_customer.name.capitalize()} Mem No: {account_transaction.customer_account.account_customer.member_number}',
        comment, {}, {}, user_id, branch
    )

    transaction_charges = SavingAccountTransactions.objects.filter(parent_id=account_transaction.id)
    for charge in transaction_charges:
        delete_interbranch_transaction(charge.transaction.id)

        group_savings = GroupSavingTransaction.objects.filter(savings=charge).first()
        if group_savings:
            GroupSavingTransaction.objects.filter(pk=group_savings.pk).update(**df)

        message = f'Deleted Saving {charge.transaction_type.replace("_", " ")} For {charge.customer_account.account_customer.name.capitalize()} Mem No: {charge.customer_account.account_customer.member_number}'
        old_details = AuditTransactionsSerializer(charge.transaction).data
        add_system_audit_trail('transaction_management', 'delete_saving_transaction_charge', message, comment, old_details, {}, user_id, branch)

        SystemTransactions.objects.filter(pk=charge.transaction.pk).update(**df)
        SavingAccountTransactions.objects.filter(pk=charge.pk).update(**df)

    group_savings = GroupSavingTransaction.objects.filter(savings=account_transaction).first()
    if group_savings:
        GroupSavingTransaction.objects.filter(pk=group_savings.pk).update(**df)

    message = f'Deleted Saving {account_transaction.transaction_type.replace("_", " ")} For {account_transaction.customer_account.account_customer.name.capitalize()} Mem No: {account_transaction.customer_account.account_customer.member_number}'
    old_details = AuditTransactionsSerializer(account_transaction.transaction).data
    add_system_audit_trail('transaction_management', 'delete_saving_transaction_charge', message, comment, old_details, {}, user_id, branch)

    SystemTransactions.objects.filter(pk=account_transaction.transaction.pk).update(**df)
    SavingAccountTransactions.objects.filter(pk=account_transaction.pk).update(**df)

    return 'deleted'

def delete_transfer_transaction(transaction_data, user_id, comment, branch):
    from django.utils import timezone
    now = timezone.now()
    df = dict(deleted=True, deleted_by_id=user_id, deleted_at=now)

    add_system_audit_trail(
        'transaction_management', 'delete_transfer_transaction',
        f'Deleted Transfer Transaction ref: {transaction_data.get("id", "")}',
        comment, {}, {}, user_id, branch
    )

    TransferTransactions.objects.filter(
        Q(sender_transaction_id=transaction_data['transaction_details']['id'])
        | Q(reciever_transaction_id=transaction_data['transaction_details']['id'])
    ).update(**df)

    delete_savings_transaction(transaction_data, user_id, comment, branch)

    transaction_data['transaction_details']['id'] = transaction_data['transaction_details']['recipient']['id']
    delete_savings_transaction(transaction_data, user_id, comment, branch)

    return 'deleted'

def update_branch_interbranch_legder(update_branch, user_id):
    parent_chart = 'sys-11416'
    branches = OrganisationBranch.objects.filter(~Q(id=update_branch.id), branch_organisation=update_branch.branch_organisation)
    for branch in branches:
        chart_name = branch.name + ":" + update_branch.name + " - InterBranch"
        mapping = InterBranchMapping.objects.filter(Q(start_branch=update_branch, end_branch=branch) | Q(start_branch=branch, end_branch=update_branch)).first()
        if mapping:
            chart = mapping.chart
            chart.account_name = chart_name
            chart.save()
        else:
            organisation = update_branch.branch_organisation
            parent = get_chart_of_account_by_code(parent_chart, organisation)
            if parent:
                account_line = parent.account_line
                account_code = generate_chart_of_account_code(parent.id, account_line, organisation.id)

                #Save Cash account chart.
                chart = OrganisationSubAccount(account_name=chart_name, account_line=account_line, account_organisation=organisation, account_code=account_code, parent_id=parent, added_by=user_id, allow_sub_accounts=False)
                chart.save()

                mapping = InterBranchMapping(start_branch=branch, end_branch=update_branch, chart=chart, added_by=user_id)
                mapping.save()
                
def reverse_ledger_transaction(transaction, user_id,branch,comment, transaction_data):
    # check if not affecting customer account
    saving_transaction = SavingAccountTransactions.objects.filter(transaction=transaction).first()
    if not saving_transaction:
        # check interbranch
        inter_branch = InterBranchTransactions.objects.filter(source_transaction=transaction).first()
        if inter_branch:
            saving_transaction = SavingAccountTransactions.objects.filter(transaction=inter_branch.destination_transaction).first()
        else:
            inter_branch = InterBranchTransactions.objects.filter(destination_transaction=transaction).first()
            if inter_branch:
                saving_transaction = SavingAccountTransactions.objects.filter(transaction=inter_branch.source_transaction).first()

    if saving_transaction:
        transaction_data['transaction_details']['id'] = saving_transaction.id
        return reverse_savings_transaction(transaction_data, user_id, branch, comment)

    # Update cash transfer is transaction is a cash transfer.
    transfer = CashTransfers.objects.filter(reference_transaction=transaction).first()
    if transfer:
        transfer.approval_status = 'reversed'
        transfer.comment = 'Reversed'
        transfer.approved_by_id = user_id
        transfer.save()
    
    # Reverse InterBranch
    reverse_interbranch_transaction(transaction, user_id)

    # Reverse actual transaction 
    return reverse_transaction(transaction, user_id,comment)

def get_inter_branch_chart(branch_one, branch_two):
    chart = None
    mapping = InterBranchMapping.objects.filter(Q(start_branch=branch_one, end_branch=branch_two) | Q(start_branch=branch_two, end_branch=branch_one)).first()
    if mapping:
        chart = mapping.chart

    return chart

# def reverse_transfer_transaction
def get_serialized_transaction(transaction):
    return AuditTransactionsSerializer(transaction).data
