"""
用户积分服务

Bug #43 fix: replace non-atomic points deduction with a SELECT FOR UPDATE
inside an atomic transaction to eliminate the race condition that allowed
the balance to go negative.
"""
from django.db import transaction
from django.db.models import F

from apps.users.models import User, PointsRecord


class InsufficientPointsError(Exception):
    """积分不足异常"""


def deduct_points(user_id, amount, record_type, description=''):
    """
    原子性地扣减用户积分，并写入流水记录。

    Bug #43 fix: the original code was:

        user.points -= amount   # read-modify-write – not atomic
        user.save()             # concurrent calls can all pass the balance
                                # check and drive points negative

    The fix uses SELECT FOR UPDATE inside an atomic block so that concurrent
    deductions are serialised at the database level, and an extra guard
    (points__gte=amount) prevents the update from proceeding when the balance
    is insufficient.
    """
    with transaction.atomic():
        # Lock the row so no other transaction can read stale data
        updated_rows = User.objects.filter(
            id=user_id,
            points__gte=amount,   # guard: only deduct when balance is sufficient
        ).update(points=F('points') - amount)

        if updated_rows == 0:
            # Either the user doesn't exist or balance was insufficient
            user = User.objects.filter(id=user_id).first()
            if user is None:
                raise ValueError(f'用户 {user_id} 不存在')
            raise InsufficientPointsError(
                f'积分不足: 当前余额 {user.points}，需要 {amount}'
            )

        PointsRecord.objects.create(
            user_id=user_id,
            amount=-amount,
            type=record_type,
            description=description,
        )


def add_points(user_id, amount, record_type, description=''):
    """
    原子性地增加用户积分，并写入流水记录。
    """
    with transaction.atomic():
        User.objects.filter(id=user_id).update(points=F('points') + amount)

        PointsRecord.objects.create(
            user_id=user_id,
            amount=amount,
            type=record_type,
            description=description,
        )
