# accounts/Unified_Payment_Refactored_Endpoints.py
# ✅ 1. CheckPaymentOptionsAPI → CheckPaymentOptionsView

from decimal import Decimal, ROUND_HALF_UP, InvalidOperation
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import TokenAuthentication
from payments.views import get_wallet_balances


def q2(x: Decimal) -> Decimal:
    return (x or Decimal("0")).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)


class CheckPaymentOptionsView(APIView):
    """
    POST /api/payment-options/
    {
        "service": "bbps",   // "recharge", "voucher", etc.
        "amount": 399
    }
    """
    # authentication_classes = [TokenAuthentication]
    # permission_classes = [IsAuthenticated]

    def post(self, request):
        amount_in = request.data.get("amount")      # bill amount

        try:
            if amount_in is None:
                raise InvalidOperation
            amount = q2(Decimal(str(amount_in)))
        except (InvalidOperation, TypeError, ValueError):
            return Response({"error": "Invalid amount"}, status=status.HTTP_400_BAD_REQUEST)

        if amount <= 0:
            return Response({"error": "Amount must be greater than zero"}, status=status.HTTP_400_BAD_REQUEST)

        ext_balance, vp_balance = get_wallet_balances(request.user)
        ext_balance = q2(ext_balance)
        vp_balance = q2(vp_balance)

        use_ext = min(ext_balance, amount)
        remaining = amount - use_ext

        use_vp = min(vp_balance, remaining)
        remaining = amount - (use_ext + use_vp)

        razorpay_required = remaining > Decimal("0.00")
        razorpay_amount_rupees = int(remaining.quantize(Decimal("1"), rounding=ROUND_HALF_UP)) if razorpay_required else 0

        return Response({
            "wallets": {
                "external": {"balance": str(q2(ext_balance)), "can_use": ext_balance > 0},
                "viralpe":  {"balance": str(q2(vp_balance)),  "can_use": vp_balance  > 0},
            },
            "recommended": {
                "use_external": use_ext > 0,
                "use_viralpe":  use_vp  > 0,
                "razorpay_required": razorpay_required,
                "razorpay_amount": razorpay_amount_rupees
            },
            "splits": {
                "external": str(q2(use_ext)),
                "viralpe":  str(q2(use_vp)),
            }
        }, status=status.HTTP_200_OK)


# ✅ 2. RazorpayCreateOrderAPI → InitiatePaymentView

import uuid, razorpay
from decimal import Decimal
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import TokenAuthentication
from payments.models import PaymentTransaction

from decimal import Decimal, ROUND_HALF_UP
from django.db import transaction, IntegrityError
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import TokenAuthentication
from django.conf import settings
import razorpay

from payments.ids import generate_client_txn_id
from payments.models import PaymentTransaction  # adjust import

