from decimal import Decimal, ROUND_HALF_UP

from rest_framework import serializers

from shop_management.models.data.accounting import JournalEntry, JournalEntryLine
from account.permissions import assert_shop_access


class JournalEntryLineSerializer(serializers.ModelSerializer):
    ledger_code = serializers.ReadOnlyField(source="ledger_account.code")
    ledger_name = serializers.ReadOnlyField(source="ledger_account.name")
    currency_title = serializers.ReadOnlyField(source="transaction_currency.title")

    class Meta:
        model = JournalEntryLine
        fields = "__all__"
        read_only_fields = ("journal_entry", "base_debit", "base_credit", "created_by")

    def validate(self, attrs):
        debit = Decimal(attrs.get("debit", 0) or 0)
        credit = Decimal(attrs.get("credit", 0) or 0)
        rate = Decimal(attrs.get("exchange_rate_used", 1) or 0)
        if debit < 0 or credit < 0 or bool(debit) == bool(credit):
            raise serializers.ValidationError("Enter either a positive debit or a positive credit.")
        if rate <= 0:
            raise serializers.ValidationError({"exchange_rate_used": "Exchange rate must be greater than zero."})
        return attrs


class JournalEntrySerializer(serializers.ModelSerializer):
    lines = JournalEntryLineSerializer(many=True)
    shop_name = serializers.ReadOnlyField(source="shop.name")
    total_debit = serializers.SerializerMethodField()
    total_credit = serializers.SerializerMethodField()

    class Meta:
        model = JournalEntry
        fields = "__all__"
        read_only_fields = ("journal_number", "status", "posted_by", "posted_at", "voided_by", "voided_at", "void_reason", "created_by")

    def get_total_debit(self, obj):
        return sum((line.debit for line in obj.lines.all()), Decimal("0"))

    def get_total_credit(self, obj):
        return sum((line.credit for line in obj.lines.all()), Decimal("0"))

    def validate(self, attrs):
        if self.instance and self.instance.status != JournalEntry.Status.DRAFT:
            raise serializers.ValidationError("Only draft journal entries can be edited.")
        shop = attrs.get("shop") or getattr(self.instance, "shop", None)
        request = self.context.get("request")
        if request and shop:
            shop = assert_shop_access(request.user, shop, request)
        for index, line in enumerate(attrs.get("lines", [])):
            if line["ledger_account"].shop_id != shop.id or line["transaction_currency"].shop_id != shop.id:
                raise serializers.ValidationError({f"lines[{index}]": "Ledger account and currency must belong to the journal branch."})
        return attrs

    def create(self, validated_data):
        lines = validated_data.pop("lines")
        user = self.context["request"].user
        entry = JournalEntry.objects.create(created_by=user, **validated_data)
        for line in lines:
            rate = Decimal(line.get("exchange_rate_used", 1))
            line["base_debit"] = (Decimal(line.get("debit", 0)) * rate).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
            line["base_credit"] = (Decimal(line.get("credit", 0)) * rate).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
            JournalEntryLine.objects.create(journal_entry=entry, created_by=user, **line)
        return entry

    def update(self, instance, validated_data):
        lines = validated_data.pop("lines", None)
        instance = super().update(instance, validated_data)
        if lines is not None:
            instance.lines.all().delete()
            user = self.context["request"].user
            for line in lines:
                rate = Decimal(line.get("exchange_rate_used", 1))
                line["base_debit"] = (Decimal(line.get("debit", 0)) * rate).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
                line["base_credit"] = (Decimal(line.get("credit", 0)) * rate).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
                JournalEntryLine.objects.create(journal_entry=instance, created_by=user, **line)
        return instance
