from decimal import Decimal, ROUND_HALF_UP

from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.utils import timezone

from shop_management.models.data.shop_account import Account
from shop_management.models.data.utils import Currency, ExchangeRate


class AccountTransactionType(models.TextChoices):
    CREDIT = "credit", "Credit"
    DEBIT = "debit", "Debit"


class AccountTransaction(models.Model):
    account = models.ForeignKey(
        Account, on_delete=models.PROTECT, related_name="transactions"
    )
    account_currency = models.ForeignKey(
        Currency,
        on_delete=models.PROTECT,
        related_name="account_transactions_as_account_currency",
    )
    transaction_currency = models.ForeignKey(
        Currency,
        on_delete=models.PROTECT,
        related_name="account_transactions_as_transaction_currency",
    )
    transaction_type = models.CharField(
        max_length=10, choices=AccountTransactionType.choices
    )
    amount = models.DecimalField(max_digits=18, decimal_places=2)
    exchange_rate = models.DecimalField(max_digits=20, decimal_places=6)
    converted_amount = models.DecimalField(max_digits=18, decimal_places=2)
    description = models.TextField(blank=True, null=True)
    reference = models.CharField(max_length=100, blank=True)
    source_type = models.CharField(max_length=50, blank=True, default="MANUAL")
    source_id = models.CharField(max_length=100, blank=True)
    is_system_generated = models.BooleanField(default=False)
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.PROTECT,
        related_name="account_transactions_created",
    )
    reversed_transaction = models.ForeignKey(
        "self",
        null=True,
        blank=True,
        on_delete=models.PROTECT,
        related_name="reversal_entries",
    )
    reversed_at = models.DateTimeField(null=True, blank=True)
    reversed_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        on_delete=models.PROTECT,
        related_name="account_transactions_reversed",
    )
    reversal_reason = models.TextField(blank=True)
    transaction_date = models.DateField(default=timezone.localdate)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-transaction_date", "-id"]

    def __str__(self):
        return (
            f"{self.get_transaction_type_display()} {self.amount} "
            f"{self.transaction_currency.title} -> {self.account.name}"
        )

    @property
    def is_reversed(self):
        return self.reversed_at is not None

    def delete(self, *args, **kwargs):
        raise ValidationError(
            "Financial transactions are immutable. Reverse the transaction with a reason instead."
        )

    def save(self, *args, **kwargs):
        # Preserve attribution even for older serializer/service paths that create
        # account movements directly. Background tasks may legitimately have no request.
        if not self.created_by_id:
            try:
                from account.middleware import get_current_request

                request = get_current_request()
                user = getattr(request, "user", None)
                if user and user.is_authenticated:
                    self.created_by = user
            except Exception:
                pass

        if (not self.source_type or self.source_type == "MANUAL") and self.description:
            description = str(self.description)
            source_patterns = (
                ("Supplier payment service charge", "SUPPLIER_PAYMENT_FEE"),
                ("Supplier payment", "SUPPLIER_PAYMENT"),
                ("Customer payment", "CUSTOMER_PAYMENT"),
                ("Purchase payment", "PURCHASE_PAYMENT"),
                ("Customer balance", "CUSTOMER_BALANCE_PAYMENT"),
                ("Supplier balance", "SUPPLIER_BALANCE_PAYMENT"),
                ("Expense", "EXPENSE"),
                ("Payroll", "PAYROLL"),
                ("Advance", "ADVANCE"),
                ("Passive income", "PASSIVE_INCOME"),
                ("Controlled transfer", "ACCOUNT_TRANSFER"),
                ("Transfer service charge", "ACCOUNT_TRANSFER_FEE"),
            )
            for prefix, source_type in source_patterns:
                if description.lower().startswith(prefix.lower()):
                    self.source_type = source_type
                    self.is_system_generated = True
                    break
        return super().save(*args, **kwargs)

    @staticmethod
    def resolve_exchange_rate(shop, from_currency_id, to_currency_id, effective_date=None):
        if from_currency_id == to_currency_id:
            return Decimal("1")

        direct_rates = ExchangeRate.objects.filter(
                shop=shop,
                from_currency_id=from_currency_id,
                to_currency_id=to_currency_id,
            )
        if effective_date:
            direct_rates = direct_rates.filter(rate_date__lte=effective_date)
        direct = direct_rates.order_by("-rate_date", "-id").first()
        if direct:
            return direct.rate

        reverse_rates = ExchangeRate.objects.filter(
                shop=shop,
                from_currency_id=to_currency_id,
                to_currency_id=from_currency_id,
            )
        if effective_date:
            reverse_rates = reverse_rates.filter(rate_date__lte=effective_date)
        reverse = reverse_rates.order_by("-rate_date", "-id").first()
        if reverse and reverse.rate and reverse.rate > 0:
            return Decimal("1") / reverse.rate

        return None

    @staticmethod
    def compute_converted_amount(amount, exchange_rate):
        return (amount * exchange_rate).quantize(
            Decimal("0.01"), rounding=ROUND_HALF_UP
        )