class InitiatePaymentView(APIView):
    """
    POST /api/payments/initiate/
    {
        "amount": 229,
        "service": "bbps"
    }
    """
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]

    def post(self, request):
        try:
            user = request.user
            raw_amount = request.data.get("amount", "0")
            service = (request.data.get("service") or "generic").lower()
            # Normalize & validate amount
            amount = Decimal(str(raw_amount)).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
            if amount <= 0:
                return Response({"success": False, "message": "Invalid amount"}, status=400)

            # 1) Reserve a unique order_id in DB (race-safe with retry)
            order_id = None
            for _ in range(5):
                candidate = generate_client_txn_id("VP", 10)  # e.g., VP8CH3B2LQ
                try:
                    with transaction.atomic():
                        pt = PaymentTransaction.objects.create(
                            user=user,
                            order_id=candidate,
                            amount=amount,              # to be verified , we should actually sdtoe total bill/recharg amount
                            wallet_amount=Decimal("0.00"),   # <-- REQUIRED
                            gateway_amount=amount,      # this is the amount to be deducted from razorpay
                            service=service,            # Here Service needs to be fetchd from front end as individual not bbps
                            status="initiating",
                        )
                    # print(pt)
                    order_id = candidate
                    # print("++++++++")
                    # print(order_id)
                    break
                except IntegrityError:
                    # extremely rare collision, try again
                    continue
            if not order_id:
                return Response({"success": False, "message": "Could not allocate order id"}, status=500)

            # 2) Create Razorpay order using our reserved order_id as receipt
            client = razorpay.Client(auth=(settings.RAZORPAY_API_KEY, settings.RAZORPAY_API_SECRET))
            amount_paise = int((amount * 100).to_integral_value(rounding=ROUND_HALF_UP))
            rzp_order = client.order.create({
                "amount": amount_paise,
                "currency": "INR",
                "receipt": order_id,   # <= 40 chars; ours is 10
                "payment_capture": 1,
                "notes": {"purpose": service},
            })

            # 3) Update our row with Razorpay order id and mark initiated
            PaymentTransaction.objects.filter(order_id=order_id).update(
                razorpay_order_id=rzp_order["id"],
                status="initiated",
            )

            return Response({
                "success": True,
                "order_id": order_id,                      # your client txn id
                "razorpay_order_id": rzp_order["id"],
                "amount": rzp_order["amount"],
                "currency": rzp_order["currency"],
                "key_id": settings.RAZORPAY_API_KEY,
            })

        except Exception as e:
            # If something blew up after reserving order_id, mark it failed (best-effort)
            try:
                if 'order_id' in locals() and order_id:
                    PaymentTransaction.objects.filter(order_id=order_id).update(status="failed")
            except Exception:
                pass
            return Response({"success": False, "message": str(e)}, status=500)


# class InitiatePaymentView(APIView):
#     """
#     POST /api/payments/initiate/
#     {
#         "amount": 229,
#         "service": "bbps"
#     }
#     """
#     authentication_classes = [TokenAuthentication]
#     permission_classes = [IsAuthenticated]

#     def post(self, request):
#         try:
#             user = request.user
#             amount = Decimal(str(request.data.get("amount", "0")))
#             service = request.data.get("service", "generic")
#             number = user.mobile_number

#             if amount <= 0:
#                 return Response({"status": "error", "message": "Invalid amount"}, status=400)

#             # order_id = f"VP-{user.id}-{number[-4:]}-{uuid.uuid4().hex[:6].upper()}"
#             order_id = generate_unique_provider_txnid("VP", 10)
#             razorpay_due = int(amount * 100)
#             # razorpay_due = int(amount)

#             client = razorpay.Client(auth=(settings.RAZORPAY_API_KEY, settings.RAZORPAY_API_SECRET))
#             rzp_order = client.order.create({
#                 # "amount": 10,
#                 "amount": razorpay_due,
#                 "currency": "INR",
#                 "receipt": order_id,
#                 "payment_capture": 1,
#                 "notes": {"purpose": service}
#             })

#             PaymentTransaction.objects.create(
#                 user=user,
#                 order_id=order_id,
#                 amount=float(amount),
#                 service=service,
#                 razorpay_order_id=rzp_order["id"],
#                 status="initiated"
#             )

#             return Response({
#                 "success": True,
#                 "order_id": order_id,
#                 "razorpay_order_id": rzp_order["id"],
#                 "amount": rzp_order["amount"],
#                 "currency": rzp_order["currency"],
#                 "key_id": settings.RAZORPAY_API_KEY,
#             })

#         except Exception as e:
#             return Response({"status": "error", "message": str(e)}, status=500)
        
# ✅ 3. RazorpayVerifyPaymentAPI → VerifyPaymentView

# import razorpay
# from django.conf import settings
# from rest_framework.views import APIView
# from rest_framework.response import Response
# from rest_framework import status
# from rest_framework.permissions import IsAuthenticated
# from rest_framework.authentication import TokenAuthentication

# from payments.models import PaymentTransaction


# class VerifyPaymentView(APIView):
#     """
#     POST /api/payments/verify/
#     {
#         "order_id": "VP-1-ABC123",
#         "razorpay_order_id": "...",
#         "razorpay_payment_id": "...",
#         "razorpay_signature": "..."
#     }
#     """
#     authentication_classes = [TokenAuthentication]
#     permission_classes = [IsAuthenticated]

