"""
Сервис для работы с финансовыми транзакциями заказа.
Обрабатывает платежи и возвраты.
"""
from decimal import Decimal, ROUND_HALF_UP
from django.db import transaction
from django.core.exceptions import ValidationError


# Константа для округления до 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 TransactionService:
    """
    Сервис для управления финансовыми транзакциями заказа.
    Все операции атомарны.
    """

    @staticmethod
    @transaction.atomic
    def create_payment(order, amount, payment_method, user, notes=None):
        """
        Создать платёж по заказу.

        Args:
            order: Заказ для оплаты
            amount: Сумма платежа
            payment_method: Способ оплаты (PaymentMethod или code)
            user: Пользователь, создающий платёж
            notes: Примечания

        Returns:
            Transaction: Созданная транзакция платежа
        """
        from orders.models import Transaction, PaymentMethod

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

        # Получаем PaymentMethod если передан code
        if isinstance(payment_method, str):
            try:
                payment_method = PaymentMethod.objects.get(code=payment_method)
            except PaymentMethod.DoesNotExist:
                raise ValueError(f'Способ оплаты "{payment_method}" не найден')

        # Ограничение для кошелька: нельзя оплатить больше чем к оплате
        if payment_method.code == 'account_balance' and amount > order.amount_due:
            raise ValidationError(
                f'Сумма оплаты из кошелька ({amount} руб.) не может превышать '
                f'остаток к оплате ({order.amount_due} руб.)'
            )

        # Создаём транзакцию
        txn = Transaction.objects.create(
            order=order,
            transaction_type='payment',
            amount=amount,
            payment_method=payment_method,
            created_by=user,
            notes=notes or f'Платёж {payment_method.name}'
        )

        return txn

    @staticmethod
    @transaction.atomic
    def create_refund(order, amount, payment_method, user, reason=None, notes=None, related_payment=None):
        """
        Создать возврат по заказу.

        Args:
            order: Заказ для возврата
            amount: Сумма возврата
            payment_method: Способ возврата (PaymentMethod или code)
            user: Пользователь, создающий возврат
            reason: Причина возврата
            notes: Примечания
            related_payment: Связанный платёж (Transaction ID или объект)

        Returns:
            Transaction: Созданная транзакция возврата
        """
        from orders.models import Transaction, PaymentMethod

        # Округляем сумму
        amount = _quantize(amount)
        if amount <= 0:
            raise ValueError('Сумма возврата должна быть положительной')

        # Проверяем, что не возвращаем больше чем оплачено
        if amount > order.amount_paid:
            raise ValidationError(
                f'Сумма возврата ({amount}) превышает оплаченную сумму ({order.amount_paid})'
            )

        # Получаем PaymentMethod если передан code
        if isinstance(payment_method, str):
            try:
                payment_method = PaymentMethod.objects.get(code=payment_method)
            except PaymentMethod.DoesNotExist:
                raise ValueError(f'Способ оплаты "{payment_method}" не найден')

        # Получаем related_payment если передан ID
        if isinstance(related_payment, int):
            try:
                related_payment = Transaction.objects.get(pk=related_payment, transaction_type='payment')
            except Transaction.DoesNotExist:
                raise ValueError(f'Платёж #{related_payment} не найден')

        # Создаём транзакцию возврата
        txn = Transaction.objects.create(
            order=order,
            transaction_type='refund',
            amount=amount,
            payment_method=payment_method,
            related_payment=related_payment,
            reason=reason or 'Возврат средств',
            notes=notes,
            created_by=user
        )

        return txn

    @staticmethod
    @transaction.atomic
    def create_full_refund(order, user, reason=None):
        """
        Полный возврат всей оплаченной суммы заказа.
        Возвращает средства пропорционально платежам (LIFO - последний вошёл, первый вышел).

        Args:
            order: Заказ для возврата
            user: Пользователь, создающий возврат
            reason: Причина возврата

        Returns:
            list[Transaction]: Список созданных транзакций возврата
        """
        from orders.models import Transaction

        if order.amount_paid <= 0:
            raise ValidationError('Нет средств для возврата')

        refunds = []
        remaining_to_refund = order.amount_paid

        # Берём платежи в обратном порядке (LIFO)
        payments = order.transactions.filter(transaction_type='payment').order_by('-transaction_date')

        for payment in payments:
            if remaining_to_refund <= 0:
                break

            # Сколько уже вернули по этому платежу
            from django.db.models import Sum
            already_refunded = payment.refunds.aggregate(
                total=Sum('amount')
            )['total'] or Decimal('0')

            # Сколько можно вернуть по этому платежу
            available_for_refund = payment.amount - already_refunded

            if available_for_refund <= 0:
                continue

            # Возвращаем минимум из доступного и оставшегося
            refund_amount = min(available_for_refund, remaining_to_refund)

            # Создаём возврат
            refund = TransactionService.create_refund(
                order=order,
                amount=refund_amount,
                payment_method=payment.payment_method,
                user=user,
                reason=reason or 'Полный возврат заказа',
                related_payment=payment
            )

            refunds.append(refund)
            remaining_to_refund -= refund_amount

        return refunds

    @staticmethod
    def get_payment_summary(order):
        """
        Получить сводку по платежам и возвратам заказа.

        Args:
            order: Заказ

        Returns:
            dict: Сводка с суммами платежей, возвратов и балансом
        """
        from django.db.models import Sum
        from decimal import Decimal

        payments_sum = order.transactions.filter(
            transaction_type='payment'
        ).aggregate(total=Sum('amount'))['total'] or Decimal('0')

        refunds_sum = order.transactions.filter(
            transaction_type='refund'
        ).aggregate(total=Sum('amount'))['total'] or Decimal('0')

        return {
            'payments_total': payments_sum,
            'refunds_total': refunds_sum,
            'amount_paid': payments_sum - refunds_sum,
            'amount_due': max(order.total_amount - (payments_sum - refunds_sum), Decimal('0')),
            'payments_count': order.transactions.filter(transaction_type='payment').count(),
            'refunds_count': order.transactions.filter(transaction_type='refund').count(),
        }

    @staticmethod
    def get_refundable_amount(order):
        """
        Получить сумму, доступную для возврата.

        Args:
            order: Заказ

        Returns:
            Decimal: Сумма, которую можно вернуть
        """
        return max(order.amount_paid, Decimal('0'))

    @staticmethod
    def get_available_payment_methods(exclude_codes=None, only_active=True):
        """
        Получить список доступных способов оплаты.

        Args:
            exclude_codes: list[str] - коды для исключения (например ['legal_entity'])
            only_active: bool - только активные методы

        Returns:
            QuerySet[PaymentMethod]
        """
        from orders.models import PaymentMethod

        qs = PaymentMethod.objects.all()

        if only_active:
            qs = qs.filter(is_active=True)

        if exclude_codes:
            qs = qs.exclude(code__in=exclude_codes)

        return qs.order_by('order', 'name')

    @staticmethod
    @transaction.atomic
    def create_multiple_payments(order, payments_list, user):
        """
        Создать несколько платежей за одну транзакцию (смешанная оплата).

        Args:
            order: Order
            payments_list: list[dict] - [{'payment_method': code_or_object, 'amount': Decimal, 'notes': str}, ...]
            user: CustomUser

        Returns:
            list[Transaction]

        Raises:
            ValidationError: если сумма превышает amount_due или недостаточно средств
        """
        from orders.models import Transaction

        transactions = []
        total_amount = Decimal('0')

        # Валидация общей суммы
        for payment_data in payments_list:
            amount = _quantize(payment_data['amount'])
            if amount <= 0:
                raise ValueError(f'Сумма платежа должна быть положительной: {amount}')
            total_amount += amount

        if total_amount > order.amount_due:
            raise ValidationError(
                f'Общая сумма платежей ({total_amount}) превышает сумму к оплате ({order.amount_due})'
            )

        # Создаём транзакции
        for payment_data in payments_list:
            txn = TransactionService.create_payment(
                order=order,
                amount=payment_data['amount'],
                payment_method=payment_data['payment_method'],
                user=user,
                notes=payment_data.get('notes')
            )
            transactions.append(txn)

        return transactions
