"""
Сервис для работы с кошельком клиента.
Все операции создают транзакции в WalletTransaction.
Баланс вычисляется как SUM(signed_amount).
"""
from decimal import Decimal, ROUND_HALF_UP

from django.db import transaction


# Константа для округления до 2 знаков
QUANTIZE_2D = Decimal('0.01')


def _quantize(value):
    """Округление до 2 знаков после запятой"""
    if isinstance(value, (int, float)):
        value = Decimal(str(value))
    return value.quantize(QUANTIZE_2D, rounding=ROUND_HALF_UP)


class WalletService:
    """
    Сервис для управления кошельком клиента.

    Архитектура:
    - Баланс = SUM(signed_amount) транзакций (нет денормализованного поля)
    - Все операции атомарны с блокировкой строк
    - Кеширование баланса для производительности
    - Инвалидация кеша при каждой транзакции
    """

    @staticmethod
    @transaction.atomic
    def create_transaction(
        customer,
        amount,
        transaction_type,
        category='money',
        order=None,
        description='',
        user=None
    ):
        """
        Создать транзакцию кошелька (базовый метод).

        Args:
            customer: Customer или customer_id
            amount: Decimal - положительная сумма
            transaction_type: str - 'deposit', 'spend', 'adjustment'
            category: str - 'money' или 'bonus'
            order: Order - связанный заказ (опционально)
            description: str - описание
            user: CustomUser - кто создал

        Returns:
            WalletTransaction

        Raises:
            ValueError: если некорректные данные или недостаточно средств
        """
        from customers.models import Customer, WalletTransaction

        # Получаем и блокируем клиента
        if isinstance(customer, int):
            customer = Customer.objects.select_for_update().get(pk=customer)
        else:
            customer = Customer.objects.select_for_update().get(pk=customer.pk)

        amount = _quantize(amount)
        if amount <= 0:
            raise ValueError('Сумма должна быть положительной')

        # Определяем знак суммы
        if transaction_type == 'spend':
            signed_amount = -amount
        else:
            signed_amount = amount

        # Получаем текущий баланс (без кеша для точности)
        current_balance = customer.get_wallet_balance(category=category, use_cache=False)

        # Проверяем баланс для списания
        if signed_amount < 0:
            if current_balance + signed_amount < 0:
                raise ValueError(
                    f'Недостаточно средств. Баланс: {current_balance}, '
                    f'запрошено: {abs(signed_amount)}'
                )

        # Вычисляем баланс после транзакции
        balance_after = current_balance + signed_amount

        # Создаём транзакцию
        txn = WalletTransaction.objects.create(
            customer=customer,
            signed_amount=signed_amount,
            transaction_type=transaction_type,
            balance_category=category,
            order=order,
            description=description,
            created_by=user,
            balance_after=balance_after
        )

        # Инвалидируем кеш
        customer.invalidate_wallet_cache(category=category)

        return txn

    @staticmethod
    @transaction.atomic
    def create_adjustment(customer, amount, description, user, category='money'):
        """
        Корректировка баланса (может быть положительной или отрицательной).

        Используется для административных операций:
        - Пополнение кошелька
        - Списание средств
        - Исправление ошибок

        Args:
            customer: Customer или customer_id
            amount: Decimal - сумма (может быть отрицательной)
            description: str - обязательное описание
            user: CustomUser
            category: str - 'money' или 'bonus'

        Returns:
            WalletTransaction
        """
        from customers.models import Customer, WalletTransaction

        if not description or not description.strip():
            raise ValueError('Описание обязательно для корректировки баланса')

        amount = _quantize(amount)
        if amount == 0:
            raise ValueError('Сумма корректировки не может быть нулевой')

        # Получаем и блокируем клиента
        if isinstance(customer, int):
            customer = Customer.objects.select_for_update().get(pk=customer)
        else:
            customer = Customer.objects.select_for_update().get(pk=customer.pk)

        # Получаем текущий баланс
        current_balance = customer.get_wallet_balance(category=category, use_cache=False)

        # Проверяем, что баланс не уйдёт в минус
        if current_balance + amount < 0:
            raise ValueError(
                f'Корректировка приведёт к отрицательному балансу. '
                f'Текущий баланс: {current_balance}, корректировка: {amount}'
            )

        # Вычисляем баланс после
        balance_after = current_balance + amount

        # Создаём транзакцию
        txn = WalletTransaction.objects.create(
            customer=customer,
            signed_amount=amount,  # Может быть положительной или отрицательной
            transaction_type='adjustment',
            balance_category=category,
            order=None,
            description=description,
            created_by=user,
            balance_after=balance_after
        )

        # Инвалидируем кеш
        customer.invalidate_wallet_cache(category=category)

        return txn

    @staticmethod
    @transaction.atomic
    def pay_with_wallet(order, amount, user):
        """
        Оплата заказа из кошелька клиента.

        Args:
            order: Заказ для оплаты
            amount: Запрашиваемая сумма
            user: Пользователь

        Returns:
            Decimal: Фактически списанная сумма или None
        """
        from customers.models import Customer
        from orders.services.transaction_service import TransactionService

        amount = _quantize(amount)
        if amount <= 0:
            return None

        # Блокируем клиента для проверки баланса
        customer = Customer.objects.select_for_update().get(pk=order.customer_id)

        # Текущий баланс
        wallet_balance = customer.get_wallet_balance(use_cache=False)

        # Остаток к оплате
        amount_due = order.total_amount - order.amount_paid

        # Фактическая сумма (минимум из трёх)
        usable_amount = min(amount, wallet_balance, amount_due)
        usable_amount = _quantize(usable_amount)

        if usable_amount <= 0:
            return None

        # Создаём транзакцию платежа
        # Transaction.save() вызовет create_wallet_spend()
        TransactionService.create_payment(
            order=order,
            amount=usable_amount,
            payment_method='account_balance',
            user=user,
            notes='Оплата из кошелька клиента'
        )

        return usable_amount

    @staticmethod
    @transaction.atomic
    def refund_wallet_payment(order, amount, user):
        """
        Возврат средств в кошелёк.

        Args:
            order: Заказ
            amount: Сумма возврата
            user: Пользователь

        Returns:
            Decimal: Возвращённая сумма
        """
        from orders.services.transaction_service import TransactionService

        amount = _quantize(amount)
        if amount <= 0:
            return None

        # Создаём транзакцию возврата
        # Transaction.save() вызовет create_wallet_deposit()
        TransactionService.create_refund(
            order=order,
            amount=amount,
            payment_method='account_balance',
            user=user,
            reason='Возврат в кошелёк'
        )

        return amount

    @staticmethod
    @transaction.atomic
    def adjust_balance(customer_id, amount, description, user):
        """
        Корректировка баланса (обёртка для обратной совместимости).

        Args:
            customer_id: ID клиента
            amount: Сумма (может быть отрицательной)
            description: Описание
            user: Пользователь

        Returns:
            WalletTransaction
        """
        return WalletService.create_adjustment(
            customer=customer_id,
            amount=amount,
            description=description,
            user=user
        )

    # ========== МЕТОДЫ ДЛЯ ВЫЗОВА ИЗ Transaction.save() ==========

    @staticmethod
    @transaction.atomic
    def create_wallet_spend(order, amount, user):
        """
        Списание из кошелька при оплате заказа.
        Вызывается из Transaction.save() при payment.

        Args:
            order: Заказ
            amount: Сумма списания
            user: Пользователь

        Returns:
            WalletTransaction
        """
        return WalletService.create_transaction(
            customer=order.customer,
            amount=amount,
            transaction_type='spend',
            order=order,
            description=f'Оплата по заказу #{order.order_number}',
            user=user
        )

    @staticmethod
    @transaction.atomic
    def create_wallet_deposit(order, amount, user):
        """
        Пополнение кошелька при возврате.
        Вызывается из Transaction.save() при refund.

        Args:
            order: Заказ
            amount: Сумма возврата
            user: Пользователь

        Returns:
            WalletTransaction
        """
        return WalletService.create_transaction(
            customer=order.customer,
            amount=amount,
            transaction_type='deposit',
            order=order,
            description=f'Возврат по заказу #{order.order_number}',
            user=user
        )

    # ========== МЕТОДЫ ДЛЯ БУДУЩЕЙ БОНУСНОЙ СИСТЕМЫ ==========

    # @staticmethod
    # @transaction.atomic
    # def accrue_bonus(customer, amount, reason, user=None, order=None):
    #     """Начислить бонусные баллы."""
    #     return WalletService.create_transaction(
    #         customer=customer,
    #         amount=amount,
    #         transaction_type='bonus_accrual',
    #         category='bonus',
    #         order=order,
    #         description=reason,
    #         user=user
    #     )

    # @staticmethod
    # @transaction.atomic
    # def spend_bonus(customer, amount, order, user):
    #     """Списать бонусы за оплату."""
    #     return WalletService.create_transaction(
    #         customer=customer,
    #         amount=amount,
    #         transaction_type='bonus_spend',
    #         category='bonus',
    #         order=order,
    #         description=f'Оплата бонусами по заказу #{order.order_number}',
    #         user=user
    #     )
