from rest_framework import serializers
from organisations.serializers import OrganisationBranchSerializer
from users.serializers import UserSerializer
from django.db.models import Q, Sum
from datetime import datetime, timedelta
from .models import *
from savings.models import TransferTransactions
from .ledgers_helper import get_chart_of_account_balance_at, get_chart_of_account_balance_between
from .transaction_edit_permissions import can_edit_transaction_amount_for_organisation
from questbanker_api.utils import get_current_user

class OrganisationSubAccountSerializer(serializers.ModelSerializer):
    account_code = serializers.CharField(read_only=True)
    child_accounts = serializers.SerializerMethodField(read_only=True)
    account_transactions = serializers.SerializerMethodField(read_only=True)
    account_organisation = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
    balance_bf = serializers.SerializerMethodField(read_only=True)
    balance = serializers.SerializerMethodField(read_only=True)

    class Meta:
        model = OrganisationSubAccount
        fields = '__all__'
    
    def get_child_accounts(self, obj):
        children = OrganisationSubAccount.objects.filter(
            parent_id=obj.id,
            deleted=False,
        ).order_by('id')
        serializer = OrganisationSubAccountSerializer(instance=children, many=True)
        return serializer.data

    def get_account_transactions(self, account):
        transactions = []
        end = self.context.get('end_date', None)
        start = self.context.get('start_date', None)
        branch_id = self.context.get('branch_id', None)
        filter_deleted = self.context.get('filter_deleted', True)

        # Get transactions between date.
        if branch_id and start and end:
            branch_id = str(branch_id).split(',')
            
            # Build the query with deleted filter
            query_filters = (
                Q(transaction_type='normal') &
                Q(branch_id__in=branch_id) &
                Q(record_date__date__gte=start) &
                Q(record_date__date__lte=end) &
                (Q(debit_chart_id=account.id) | Q(credit_chart_id=account.id))
            )
            
            # Always filter out deleted transactions
            if filter_deleted:
                query_filters &= Q(deleted=False)
            
            period_transactions = SystemTransactions.objects.filter(query_filters).order_by('record_date__date', 'id')
            
            deleted_count = SystemTransactions.objects.filter(
                Q(transaction_type='normal') &
                Q(branch_id__in=branch_id) &
                Q(record_date__date__gte=start) &
                Q(record_date__date__lte=end) &
                (Q(debit_chart_id=account.id) | Q(credit_chart_id=account.id)) &
                Q(deleted=True)
            ).count()
            
            transactions = SystemTransactionsSerializer(period_transactions, many=True).data

        return transactions

    def get_balance_bf(self, account):
        start = self.context.get('start_date', None)
        branch_id = self.context.get('branch_id', None)
        type = self.context.get('type', None)

        # Get transactions between date.
        if start and type == 'balanced':
            if account.account_line in ['assets', 'liabilities', 'capital']:
                return get_chart_of_account_balance_at(account, branch_id, start)
        
            # get balances for only current year for incomes and expenses
            end = datetime.strptime(start, "%Y-%m-%d") - timedelta(days=1)
            end = end.strftime('%Y-%m-%d')
            start = start.split('-')[0] + '-01-01'
            return get_chart_of_account_balance_between(account, start, end, branch_id)
    
    def get_balance(self, account):
        branch_id = self.context.get('branch_id', None)
        if branch_id:
            account_totals = get_chart_of_account_balance_at(account, branch_id)
            return account_totals.get('balance', 0)
        return 0

class BankAccountsSerializer(serializers.ModelSerializer):
    chart = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
    branch = OrganisationBranchSerializer(read_only=True, required=False)
    current_balance = serializers.SerializerMethodField(read_only=True)
    account_alias = serializers.CharField(read_only=True, required=False)

    def get_current_balance(self, bank_account):
        if bank_account.chart:
            account_totals = get_chart_of_account_balance_at(bank_account.chart, bank_account.branch.id)
            return account_totals['balance']
        return 0

    class Meta:
        model = BankAccounts
        fields = '__all__'

class CurrenciesSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(read_only=True)

    class Meta:
        model = Currencies
        fields = '__all__'

class CashAccountsSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(read_only=True)
    chart = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
    account_code = serializers.CharField(source="chart.account_code",read_only=True)
    teller = UserSerializer(read_only=True, required=False)
    current_balance = serializers.SerializerMethodField(read_only=True)

    def get_current_balance(self, cash_account):
       account_totals = get_chart_of_account_balance_at(cash_account.chart, cash_account.teller.user_organisation_branch.id)
       return account_totals['balance']
    
    class Meta:
        model = CashAccounts
        fields = '__all__'

class SafeAccountsSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(read_only=True)
    chart = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
    branch = OrganisationBranchSerializer(read_only=True, required=False)
    current_balance = serializers.SerializerMethodField(read_only=True)

    def get_current_balance(self, safe_account):
        account_totals = get_chart_of_account_balance_at(safe_account.chart, safe_account.branch.id)
        return account_totals['balance']

    class Meta:
        model = SafeAccounts
        fields = '__all__'

class MobileMoneyAccountsSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(read_only=True)
    chart = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
    branch = OrganisationBranchSerializer(read_only=True, required=False)
    current_balance = serializers.SerializerMethodField(read_only=True)

    def get_current_balance(self, mobile_money_account):
        account_totals = get_chart_of_account_balance_at(mobile_money_account.chart, mobile_money_account.branch.id)
        return account_totals['balance']

    class Meta:
        model = MobileMoneyAccounts
        fields = '__all__'

class SystemTransactionsSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(read_only=True)
    credit_chart = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
    debit_chart = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
    added_by = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
    branch = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
    teller = serializers.SerializerMethodField(read_only=True)

    def get_teller(self, transaction):
        if transaction.added_by:
            return transaction.added_by.username
        
        return ''

    class Meta:
        model = SystemTransactions
        fields = '__all__'

class CashTransfersSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(read_only=True)
    source_chart = serializers.SerializerMethodField(read_only=True, required=False)
    destination_chart = serializers.SerializerMethodField(read_only=True, required=False)
    added_by = serializers.SerializerMethodField(read_only=True, required=False)
    branch = serializers.SerializerMethodField(read_only=True, required=False)
    approved_by = serializers.SerializerMethodField(read_only=True, required=False)

    def get_source_chart(self, transfer):
        return transfer.source_chart.account_name

    def get_destination_chart(self, transfer):
        return transfer.destination_chart.account_name

    def get_added_by(self, transfer):
        return transfer.added_by.username

    def get_branch(self, transfer):
        return transfer.branch.name

    def get_approved_by(self, transfer):
        approver = ''
        if transfer.approved_by:
            approver = transfer.approved_by.username
        
        return approver

    class Meta:
        model = CashTransfers
        fields = '__all__'

class PendingExpensesSerializer(serializers.ModelSerializer):
    added_by = serializers.SerializerMethodField(read_only=True, required=False)
    branch = serializers.SerializerMethodField(read_only=True, required=False)
    account_transactions = serializers.SerializerMethodField(read_only=True)
    account_organisation = serializers.PrimaryKeyRelatedField(read_only=True, required=False)

    def get_added_by(self, expense):
        return expense.added_by.username if expense.added_by else ''

    def get_branch(self, expense):
        return expense.branch.name if expense.branch else ''

    def get_account_transactions(self, account):
        transactions = []
        end = self.context.get('end_date', None)
        start = self.context.get('start_date', None)
        branch_id = self.context.get('branch_id', None)

        if branch_id and start and end:
            period_transactions = SystemTransactions.objects.filter(
                Q(transaction_type='normal'),
                Q(branch_id=branch_id),
                Q(record_date__date__gte=start),
                Q(record_date__date__lte=end),
                Q(debit_chart_id=account.id) | Q(credit_chart_id=account.id),
                deleted=False
            ).order_by('record_date__date', 'id')
            transactions = SystemTransactionsSerializer(period_transactions, many=True).data

        return transactions

    class Meta:
        model = PendingExpenses
        fields = '__all__'


class CrbActivationRequestSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(read_only=True)
    added_by = serializers.SerializerMethodField(read_only=True, required=False)
    approved_by = serializers.SerializerMethodField(read_only=True, required=False)
    organisation_branch = serializers.SerializerMethodField(read_only=True, required=False)
    added_by_user = serializers.PrimaryKeyRelatedField(read_only=True, source='added_by')
    amount = serializers.FloatField(required=False)
    debit_chart = serializers.PrimaryKeyRelatedField(queryset=OrganisationSubAccount.objects.all(), required=False)
    credit_chart = serializers.PrimaryKeyRelatedField(queryset=OrganisationSubAccount.objects.all(), required=False)

    def get_added_by(self, req):
        return req.added_by.username if req.added_by else ''

    def get_approved_by(self, req):
        return req.approved_by.username if req.approved_by else ''

    def get_organisation_branch(self, req):
        try:
            return req.organisation_branch.name if req.organisation_branch else ''
        except:
            return ''

    class Meta:
        model = CrbActivationRequest
        fields = '__all__'
        read_only_fields = ['id', 'added_by', 'date_added', 'status', 'approved_by', 'date_approved', 'reference_transaction', 'organisation_branch', 'deleted']

class CreditorAccountsSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(read_only=True)
    organisation = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
    chart = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
    chart_name = serializers.SerializerMethodField(read_only=True, required=False)
    total_payables = serializers.SerializerMethodField(read_only=True, required=False)
    total_paid = serializers.SerializerMethodField(read_only=True, required=False)

    def get_total_payables(self, creditor):
        payables = CreditorSupplies.objects.filter(creditor=creditor).aggregate(total=Sum('reference_transaction__amount'))['total']
        return payables if payables else 0

    def get_total_paid(self, creditor):
        paid = CreditorPayments.objects.filter(supply__creditor=creditor).aggregate(total=Sum('reference_transaction__amount'))['total']
        return paid if paid else 0
    
    def get_chart_name(self, account):
        return account.chart.account_code + ' ' + account.chart.account_name

    class Meta:
        model = CreditorAccounts
        fields = '__all__'

class CreditorPaymentsSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(read_only=True)
    reference_transaction = SystemTransactionsSerializer(read_only=True, required=False)
    supply = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
    
    class Meta:
        model = CreditorPayments
        fields = '__all__'

class CreditorSuppliesSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(read_only=True)
    reference_transaction = SystemTransactionsSerializer(read_only=True, required=False)
    creditor = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
    payments = serializers.SerializerMethodField(read_only=True, required=False)
    
    def get_payments(self, supply):
        payments = CreditorPayments.objects.filter(supply=supply)
        paymentTotals = payments.aggregate(total=Sum('reference_transaction__amount'))['total']
        payments = {
            'total': paymentTotals if paymentTotals else 0,
            'list': CreditorPaymentsSerializer(payments, many=True).data
        }

        return payments
    
    class Meta:
        model = CreditorSupplies
        fields = '__all__'

class DebtorAccountsSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(read_only=True)
    organisation = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
    chart = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
    chart_name = serializers.SerializerMethodField(read_only=True, required=False)
    total_receivable = serializers.SerializerMethodField(read_only=True, required=False)
    total_paid = serializers.SerializerMethodField(read_only=True, required=False)

    def get_total_receivable(self, debtor):
        receivables = DebtorSupplies.objects.filter(debtor=debtor).aggregate(total=Sum('reference_transaction__amount'))['total']
        return receivables if receivables else 0

    def get_total_paid(self, debtor):
        paid = DebtorPayments.objects.filter(supply__debtor=debtor).aggregate(total=Sum('reference_transaction__amount'))['total']
        return paid if paid else 0
    
    def get_chart_name(self, account):
        return account.chart.account_code + ' ' + account.chart.account_name

    class Meta:
        model = DebtorAccounts
        fields = '__all__'

class DebtorPaymentsSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(read_only=True)
    reference_transaction = SystemTransactionsSerializer(read_only=True, required=False)
    supply = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
    
    class Meta:
        model = DebtorPayments
        fields = '__all__'

class DebtorSuppliesSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(read_only=True)
    reference_transaction = SystemTransactionsSerializer(read_only=True, required=False)
    debtor = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
    payments = serializers.SerializerMethodField(read_only=True, required=False)
    
    def get_payments(self, supply):
        payments = DebtorPayments.objects.filter(supply=supply)
        paymentTotals = payments.aggregate(total=Sum('reference_transaction__amount'))['total']
        payments = {
            'total': paymentTotals if paymentTotals else 0,
            'list': DebtorPaymentsSerializer(payments, many=True).data
        }

        return payments
    
    class Meta:
        model = DebtorSupplies
        fields = '__all__'

class SystemTransactionDetailsSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(read_only=True)
    credit_chart_id = serializers.CharField(source='credit_chart.id',read_only=True)
    credit_chart = serializers.CharField(source='credit_chart.account_name',read_only=True)
    credit_chart_code = serializers.CharField(source='credit_chart.account_code',read_only=True)
    debit_chart_id = serializers.CharField(source='debit_chart.id',read_only=True)
    debit_chart = serializers.CharField(source='debit_chart.account_name',read_only=True)
    debit_chart_code = serializers.CharField(source='debit_chart.account_code',read_only=True)
    branch = serializers.SerializerMethodField(read_only=True)
    teller = serializers.SerializerMethodField(read_only=True)
    transaction_details = serializers.SerializerMethodField(read_only=True)
    can_edit_amount = serializers.SerializerMethodField(read_only=True)

    def get_teller(self, transaction):
        try:
            if transaction.added_by:
                return transaction.added_by.username
        except:
            return ''
    
    def get_branch(self, transaction):
        try:
            inter_branch = InterBranchTransactions.objects.filter(Q(source_transaction=transaction) | Q(destination_transaction=transaction)).first()
            if inter_branch:
                return inter_branch.source_transaction.branch.name
            
            return transaction.branch.name
        except:
            return ''
    
    def get_transaction_details(self, transaction):
        details = {}
        savings_details = transaction.system_transactions.first()
        if savings_details:
            details = {
                'id': savings_details.id,
                'type': savings_details.transaction_type,
                'customer': savings_details.customer_account.account_customer.name,
                'account_no': savings_details.customer_account.account_no
            }

            if savings_details.transaction_type in ['transfer', 'transfer_reversed', 'transfer_reversal']:
                if savings_details.transaction_type == 'transfer_reversal':
                    reference_number = transaction.reference_no.replace('rev-', '')
                    original_transaction = SystemTransactions.objects.filter(reference_no=reference_number).first()
                    original_transaction_data = SystemTransactionDetailsSerializer(original_transaction).data
                    details = original_transaction_data['transaction_details']['recipient']
                    details['type'] = savings_details.transaction_type
                    details['recipient'] = {
                        "id": original_transaction_data['transaction_details']['id'],
                        "customer": original_transaction_data['transaction_details']['customer'],
                        "account_no": original_transaction_data['transaction_details']['account_no']
                    }
                else:
                    receiver = TransferTransactions.objects.filter(sender_transaction=savings_details)
                    if len(receiver) > 0:
                        details['recipient'] = {
                            "id": receiver[0].reciever_transaction.id,
                            "customer": receiver[0].reciever_transaction.customer_account.account_customer.name,
                            "account_no": receiver[0].reciever_transaction.customer_account.account_no
                        }
                    else:
                        receiver = details
                        sender = TransferTransactions.objects.filter(reciever_transaction=savings_details)
                        if len(sender) > 0:
                            details = {
                                'id': sender[0].sender_transaction.id,
                                'type': sender[0].sender_transaction.transaction_type,
                                'customer': sender[0].sender_transaction.customer_account.account_customer.name,
                                'account_no': sender[0].sender_transaction.customer_account.account_no,
                                'recipient': receiver
                            }

            if savings_details.transaction_type in ['offset', 'offset_reversed', 'offset_reversal']:
                details['product_chart'] = savings_details.customer_account.account_product.accounts_chart.id

        share_details = transaction.shares_system_trasactions.first()
        if share_details:
            details = {
                'id': share_details.id,
                'type': 'share-' + share_details.transaction_type,
                'customer': share_details.shareholder.customer.name,
                'member_no': share_details.shareholder.customer.member_number
            }

        if not share_details and not savings_details:
            if transaction.payment_method in ['cash', 'bank', 'settlement']:
                details = {
                    'type': 'ledger_to_ledger'
                }

                if transaction.payment_method == 'settlement':
                    is_interbranch = InterBranchTransactions.objects.filter(Q(source_transaction=transaction) | Q(destination_transaction=transaction)).first()
                    if is_interbranch:
                        details = {
                            'type': 'inter_branch'
                        }

        inventory_order_transaction = transaction.order_transaction.first()
        if inventory_order_transaction:
            item_order = inventory_order_transaction.item_order.first()
            details = {
                    'type': 'inventory_ledger',
                    "transaction_type": inventory_order_transaction.transaction_type,
                    "maturity_date": inventory_order_transaction.maturity_date,
                    "qty": item_order.quantity if item_order else 0
                }

        return details

    def get_can_edit_amount(self, transaction):
        request = self.context.get('request')
        organisation_id = get_current_user(request, 'organisation_id', None) if request else None
        return can_edit_transaction_amount_for_organisation(transaction, organisation_id)

    class Meta:
        model = SystemTransactions
        fields = '__all__'


class SystemTransactionCreateSerializer(serializers.Serializer):
    amount = serializers.FloatField()
    heading = serializers.CharField()
    debit_chart = serializers.PrimaryKeyRelatedField(
        queryset=OrganisationSubAccount.objects.all()
    )
    credit_chart = serializers.PrimaryKeyRelatedField(
        queryset=OrganisationSubAccount.objects.all()
    )
    voucher_no = serializers.CharField(
        required=False, allow_blank=True, allow_null=True
    )
    coment = serializers.CharField(required=False, allow_blank=True, allow_null=True)
    comment = serializers.CharField(
        required=False, allow_blank=True, allow_null=True, write_only=True
    )
    record_date = serializers.DateTimeField()

    def validate(self, attrs):
        if attrs['amount'] <= 0:
            raise serializers.ValidationError(
                {'amount': 'Transaction amount should be greater than 0.'}
            )

        if attrs['debit_chart'].id == attrs['credit_chart'].id:
            raise serializers.ValidationError(
                {
                    'credit_chart': 'Debit and credit accounts must be different.'
                }
            )

        comment = attrs.pop('comment', None)
        if comment is not None and 'coment' not in attrs:
            attrs['coment'] = comment

        return attrs


class SystemTransactionsBulkCreateSerializer(serializers.Serializer):
    record_date = serializers.DateTimeField()
    transactions = serializers.ListField(
        child=serializers.DictField(), allow_empty=False
    )