#     def post(self, request):
#         try:
#             order_id = request.data.get("order_id")
#             rzp_order_id = request.data.get("razorpay_order_id")
#             rzp_payment_id = request.data.get("razorpay_payment_id")
#             rzp_signature = request.data.get("razorpay_signature")

#             if not (order_id and rzp_order_id and rzp_payment_id and rzp_signature):
#                 return Response(
#                     {"verified": False, "message": "Missing one or more required fields."},
#                     status=status.HTTP_400_BAD_REQUEST
#                 )

#             client = razorpay.Client(auth=(settings.RAZORPAY_API_KEY, settings.RAZORPAY_API_SECRET))
#             client.utility.verify_payment_signature({
#                 "razorpay_order_id": rzp_order_id,
#                 "razorpay_payment_id": rzp_payment_id,
#                 "razorpay_signature": rzp_signature,
#             })

#             tx = PaymentTransaction.objects.filter(razorpay_order_id=rzp_order_id).first()
#             if not tx:
#                 return Response({"verified": False, "message": "Transaction not found."}, status=404)

#             if tx.status not in ("success", "verified"):
#                 tx.status = "verified"
#                 tx.razorpay_payment_id = rzp_payment_id
#                 tx.razorpay_signature = rzp_signature
#                 tx.save()

#             return Response({
#                 "verified": True,
#                 "order_id": tx.order_id,
#                 "service": tx.service,
#                 "razorpay_payment_id": rzp_payment_id
#             }, status=200)

#         except razorpay.errors.SignatureVerificationError:
#             return Response({"verified": False, "message": "Invalid payment signature"}, status=400)
#         except Exception as e:
#             return Response({"verified": False, "message": str(e)}, status=500)

import razorpay
from django.conf import settings
from django.utils import timezone
from django.db import transaction
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import TokenAuthentication

from payments.models import PaymentTransaction  # adjust import


