from decimal import Decimal

from django.conf import settings
from django.db import models
from django.db.models import Sum

from apps.common.models import TimeStampedModel


class Account(models.Model):
    """
    A book account. User wallets are per-user liabilities (money the platform
    owes the seller); system accounts are singletons (user is null).

    Balances are never stored — they are always derived from the sum of this
    account's entries (`balance`).
    """

    class Kind(models.TextChoices):
        USER_WALLET = 'user_wallet', 'User wallet'                 # liability to seller
        COMMISSION_EXPENSE = 'commission_expense', 'Commission expense'
        PAYOUTS_CLEARING = 'payouts_clearing', 'Payouts clearing'  # reserved, in-flight
        CASH = 'cash', 'Cash / settled out'
        REVENUE = 'revenue', 'Platform revenue'

    kind = models.CharField(max_length=24, choices=Kind.choices)
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL, null=True, blank=True,
        on_delete=models.PROTECT, related_name='ledger_accounts')
    name = models.CharField(max_length=120, blank=True)

    class Meta:
        db_table = 'ledger_accounts'
        constraints = [
            models.UniqueConstraint(
                fields=['user', 'kind'], name='uniq_account_user_kind'),
            # Only one system account per kind (user IS NULL).
            models.UniqueConstraint(
                fields=['kind'], condition=models.Q(user__isnull=True),
                name='uniq_system_account_kind'),
        ]

    def __str__(self):
        owner = f'#{self.user_id}' if self.user_id else 'system'
        return f'{self.kind} ({owner})'

    @property
    def balance(self) -> Decimal:
        total = self.entries.aggregate(s=Sum('amount'))['s']
        return total or Decimal('0.00')


class Transaction(TimeStampedModel):
    """A balanced set of entries. The sum of its entries is always zero."""

    class Kind(models.TextChoices):
        COMMISSION = 'commission', 'Sales commission'
        COMMISSION_REVERSAL = 'commission_reversal', 'Commission reversal'
        REFERRAL = 'referral', 'Referral bonus'
        REWARD = 'reward', 'Reward'
        TASK = 'task', 'Task payout'
        PAYOUT_RESERVE = 'payout_reserve', 'Payout reserve'
        PAYOUT_SETTLE = 'payout_settle', 'Payout settled'
        PAYOUT_REVERSE = 'payout_reverse', 'Payout reversed'
        ADJUSTMENT = 'adjustment', 'Manual adjustment'

    kind = models.CharField(max_length=24, choices=Kind.choices)
    idempotency_key = models.CharField(
        max_length=120, unique=True, null=True, blank=True)
    reference_type = models.CharField(max_length=40, blank=True)
    reference_id = models.BigIntegerField(null=True, blank=True)
    memo = models.CharField(max_length=255, blank=True)
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, null=True, blank=True,
        on_delete=models.SET_NULL, related_name='ledger_transactions')

    class Meta:
        db_table = 'ledger_transactions'
        ordering = ['-created_at']
        indexes = [
            models.Index(fields=['reference_type', 'reference_id']),
            models.Index(fields=['kind']),
        ]

    def __str__(self):
        return f'{self.kind} #{self.pk}'


class Entry(models.Model):
    """A single signed posting against one account within a transaction."""

    transaction = models.ForeignKey(
        Transaction, on_delete=models.CASCADE, related_name='entries')
    account = models.ForeignKey(
        Account, on_delete=models.PROTECT, related_name='entries')
    amount = models.DecimalField(max_digits=18, decimal_places=2)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'ledger_entries'
        indexes = [
            models.Index(fields=['account']),
        ]

    def __str__(self):
        return f'{self.account} {self.amount}'
