"""Serializers for payment deposit and withdrawal APIs."""

from __future__ import annotations

from decimal import Decimal, ROUND_HALF_UP
from django.db import transaction
from django.utils import timezone
from rest_framework import serializers
from rest_framework.exceptions import ValidationError

from django_wallet_utils.exceptions import InsufficientBalanceError
from apps.transactions.models import Transaction
from utils.wallet import get_wallet_service, WITHDRAWAL

from .models import Deposit, PaymentAddress, WithdrawalRequest

DECIMAL_QUANTIZE = Decimal("0.00000001")

# Fee configurations
WITHDRAWAL_FEE_PERCENT = Decimal("1.00")  # 1%
WITHDRAWAL_FEE_FIXED = {
    "tron": Decimal("3.00"),
    "bnb": Decimal("2.00"),
}
MIN_WITHDRAWAL = Decimal("10.00")
MIN_DEPOSIT = Decimal("5.00")


class PaymentAddressSerializer(serializers.ModelSerializer):
    """Serializer for deposit addresses."""

    class Meta:
        model = PaymentAddress
        fields = [
            "id",
            "blockchain",
            "asset",
            "address",
            "status",
            "label",
            "expires_at",
            "created_at",
        ]
        read_only_fields = fields


class DepositSerializer(serializers.ModelSerializer):
    """Serializer for deposit history."""

    class Meta:
        model = Deposit
        fields = [
            "id",
            "blockchain",
            "asset",
            "amount",
            "transaction_hash",
            "confirmations",
            "status",
            "detected_at",
            "confirmed_at",
        ]
        read_only_fields = fields


class WithdrawalRequestSerializer(serializers.ModelSerializer):
    """Serializer for withdrawal creation and history."""
    
    fee_amount = serializers.DecimalField(
        max_digits=20,
        decimal_places=8,
        read_only=True,
        help_text="Calculated withdrawal fee"
    )
    net_amount = serializers.DecimalField(
        max_digits=20,
        decimal_places=8,
        read_only=True,
        help_text="Amount after fees (what user receives)"
    )

    class Meta:
        model = WithdrawalRequest
        fields = [
            "id",
            "blockchain",
            "asset",
            "amount",
            "destination_address",
            "status",
            "transaction_hash",
            "fee_amount",
            "net_amount",
            "requested_at",
            "processed_at",
        ]
        read_only_fields = [
            "id",
            "status",
            "transaction_hash",
            "fee_amount",
            "net_amount",
            "requested_at",
            "processed_at",
        ]

    def validate_amount(self, value):
        """Validate withdrawal amount is above minimum."""
        if value < MIN_WITHDRAWAL:
            raise ValidationError(
                f"Minimum withdrawal amount is ${MIN_WITHDRAWAL}"
            )
        return value

    def validate_blockchain(self, value):
        """Validate blockchain is supported."""
        if value.lower() not in ["tron", "bnb"]:
            raise ValidationError("Only 'tron' and 'bnb' networks are supported")
        return value.lower()

    def validate(self, attrs):
        """Validate user has sufficient balance."""
        user = self.context["request"].user
        amount = Decimal(str(attrs["amount"])).quantize(DECIMAL_QUANTIZE, rounding=ROUND_HALF_UP)
        blockchain = attrs["blockchain"].lower()
        
        # Calculate fee
        fee_percent = amount * WITHDRAWAL_FEE_PERCENT / Decimal("100")
        fee_fixed = WITHDRAWAL_FEE_FIXED.get(blockchain, Decimal("0"))
        total_fee = (fee_percent + fee_fixed).quantize(DECIMAL_QUANTIZE, rounding=ROUND_HALF_UP)
        
        # Total deduction = requested amount + fee
        # (User requests X, we deduct X + fee from their balance, send X to their address)
        total_deduction = (amount + total_fee).quantize(DECIMAL_QUANTIZE, rounding=ROUND_HALF_UP)
        
        if user.credit_balance < total_deduction:
            available_str = f"{user.credit_balance:.8f}"
            required_str = f"{total_deduction:.8f}"
            raise ValidationError({
                "amount": [
                    f"Insufficient balance. Available: {available_str}, "
                    f"Required (including fees): {required_str}"
                ]
            })
        
        # Store calculated values for create method
        attrs["_total_fee"] = total_fee
        attrs["_total_deduction"] = total_deduction
        
        return attrs

    def create(self, validated_data):
        """Create withdrawal request and deduct balance."""
        user = self.context["request"].user
        amount = Decimal(str(validated_data["amount"])).quantize(DECIMAL_QUANTIZE, rounding=ROUND_HALF_UP)
        blockchain = validated_data["blockchain"]
        destination_address = validated_data["destination_address"]
        
        total_fee = validated_data.pop("_total_fee")
        total_deduction = validated_data.pop("_total_deduction")
        
        wallet_service = get_wallet_service()

        with transaction.atomic():
            # Lock user row
            user_locked = user.__class__.objects.select_for_update().get(pk=user.pk)
            balance_before = user_locked.credit_balance

            # Deduct balance
            try:
                wallet_service.deduct_point(
                    user_id=user_locked.id,
                    point_type="credit_balance",
                    amount=total_deduction,
                    remarks=f"Withdrawal request to {destination_address}",
                    trans_type=WITHDRAWAL,
                    allow_negative=False,
                    iid=user_locked.id,
                )
            except InsufficientBalanceError as e:
                available = Decimal(str(e.available)) if e.available is not None else Decimal("0")
                required = Decimal(str(e.requested)) if e.requested is not None else Decimal("0")
                raise ValidationError({
                    "amount": [
                        f"Insufficient balance. Available: {available:.8f}, "
                        f"Required: {required:.8f}"
                    ]
                })

            # Refresh user
            user_locked.refresh_from_db()
            balance_after = user_locked.credit_balance

            # Create transaction record
            tx = Transaction.objects.create(
                user=user_locked,
                transaction_type=Transaction.TYPE_WITHDRAWAL,
                amount=total_deduction,
                status=Transaction.STATUS_PENDING,
                balance_before=balance_before,
                balance_after=balance_after,
                blockchain=blockchain,
                data={
                    "destination_address": destination_address,
                    "requested_amount": str(amount),
                    "fee_amount": str(total_fee),
                    "net_amount": str(amount),  # User receives the requested amount
                },
            )

            # Create withdrawal request
            withdrawal = WithdrawalRequest.objects.create(
                user=user_locked,
                transaction=tx,
                blockchain=blockchain,
                asset="USDT",
                amount=amount,
                destination_address=destination_address,
                status=WithdrawalRequest.STATUS_REQUESTED,
                metadata={
                    "fee_amount": str(total_fee),
                    "total_deduction": str(total_deduction),
                },
            )

            return withdrawal

    def to_representation(self, instance):
        """Add calculated fee and net amount to response."""
        data = super().to_representation(instance)
        
        # Extract from metadata if available
        if instance.metadata:
            data["fee_amount"] = instance.metadata.get("fee_amount", "0.00000000")
            data["net_amount"] = str(instance.amount)  # Net is the requested amount
        else:
            data["fee_amount"] = "0.00000000"
            data["net_amount"] = str(instance.amount)
        
        return data