class VerifyPaymentView(APIView):
    """
    POST /api/payments/verify/
    {
        "order_id": "VPXXXXXX",             # optional for cross-check
        "razorpay_order_id": "...",         # required
        "razorpay_payment_id": "...",       # required
        "razorpay_signature": "..."         # required
    }
    """
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]

    def post(self, request):
        order_id = request.data.get("order_id")  # optional cross-check only
        rzp_order_id = request.data.get("razorpay_order_id")
        rzp_payment_id = request.data.get("razorpay_payment_id")
        rzp_signature = request.data.get("razorpay_signature")

        if not (rzp_order_id and rzp_payment_id and rzp_signature):
            return Response(
                {"verified": False, "message": "razorpay_order_id, razorpay_payment_id, razorpay_signature are required."},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            client = razorpay.Client(auth=(settings.RAZORPAY_API_KEY, settings.RAZORPAY_API_SECRET))

            # 1) Verify signature (order_id|payment_id signed with secret)
            client.utility.verify_payment_signature({
                "razorpay_order_id": rzp_order_id,
                "razorpay_payment_id": rzp_payment_id,
                "razorpay_signature": rzp_signature,
            })

            # 2) Lock the row to avoid races; also enforce ownership
            with transaction.atomic():
                tx = (
                    PaymentTransaction.objects
                    .select_for_update()
                    .filter(razorpay_order_id=rzp_order_id)
                    .first()
                )
                if not tx:
                    return Response({"verified": False, "message": "Transaction not found."}, status=status.HTTP_404_NOT_FOUND)

                if tx.user_id != request.user.id:
                    return Response({"verified": False, "message": "Not allowed for this transaction."}, status=status.HTTP_403_FORBIDDEN)

                # Optional cross-check (don’t rely on client’s order_id)
                if order_id and order_id != tx.order_id:
                    return Response({"verified": False, "message": "Order mismatch."}, status=status.HTTP_400_BAD_REQUEST)

                # Idempotent success
                if tx.status in ("verified", "success"):
                    return Response({
                        "verified": True,
                        "order_id": tx.order_id,
                        "service": tx.service,
                        "razorpay_payment_id": tx.razorpay_payment_id,
                    }, status=status.HTTP_200_OK)

                # 3) Fetch payment from Razorpay and validate amount/currency
                payment = client.payment.fetch(rzp_payment_id)
                # If you store amount_paise as an int, prefer that:
                expected_amount_paise = int(round(float(tx.amount) * 100))
                if payment.get("amount") != expected_amount_paise:
                    return Response({"verified": False, "message": "Amount mismatch."}, status=status.HTTP_400_BAD_REQUEST)
                if payment.get("currency") != "INR":
                    return Response({"verified": False, "message": "Currency mismatch."}, status=status.HTTP_400_BAD_REQUEST)

                # Optional: ensure the payment is captured (you set payment_capture=1 when creating orders)
                if payment.get("status") not in ("captured", "authorized"):
                    # If 'authorized' and you rely on auto-capture, you may wait or call capture here.
                    # Usually with payment_capture=1 it returns 'captured'.
                    pass

                # 4) Persist success
                tx.status = "verified"
                tx.razorpay_payment_id = rzp_payment_id
                # only if you have this field in your model
                if hasattr(tx, "razorpay_signature"):
                    tx.razorpay_signature = rzp_signature
                if hasattr(tx, "verified_at"):
                    tx.verified_at = timezone.now()
                tx.save(update_fields=[
                    "status", "razorpay_payment_id",
                    *(["razorpay_signature"] if hasattr(tx, "razorpay_signature") else []),
                    *(["verified_at"] if hasattr(tx, "verified_at") else []),
                ])

            return Response({
                "verified": True,
                "order_id": tx.order_id,
                "service": tx.service,
                "razorpay_payment_id": rzp_payment_id,
            }, status=status.HTTP_200_OK)

        except razorpay.errors.SignatureVerificationError:
            return Response({"verified": False, "message": "Invalid payment signature."}, status=status.HTTP_400_BAD_REQUEST)
        except razorpay.errors.BadRequestError as e:
            return Response({"verified": False, "message": f"Razorpay error: {e}"}, status=status.HTTP_400_BAD_REQUEST)
        except Exception as e:
            return Response({"verified": False, "message": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)


# class PaymentTransaction(models.Model):
#     SERVICE_CHOICES = [
#         ('recharge', 'Recharge'),
#         ('bbps', 'BBPS'),
#         ('voucher', 'Voucher'),
#         ('subscription', 'Subscription'),
#         ('other', 'Other'),
#     ]

#     STATUS_CHOICES = [
#         ('initiated', 'Initiated'),
#         ('verified', 'Verified'),
#         ('success', 'Success'),
#         ('failed', 'Failed'),
#         ('cancelled', 'Cancelled'),
#     ]

#     user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

#     service = models.CharField(max_length=30, choices=SERVICE_CHOICES, default='other')
#     amount = models.DecimalField(max_digits=10, decimal_places=2)

#     order_id = models.CharField(max_length=100, unique=True)
#     razorpay_order_id = models.CharField(max_length=100, null=True, blank=True, unique=True)
#     razorpay_payment_id = models.CharField(max_length=100, blank=True, null=True)
#     razorpay_signature = models.TextField(blank=True, null=True)

#     status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="initiated")

#     created_at = models.DateTimeField(auto_now_add=True)
#     updated_at = models.DateTimeField(auto_now=True)

#     metadata = models.JSONField(blank=True, null=True)  # 🆕 Optional extra info per use case

#     def __str__(self):
#         return f"{self.user} | INR {self.amount} | {self.status} | {self.service}"


# =========================================================================================

# Overall Architecture
# PaymentPerformAPI (entrypoint)
# ├── validate_payment_request()        # common validation
# ├── resolve_order_and_payment_refs()  # wallet vs gateway
# ├── compute_wallet_split()
# ├── deduct_wallets()
# ├── update RechargePaymentSummary
# ├── dispatch_service_payment()        # core dispatcher
# │   ├── perform_wallet_recharge()     # for recharges
# │       └── initiate_recharge()
# │           └── handle_recharge_outcome()
# │   ├── perform_bbps_payment()
# │   ├── perform_voucher_purchase()
# └── Return unified response
# =========================================================================================


from decimal import Decimal, InvalidOperation, ROUND_HALF_UP

TWOPL = Decimal("0.01")

def _d(x) -> Decimal:
    try:
        return Decimal(str(x))
    except (InvalidOperation, ValueError, TypeError):
        return Decimal("0")

def q2(x: Decimal) -> Decimal:
    return _d(x).quantize(TWOPL, rounding=ROUND_HALF_UP)

def to_paise(x: Decimal) -> int:
    # Integer paise for gateways/providers
    return int((q2(x) * 100).quantize(Decimal("1"), rounding=ROUND_HALF_UP))

def as_bool(x) -> bool:
    # Accepts true/false, "true"/"false", 1/0, "1"/"0"
    if isinstance(x, bool):
        return x
    s = str(x).strip().lower()
    return s in {"1", "true", "yes", "y", "on"}

# =========================================================================================

from threading import Thread
from django.utils.timezone import now

def perform_in_background(service, user, metadata, amount_rupees, amount_paise,
                          order_id, client_txnid, rzp_payment_id,
                          ext_use_rupees, vp_use_rupees):
    log = logging.getLogger(__name__)
    try:
        result = dispatch_service_payment(
            service=service,
            user=user,
            metadata=metadata,
            # amounts
            order_id=order_id,
            client_txn_id=client_txnid,
            amount_rupees=str(q2(amount_rupees)),   # exact 2dp as string
            amount_paise=amount_paise,              # int
            # splits (rupees)
            use_ext=str(q2(ext_use_rupees)),
            use_int=str(q2(vp_use_rupees)),
            # identifiers
            # gateway label / refs
            razorpay_payment_id=rzp_payment_id,
            gateway_ref=rzp_payment_id or "WALLET_ONLY",
        )

        RechargePaymentSummary.objects.filter(order_id=order_id).update(
            recharge_status=(result.get("status") or "Pending").title(),
            status_message=result.get("message") or "",
            # If provider ref exists, record it
            gateway_reference=(result.get("provider_ref")
                               or result.get("provider_order_id")
                               or rzp_payment_id
                               or client_txnid),
            updated_at=now(),
        )

        log.info("2. %s", result.get("status"))
        log.info("2. %s", str(result.get("status") or "Pending").title())

    except Exception as e:
        RechargePaymentSummary.objects.filter(order_id=order_id).update(
            recharge_status="Failed",
            status_message=str(e),
            updated_at=now(),
        )
        log.info("3. %s", "recharge_status=Failed")
        log.info("3. %s", str(e))
        

# def perform_in_background(service, user, data, amount, order_id, client_txnid, rzp_payment_id, ext_use, vp_use):
#     try:
#         result = dispatch_service_payment(
#             service=service,
#             user=user,
#             metadata=data,
#             amount=amount,
#             order_id=order_id,
#             client_txn_id=client_txnid,
#             use_ext=ext_use,
#             use_int=vp_use,
#             gateway_ref=rzp_payment_id or "WALLET_ONLY"
#         )
#         # Update DB with result
#         RechargePaymentSummary.objects.filter(order_id=order_id).update(
#             # provider_ref=result.get("provider_ref"),
#             recharge_status=result.get("status"),
#             updated_at=now()
#         )
#     except Exception as e:
#         RechargePaymentSummary.objects.filter(order_id=order_id).update(
#             recharge_status="FAILED",
#             status_message=str(e),
#             updated_at=now()
#         )


from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import TokenAuthentication
from uuid import uuid4

from payments.models import PaymentTransaction, RechargePaymentSummary
from payments.ids import generate_unique_provider_txnid
from payments.views import get_wallet_balances, deduct_wallets
from accounts.dispatcher import dispatch_service_payment
from payments.ids import generate_client_txn_id
from django.db import transaction, IntegrityError
from decimal import Decimal, ROUND_HALF_UP
import logging
from django.db import transaction
from accounts.executor import EXECUTOR
from django.utils.timezone import now
log = logging.getLogger(__name__)

class PaymentPerformAPI(APIView):
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]
    def post(self, request):
        user = request.user
        data = request.data
        # log.info("perform: service=%s, bill.service=%s", data.get("service"), (data.get("bill") or {}).get("service"))
        # ---- Validate base fields ----
        try:
            service  = (data.get("service") or "").lower().strip()
            amount   = q2(_d(data.get("amount", "0")))
            metadata = data
            # log.info("incoming payload %s", data)
            if not service or amount <= 0:
                return Response({"status": "FAILED", "message": "Invalid service or amount"}, status=400)
        except (InvalidOperation, TypeError, ValueError):
            return Response({"status": "FAILED", "message": "Invalid amount format"}, status=400)

        # ---- Razorpay involvement (hybrid) ----
        rzp_order_id   = data.get("razorpay_order_id") or None
        rzp_payment_id = data.get("razorpay_payment_id") or None
        rzp_signature  = data.get("razorpay_signature") or None
        has_rzp = bool(rzp_order_id and rzp_payment_id and rzp_signature)

        if has_rzp:
            try:
                tx = PaymentTransaction.objects.get(razorpay_order_id=rzp_order_id, user=user)
            except PaymentTransaction.DoesNotExist:
                return Response({"status": "FAILED", "message": "Unknown Razorpay order"}, status=404)

            if tx.status not in ("verified", "success"):
                return Response({"status": "FAILED", "message": "Payment not verified yet"}, status=400)

            order_id      = tx.order_id
            razorpay_paid = q2(_d(tx.gateway_amount))   # rupees
        else:
            # Reserve unique order_id
            order_id = None
            for _ in range(5):
                candidate = generate_client_txn_id("VP", 10)  # e.g., VP8CH3B2LQ
                try:
                    with transaction.atomic():
                        PaymentTransaction.objects.create(
                            user=user,
                            order_id=candidate,        # unique=True in model
                            amount=amount,
                            wallet_amount=amount,             # keep Decimal
                            gateway_amount=Decimal("0.00"), # <-- REQUIRED in your model
                            # amount_paise=int((amount * 100).to_integral_value(rounding=ROUND_HALF_UP)),  # optional field
                            service=service,
                            status="notrequired",           # or "initiating" if you prefer
                            wallet_status="initiated",
                        )
                    order_id = candidate
                    break
                except IntegrityError:
                    # rare collision: try another id
                    continue

            if not order_id:
                return Response({"success": False, "message": "Could not allocate order id"}, status=500)
            razorpay_paid = q2(Decimal("0"))

        # ---- Wallet requirement ----
        wallet_required = q2(amount - razorpay_paid)
        if wallet_required < 0:
            wallet_required = q2(Decimal("0"))

        # ---- Wallet usage flags ----
        use_ext = as_bool(data.get("use_external", False))
        use_vp  = as_bool(data.get("use_viralpe", False))

        # ---- Wallet balances (must be Decimal) ----
        ext_bal, vp_bal = get_wallet_balances(user)  # ensure these return Decimal
        ext_bal, vp_bal = q2(ext_bal), q2(vp_bal)

        # ---- Compute splits ----
        ext_use = q2(Decimal("0"))
        vp_use  = q2(Decimal("0"))
        remaining = wallet_required

        if remaining > 0 and use_ext and ext_bal > 0:
            ext_use   = q2(min(ext_bal, remaining))
            remaining = q2(remaining - ext_use)

        if remaining > 0 and use_vp and vp_bal > 0:
            vp_use    = q2(min(vp_bal, remaining))
            remaining = q2(remaining - vp_use)

        if remaining > 0:
            return Response(
                {"status": "FAILED", "message": f"Insufficient wallet balance; need {str(q2(remaining))} more"},
                status=400,
            )

        # ---- Conservation fix (rare rounding drift) ----
        if q2(ext_use + vp_use + razorpay_paid) != amount:
            drift = q2(amount - (ext_use + vp_use + razorpay_paid))
            if vp_use > 0:
                vp_use = q2(vp_use + drift)
            elif ext_use > 0:
                ext_use = q2(ext_use + drift)
            else:
                razorpay_paid = q2(razorpay_paid + drift)

        client_txnid = order_id  

        # ---- Summary upsert ----
        log.info("1. recharge_status : Pending")
        RechargePaymentSummary.objects.update_or_create(
            order_id=order_id,
            defaults={
                "user": user,
                "recharge_amount": amount,          # if DecimalField; else float(amount)
                "used_external_wallet": ext_use,
                "used_internal_wallet": vp_use,
                "paid_via_gateway": q2(amount - ext_use - vp_use),
                "gateway_reference": rzp_payment_id,
                "recharge_status": "Pending",
                "updated_at": now(),
            }
        )

        # ---- Deduct Wallets (atomic inside util) ----
        # try:
        #     if ext_use > 0 or vp_use > 0:
        #         deduct_wallets(request, amount, ext_use, vp_use, order_id, client_txnid)
        #         PaymentTransaction.objects.update_or_create(
        #             order_id=candidate,        # unique=True in model
        #             status="success",
        #         )
        # except Exception as e:
        #     return Response({"status": "FAILED", "message": f"Wallet deduction failed: {e}"}, status=400)
        
        try:
            if Decimal(ext_use) > 0 or Decimal(vp_use) > 0:
                deduct_wallets(request, amount, ext_use, vp_use, order_id, client_txnid)

            # If we get here, deduction succeeded (or nothing to deduct). Mark success.
            PaymentTransaction.objects.update_or_create(
                order_id=order_id,                      # unique/PK-like identifier
                defaults={
                    "wallet_status": "success",
                    "amount": Decimal(amount),
                    "wallet_amount": Decimal(vp_use),
                    "updated_at": timezone.now(),
                    # add any other fields you keep (user, method, etc.)
                }
            )

        except Exception as e:
            return Response({"status": "FAILED", "message": f"Wallet deduction failed: {e}"},status=400)


        # ---- Kick background worker AFTER COMMIT ----
        amt_paise = to_paise(amount)
        # log.info("ext_use=%s vp_use=%s amt_paise=%s amount=%s", ext_use, vp_use, amt_paise, amount)

        def _submit():
                # from accounts.jobs import perform_in_background_safe
                EXECUTOR.submit(
                    perform_in_background_safe,
                    service, user.id, metadata, str(amount), amt_paise, order_id, client_txnid,
                    rzp_payment_id, str(ext_use), str(vp_use)
                )

        transaction.on_commit(_submit)
        
        # Thread(
        #     target=perform_in_background,
        #     args=(
        #         service, user, metadata, amount, amt_paise, order_id, client_txnid,
        #         rzp_payment_id, ext_use, vp_use
        #     ),
        #     daemon=True,
        # ).start()

        # ---- Early response ----
        return Response({
            "status": "PENDING",
            "transaction_id": client_txnid,
            "message": "Payment initiated, awaiting confirmation",
            "receipt": {
                "service": service,
                "amount": str(amount),      # keep as string 2dp for exactness
                "time": now().isoformat(),
                # Avoid echoing sensitive fields from data; include only safe metadata if needed.
            }
        }, status=200)

from decimal import Decimal
from decimal import Decimal as Dec

from django.db import close_old_connections
from django.contrib.auth import get_user_model
import logging

log = logging.getLogger(__name__)

def perform_in_background_safe(
    service, user_id, metadata, amount_str, amt_paise, order_id, client_txn_id,
    rzp_payment_id, ext_use_str, vp_use_str,
):
    try:
        close_old_connections()
        # log.info(amount_str)
        amount = Dec(amount_str)
        ext_use = Dec(ext_use_str)
        vp_use  = Dec(vp_use_str)


        # fetch the user instance here (don’t pass ORM objects across threads)
        User = get_user_model()
        user = User.objects.get(pk=user_id)

        log.info("bg start order_id=%s service=%s", order_id, service)

        from accounts.dispatcher import dispatch_service_payment
        from django.utils.timezone import now
        from payments.models import RechargePaymentSummary

        result = dispatch_service_payment(
            service=service,
            user=user,                # adjust dispatcher to accept user_id (or fetch user here)
            metadata=metadata,
            order_id=order_id,
            client_txn_id=client_txn_id,
            amount_rupees=str(amount),
            amount_paise=amt_paise,
            use_ext=str(ext_use),
            use_int=str(vp_use),
            razorpay_payment_id=rzp_payment_id,
            gateway_ref=rzp_payment_id or "WALLET_ONLY",
        )

        RechargePaymentSummary.objects.filter(order_id=order_id).update(
            recharge_status=(result.get("status") or "Pending").title(),
            status_message=result.get("message") or "",
            gateway_reference=(result.get("provider_ref")
                               or result.get("provider_order_id")
                               or rzp_payment_id
                               or client_txn_id),
            updated_at=now(),
        )

        log.info("bg done order_id=%s status=%s", order_id, result.get("status"))
        log.info("2. %s", result.get("status"))
        log.info("2. %s", str(result.get("status") or "Pending").title())
    except Exception as e:
        from django.utils.timezone import now
        from payments.models import RechargePaymentSummary
        from recharge.models import RechargeTransaction
        from recharge.services.provider_utils import get_source_value
        from recharge.services.recharge_flow import handle_recharge_outcome
        from payments.views import process_recharge_refund
        from decimal import Decimal

        err_msg = str(e)


        # 1) Mark summary Failed (idempotent)
        summary = RechargePaymentSummary.objects.filter(order_id=order_id).first()
        if summary:
            summary.recharge_status = "Failure"
            summary.status_message = err_msg
            summary.updated_at = now()
            summary.save(update_fields=["recharge_status", "status_message", "updated_at"])
        else:
            RechargePaymentSummary.objects.filter(order_id=order_id).update(
                recharge_status="Failure", status_message=err_msg, updated_at=now()
            )

        # 2) Try to load the transaction row (may or may not exist yet)
        tx = RechargeTransaction.objects.filter(order_id=order_id).first()

        # 3) If we have a tx, call the standard failure handler (does refund + notify)
        if tx:
            vendor_code = get_source_value()  # e.g., "Goter" | "A1Topup"
            # Use amounts/refs we already have; fall back to tx if needed
            amt_dec = Decimal(amount_str) if 'amount_str' in locals() else tx.amount
            gateway_ref = rzp_payment_id or getattr(tx, "razorpay_order_id", None) or "WALLET-ONLY"
            paid_via_gateway = getattr(tx, "paid_via_gateway", Decimal("0.00"))
            service_val = service  # what you passed into the worker

            handle_recharge_outcome(
                user=user,                     # resolved earlier in the worker
                transaction=tx,
                order_id=order_id,
                status_title="failure",
                status_message=err_msg,
                amount=amt_dec,
                operator=(tx.operator.code if tx.operator_id else ""),  # safe-ish
                vendor_code=vendor_code,
                gateway_ref=gateway_ref,
                paid_via_gateway=paid_via_gateway,
                service=service_val,
            )
        else:
            # 4) No tx row yet: refund directly from the summary & notify a minimal message
            if summary:
                try:
                    process_recharge_refund(summary)   # credits ViralPe wallet only (your policy)
                except Exception:
                    log.exception("refund attempt failed for order_id=%s", order_id)

            # Optional: send a lightweight failure notification if you have a notifier
            try:
                from notifications.services import send_notification
                number = (metadata.get("mobile") if isinstance(metadata, dict) else None) or "N/A"
                send_notification(
                    user=user,
                    type_key="recharge_failed",
                    context={
                        "user": user, "number": number, "amount": str(amount_str),
                        "order_id": order_id, "operator": (metadata.get("operator") if isinstance(metadata, dict) else ""),
                        "created_at": now(), "year": now().year,
                        "payment_id": rzp_payment_id or "WALLET-ONLY",
                        "failure_reason": err_msg, "Service": service,
                    },
                    message=f"Recharge of INR {amount_str} for {number} failed. "
                            f"Amount has been (or will be) credited to your ViralPe wallet. {err_msg}"
                )
            except Exception:
                log.exception("failed to send failure notification for order_id=%s", order_id)

        log.exception("bg failed order_id=%s", order_id)

        log.info("3. %s", "recharge_status=Failed")
        log.info("3. %s", str(e))

    finally:
        close_old_connections()
