# payments/api_views.py
from decimal import Decimal, ROUND_HALF_UP
from django.db.models import Sum, Q
from django.utils.timezone import make_aware
from datetime import datetime

from rest_framework.views import APIView
from rest_framework.generics import ListAPIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import TokenAuthentication
from rest_framework.response import Response
from rest_framework import status

from payments.models import (
    ExternalWallet, ViralPeWallet,
    ViralPeWalletUsage, ExternalWalletTransaction,
    RechargePaymentSummary
)
from .api_serializers import RechargePaymentSummarySerializer


def round0(val):
    return Decimal(val or 0).quantize(Decimal('0'), rounding=ROUND_HALF_UP)


class WalletSummaryAPI(APIView):
    """
    GET /api/wallet/
    Optional: ?limit=5  (recent transactions)
    """
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]

    def get(self, request):
        user = request.user
        limit = int(request.query_params.get('limit', 5))

        # Balances
        external_wallet_obj = ExternalWallet.objects.filter(user=user).first()
        viralpe_wallet_obj = ViralPeWallet.objects.filter(user=user).first()

        external_wallet = round0(external_wallet_obj.balance if external_wallet_obj else 0)
        viralpe_wallet = round0(viralpe_wallet_obj.balance if viralpe_wallet_obj else 0)

        # Income/Expense from both wallets
        vp_income = ViralPeWalletUsage.objects.filter(
            user=user, transaction_type="credit"
        ).aggregate(total=Sum('amount_used'))['total'] or Decimal('0')

        vp_expense = ViralPeWalletUsage.objects.filter(
            user=user, transaction_type="debit"
        ).aggregate(total=Sum('amount_used'))['total'] or Decimal('0')

        ext_income = ExternalWalletTransaction.objects.filter(
            user=user, transaction_type="credit"
        ).aggregate(total=Sum('amount'))['total'] or Decimal('0')

        ext_expense = ExternalWalletTransaction.objects.filter(
            user=user, transaction_type="debit"
        ).aggregate(total=Sum('amount'))['total'] or Decimal('0')

        income = round0(vp_income + ext_income)
        expense = round0(vp_expense + ext_expense)

        # Recent recharge payment summaries
        recents = RechargePaymentSummary.objects.filter(user=user).order_by('-updated_at')[:limit]
        recents_data = RechargePaymentSummarySerializer(recents, many=True).data

        return Response({
            "status": "success",
            "balances": {
                "external_wallet": str(external_wallet),
                "viralpe_wallet": str(viralpe_wallet),
            },
            "totals": {
                "income": str(income),
                "expense": str(expense),
            },
            "recent_transactions": recents_data,
        }, status=status.HTTP_200_OK)


# payments/api_views.py (continued)
from rest_framework.pagination import PageNumberPagination

class SmallResultsSetPagination(PageNumberPagination):
    page_size = 20
    page_size_query_param = 'page_size'
    max_page_size = 200

class RechargePaymentSummaryListAPI(ListAPIView):
    """
    GET /api/recharge-payment-summary/
    Query params (all optional):
      - page, page_size
      - status=<SUCCESS|FAILED|PENDING|...>
      - date_from=YYYY-MM-DD
      - date_to=YYYY-MM-DD
      - search=<number or operator contains>
    """
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]
    serializer_class = RechargePaymentSummarySerializer
    pagination_class = SmallResultsSetPagination

    def get_queryset(self):
        user = self.request.user
        qs = RechargePaymentSummary.objects.filter(user=user).order_by('-updated_at')

        status_q = self.request.query_params.get('status')
        if status_q:
            qs = qs.filter(status=status_q)

        date_from = self.request.query_params.get('date_from')
        date_to = self.request.query_params.get('date_to')
        # inclusive date filtering
        if date_from:
            try:
                dt_from = make_aware(datetime.strptime(date_from, "%Y-%m-%d"))
                qs = qs.filter(updated_at__date__gte=dt_from.date())
            except Exception:
                pass
        if date_to:
            try:
                dt_to = make_aware(datetime.strptime(date_to, "%Y-%m-%d"))
                qs = qs.filter(updated_at__date__lte=dt_to.date())
            except Exception:
                pass

        search = self.request.query_params.get('search')
        if search:
            qs = qs.filter(
                Q(number__icontains=search) |
                Q(operator__icontains=search)  # if operator is CharField; adjust if FK
            )

        return qs



# ----------------

# payments/api_views.py
from decimal import Decimal, ROUND_HALF_UP
from django.utils.dateformat import format as dj_format
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 payments.models import (
    ViralPeWalletUsage, ExternalWalletTransaction,
    ViralPeWallet, ExternalWallet
)

def round2(val):
    return (Decimal(val or 0).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP))

class WalletSummaryAPI(APIView):
    """
    GET /api/wallet/summary?limit=10
    Authorization: Token <key>
    """
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]

    def get(self, request):
        user = request.user
        limit = request.query_params.get('limit')
        try:
            limit = max(1, min(int(limit), 100)) if limit is not None else 10
        except ValueError:
            limit = 10

        # Balances
        internal = ViralPeWallet.objects.filter(user=user).only("balance").first()
        external = ExternalWallet.objects.filter(user=user).only("balance").first()
        internal_balance = round2(internal.balance) if internal else Decimal('0.00')
        external_balance = round2(external.balance) if external else Decimal('0.00')

        # Recent transactions
        internal_txns = (
            ViralPeWalletUsage.objects
            .filter(user=user)
            .order_by('-used_on')[:limit]
        )
        external_txns = (
            ExternalWalletTransaction.objects
            .filter(user=user)
            .order_by('-created_at')[:limit]
        )

        def ser_internal(t):
            # Common fields used in your project; safe-get with defaults
            return {
                "id": t.id,
                "type": getattr(t, "transaction_type", ""),  # "credit"/"debit"
                "amount": str(round2(getattr(t, "amount_used", 0))),
                "purpose": getattr(t, "purpose", "") or "",
                "ref": getattr(t, "reference_id", None),
                "at": getattr(t, "used_on", None),  # DRF will render as ISO 8601
            }

        def ser_external(t):
            return {
                "id": t.id,
                "type": getattr(t, "transaction_type", ""),  # "topup"/"usage"/etc.
                "amount": str(round2(getattr(t, "amount", 0))),
                "note": getattr(t, "note", "") or getattr(t, "description", "") or "",
                "ref": getattr(t, "reference_id", None),
                "at": getattr(t, "created_at", None),
                "source": getattr(t, "source", None),       # e.g., "A1Topup" / "Goter"
            }

        data = {
            "balances": {
                "viralpe": str(internal_balance),
                "external": str(external_balance),
                "total": str(round2(internal_balance + external_balance)),
            },
            "wallet_labels": {
                "viralpe_wallet_id": "ViralPe Wallet" if internal else "N/A",
                "external_wallet_id": "1Apportunity Wallet" if external else "N/A",
            },
            "recent": {
                "viralpe": [ser_internal(x) for x in internal_txns],
                "external": [ser_external(x) for x in external_txns],
                "limit": limit,
            },
        }
        return Response(data)


# payments/api_views.py
# payments/api_views.py
from urllib.parse import urlencode
from django.utils import timezone
from django.utils.dateparse import parse_datetime, parse_date
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 payments.utils import get_all_user_transactions,get_all_user_recharge_transactions


def _parse_dt(qs_value):
    """
    Accepts 'YYYY-MM-DD' or full ISO datetime and returns aware datetime (local tz).
    """
    if not qs_value:
        return None
    dt = parse_datetime(qs_value)
    if dt is None:
        d = parse_date(qs_value)
        if d is not None:
            # cover whole day when 'from'/'to' are dates
            # caller will control <= or >=; we'll handle inclusivity below
            # Return naive midnight for date; make aware below
            dt = timezone.datetime(d.year, d.month, d.day)
    if dt is None:
        return None
    if timezone.is_naive(dt):
        dt = timezone.make_aware(dt, timezone.get_current_timezone())
    return dt


def _build_url(request, **overrides):
    params = request.GET.copy()
    for k, v in overrides.items():
        if v is None:
            params.pop(k, None)
        else:
            params[k] = v
    return f"{request.build_absolute_uri(request.path)}?{urlencode(params, doseq=True)}"

def _rupee_amount_in_words(amount_float: float) -> str:
    """
    Very small utility to turn 1234.50 -> 'One Thousand Two Hundred Thirty Four Rupees and Fifty Paise'
    Good enough for receipts. Tweak word map to your style if needed.
    """
    import math

    ones = ["Zero","One","Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten",
            "Eleven","Twelve","Thirteen","Fourteen","Fifteen","Sixteen","Seventeen","Eighteen","Nineteen"]
    tens = ["","Ten","Twenty","Thirty","Forty","Fifty","Sixty","Seventy","Eighty","Ninety"]

    def two_digit(n):
        if n < 20:
            return ones[n]
        t, o = divmod(n, 10)
        return tens[t] + ("" if o == 0 else f" {ones[o]}")

    def three_digit(n):
        h, rem = divmod(n, 100)
        if h and rem:
            return f"{ones[h]} Hundred {two_digit(rem)}"
        if h:
            return f"{ones[h]} Hundred"
        if rem:
            return two_digit(rem)
        return ""

    def chunk_to_words(n, suffix):
        return (three_digit(n) + f" {suffix}") if n else ""

    # Split rupees / paise
    rupees = int(math.floor(amount_float + 1e-9))
    paise = int(round((amount_float - rupees) * 100))

    if rupees == 0 and paise == 0:
        return "Zero Rupees"

    parts = []
    # Indian system: Crore, Lakh, Thousand, Hundred...
    crore, rem = divmod(rupees, 10_000_000)
    lakh, rem  = divmod(rem, 100_000)
    thou, rem  = divmod(rem, 1_000)
    hund, last = divmod(rem, 100)

    if crore: parts.append(chunk_to_words(crore, "Crore"))
    if lakh:  parts.append(chunk_to_words(lakh, "Lakh"))
    if thou:  parts.append(chunk_to_words(thou, "Thousand"))
    if hund:  parts.append(chunk_to_words(hund, "Hundred"))
    if last:  parts.append(two_digit(last))

    words = " ".join([p for p in parts if p]).strip()
    if not words:
        words = "Zero"

    rupee_part = f"{words} Rupees"
    if paise:
        paise_part = two_digit(paise) + " Paise"
        return f"{rupee_part} and {paise_part}"
    return rupee_part

from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.authentication import TokenAuthentication, SessionAuthentication

class TransactionHistoryAPI(APIView):
    """
    GET /api/transactions?type=credit|debit|all&service=recharge&from=2025-08-01&to=2025-08-31&page=1&page_size=20
    """
    # authentication_classes = [SessionAuthentication, TokenAuthentication]
    permission_classes = [IsAuthenticated]
    # permission_classes = [AllowAny]

    
    def get(self, request):
        user = request.user
        # ---- query params ----
        filter_type = (request.query_params.get("type") or "all").lower()
        service = request.query_params.get("service")

        dt_from = _parse_dt(request.query_params.get("from"))
        dt_to = _parse_dt(request.query_params.get("to"))
        # make 'to' inclusive: add 1 second to include boundary if date-only
        if dt_to:
            dt_to_inclusive = dt_to
            # If user passed a date (00:00), include full day by jumping to 23:59:59.999999
            if dt_to.hour == 0 and dt_to.minute == 0 and dt_to.second == 0 and dt_to.microsecond == 0:
                dt_to_inclusive = dt_to.replace(hour=23, minute=59, second=59, microsecond=999999)
        else:
            dt_to_inclusive = None

        try:
            page = max(1, int(request.query_params.get("page", 1)))
        except ValueError:
            page = 1
        try:
            page_size = int(request.query_params.get("page_size", 20))
        except ValueError:
            page_size = 20
        page_size = max(1, min(100, page_size))  # clamp 1..100

        # ---- fetch & normalize ----
        raw_txns = get_all_user_recharge_transactions(user)
        safe_txns = []
        for t in raw_txns:
            t_copy = t.copy()
            t_copy.pop("full_data", None)
            if "timestamp" in t_copy:
                t_copy["date"] = t_copy.pop("timestamp").isoformat()
            safe_txns.append(t_copy)

        # ---- filters ----
        if filter_type in ("credit", "debit"):
            want_credit = (filter_type == "credit")
            safe_txns = [t for t in safe_txns if bool(t.get("is_credit")) == want_credit]

        if service:
            safe_txns = [t for t in safe_txns if t.get("service") == service]

        if dt_from or dt_to_inclusive:
            def _within(t):
                # 'date' is ISO string in safe_txns; parse back for comparison
                tdt = parse_datetime(t["date"])
                if tdt is None:
                    # fallback if it was date-only string (shouldn't happen here, but safe)
                    d = parse_date(t["date"])
                    if d:
                        tdt = timezone.make_aware(timezone.datetime(d.year, d.month, d.day))
                if tdt is None:
                    return False
                if dt_from and tdt < dt_from:
                    return False
                if dt_to_inclusive and tdt > dt_to_inclusive:
                    return False
                return True
            safe_txns = [t for t in safe_txns if _within(t)]

        # ---- pagination ----
        total = len(safe_txns)
        start = (page - 1) * page_size
        end = start + page_size
        results = safe_txns[start:end]

        has_prev = start > 0
        has_next = end < total

        resp = {
            "count": total,
            "page": page,
            "page_size": page_size,
            "has_next": has_next,
            "has_prev": has_prev,
            "next": _build_url(request, page=page + 1) if has_next else None,
            "prev": _build_url(request, page=page - 1) if has_prev else None,
            "results": results,
        }
        return Response(resp)


from django.utils.timezone import localtime
from decimal import Decimal

def _to_float(v):
    if v is None:
        return 0.0
    if isinstance(v, Decimal):
        return float(v)
    try:
        return float(v)
    except Exception:
        return 0.0

from rest_framework import status
from django.shortcuts import get_object_or_404
from payments.models import ExternalWalletTopUp, ExternalWalletUsage, ViralPeWalletUsage
from recharge.models import RechargeTransaction

class TransactionDetailAPI(APIView):
    """
    GET /api/transactions/<tid>   where tid is like 'recharge-123'
    Returns a rich object for your full-screen detail:
    {
      type, status, amount, amount_text, order_id, date, details,
      payment_breakup: {viralpe_balance, online_gateway, external_wallet},
      number, operator_name, circle_name, client_txn_id, provider_order_id, refund_status
    }
    """
    # authentication_classes = [TokenAuthentication]
    # permission_classes = [IsAuthenticated]

    def get(self, request, txn_id: str):
        if not txn_id or "-" not in txn_id:
            return Response({"detail": "Invalid id"}, status=400)

        prefix, raw_id = txn_id.split("-", 1)
        if prefix != "recharge":
            return Response({"detail": "Unsupported transaction type"}, status=400)

        from django.shortcuts import get_object_or_404
        r = get_object_or_404(RechargeTransaction, pk=raw_id)

        amount_f = float(r.amount)
        data = {
            "type": "Recharge",
            "status": r.status,
            "amount": f"{amount_f:.2f}",
            "amount_text": _rupee_amount_in_words(amount_f),  # ← "Ten Rupees" etc.
            "order_id": r.order_id,
            "date": localtime(r.created_at).isoformat(),
            "details": f"Recharge to {r.number}",

            "number": r.number,
            "operator_name": getattr(getattr(r, "operator", None), "name", None),
            "operator_code": getattr(getattr(r, "operator", None), "code", None),
            "circle_name": getattr(getattr(r, "circle", None), "name", None),
            "circle_code": getattr(getattr(r, "circle", None), "code", None),

            "client_txn_id": r.client_txn_id,
            "provider_order_id": r.provider_order_id,
            "refund_status": r.refund_status,
            "status_message": r.status_message,

            # Payment breakup shown under "Payment mode"
            "payment_breakup": {
                "viralpe_balance": _to_float(r.used_viralpe_wallet),
                "online_gateway": _to_float(r.paid_via_gateway),
                "external_wallet": _to_float(r.used_ext_wallet),
            }
        }
        op_code = getattr(getattr(r, "operator", None), "code", None)
        if op_code:
            data["operator_logo_url"] = request.build_absolute_uri(f"/static/img/{op_code}.png")

        return Response(data)

class TransactionDetailAPI_old(APIView):
    """
    GET /api/transaction/<txn_id>/
    """
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]

    def get(self, request, txn_id):
        user = request.user
        txn_type, obj_id = txn_id.split("-", 1)
        context = {}

        try:
            if txn_type == "recharge":
                txn = get_object_or_404(RechargeTransaction, id=obj_id)
                context = {
                    "type": "Recharge",
                    "amount": str(txn.amount),
                    "status": txn.status,
                    "order_id": txn.order_id,
                    "extra": txn.response_data,
                    "date": txn.created_at,
                    "number": txn.number,
                }

            elif txn_type == "topup":
                txn = get_object_or_404(ExternalWalletTopUp, id=obj_id, user=user)
                context = {
                    "type": "Wallet Top-up",
                    "amount": str(txn.amount),
                    "status": "Success",
                    "order_id": f"EXT-{txn.id}",
                    "date": txn.added_on,
                }

            elif txn_type == "extuse":
                txn = get_object_or_404(ExternalWalletUsage, id=obj_id, user=user)
                context = {
                    "type": "Wallet Debit",
                    "amount": str(txn.amount_used),
                    "status": "Used",
                    "order_id": f"EXTU-{txn.id}",
                    "date": txn.used_on,
                    "details": txn.used_for,
                }

            elif txn_type == "vpuse":
                txn = get_object_or_404(ViralPeWalletUsage, id=obj_id, user=user)
                context = {
                    "type": "VP Wallet Debit",
                    "amount": str(txn.amount_used),
                    "status": "Used",
                    "order_id": f"VPU-{txn.id}",
                    "date": txn.used_on,
                    "details": txn.purpose,
                }

            else:
                return Response({"error": "Invalid transaction type"}, status=status.HTTP_404_NOT_FOUND)

            return Response(context)

        except Exception as e:
            return Response({"error": str(e)}, status=status.HTTP_404_NOT_FOUND)


# Wallet topup api start
from decimal import Decimal
from django.db import transaction
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import permissions, status, authentication

from accounts.models import User
from payments.models import ViralPeWallet, ViralPeWalletUsage
from payments.api_serializers import WalletTopupRequestSerializer, WalletTopupResponseSerializer
from payments.utils import generate_wallet_credit_order_id  # your 15-char generator
from notifications.services import send_notification

PURPOSE_MAP = {
    'manual_test': 'Manual Top-Up (Testing)',
    'manual_admin': 'Manual Top-Up (Admin)',
    'oneapp_withdrawal': 'Credit from 1App Withdrawal',
    'other': 'Other',
}

class HasAddWalletUsagePermission(permissions.BasePermission):
    def has_permission(self, request, view):
        return bool(
            request.user and request.user.is_authenticated
            and request.user.has_perm("payments.add_viralpewalletusage")
        )

@method_decorator(csrf_exempt, name="dispatch")
class WalletTopupAPI(APIView):
    authentication_classes = [authentication.TokenAuthentication, authentication.SessionAuthentication]
    permission_classes = [HasAddWalletUsagePermission]

    def post(self, request):
        ser = WalletTopupRequestSerializer(data=request.data)
        ser.is_valid(raise_exception=True)
        data = ser.validated_data

        mobile = data["mobile"].strip()
        amount = data["amount"]  # Decimal
        purpose_code = data["purpose_code"]
        purpose_note = data.get("purpose_note", "").strip()

        if amount <= Decimal("0"):
            return Response({"success": False, "message": "Amount must be greater than zero"}, status=400)

        try:
            user = User.objects.get(mobile_number=mobile)
        except User.DoesNotExist:
            return Response({"success": False, "message": "User not found"}, status=404)

        purpose_str = PURPOSE_MAP.get(purpose_code, "Manual Top-Up (Admin)")
        purpose_full = purpose_str if not purpose_note else f"{purpose_str} | {purpose_note}"

        # Generate a unique 15-char order_id here; retry on rare collision
        order_id = generate_wallet_credit_order_id()
        tries = 0
        while ViralPeWalletUsage.objects.filter(order_id=order_id).exists():
            tries += 1
            if tries > 5:
                return Response({"success": False, "message": "Could not allocate order id"}, status=500)
            order_id = generate_wallet_credit_order_id()

        with transaction.atomic():
            wallet, _ = ViralPeWallet.objects.select_for_update().get_or_create(user=user)
            wallet.balance = (wallet.balance or Decimal("0.00")) + amount
            wallet.save(update_fields=["balance"])

            usage = ViralPeWalletUsage.objects.create(
                user=user,
                amount_used=amount,
                transaction_type="credit",
                purpose=purpose_full,
                purpose_code=purpose_code,
                purpose_note=purpose_note,
                order_id=order_id,
                reference_user=request.user,
            )

        resp = WalletTopupResponseSerializer({
            "success": True,
            "order_id": usage.order_id,
            "mobile": user.mobile_number,
            "amount": amount,
            "balance": wallet.balance,
            "purpose_code": purpose_code,
            "purpose": purpose_full,
            "reference_user": request.user.username,
        }).data
        send_notification(
            user=user,
            type_key="wallet_topup_credit",
            context={
                "user": user,
                "amount": amount,
                "order_id": usage.order_id,
                "balance": wallet.balance,
                "purpose": purpose_full,
            },
            message=f"₹{amount} added to your ViralPe Wallet. Order {usage.order_id}.",
        )


        return Response(resp, status=status.HTTP_201_CREATED)

# Wallet topup api ends
from django.core.cache import cache
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import status
from rest_framework.exceptions import ValidationError
import secrets

from payments.api_serializers import VerifyTxnPinSerializer

ATTEMPT_WINDOW_SECONDS = 300   # 5 minutes
MAX_ATTEMPTS = 5
import logging
log = logging.getLogger(__name__)

class VerifyTransactionPinAPI(APIView):
    """
    POST { "transaction_pin": "1234" }
    200: { "ok": true }
    403: { "ok": false, "error": "wrong_pin" }
    409: { "ok": false, "error": "pin_not_set" }
    400: { "ok": false, "error": "invalid_format" }
    429: { "ok": false, "error": "too_many_attempts", "retry_in": <seconds> }
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # ✅ Replace your manual parsing/format check with the serializer:
        try:
            ser = VerifyTxnPinSerializer(data=request.data)
            log.info(ser)
            ser.is_valid(raise_exception=True)
            pin = ser.validated_data["transaction_pin"]
            log.info(pin)
        except ValidationError:
            return Response({"ok": False, "error": "invalid_format"}, status=status.HTTP_400_BAD_REQUEST)

        user = request.user
        if not user.transaction_pin:
            return Response({"ok": False, "error": "pin_not_set"}, status=status.HTTP_409_CONFLICT)

        # basic per-user throttling on wrong attempts
        key = f"txn_pin_attempts:{user.id}"
        attempts = cache.get(key, 0)
        if attempts >= MAX_ATTEMPTS:
            ttl = cache.ttl(key)
            retry_in = ttl if isinstance(ttl, int) and ttl > 0 else ATTEMPT_WINDOW_SECONDS
            return Response(
                {"ok": False, "error": "too_many_attempts", "retry_in": retry_in},
                status=status.HTTP_429_TOO_MANY_REQUESTS,
            )

        # constant-time compare (use your hashed check here if you switched to hashing)
        if secrets.compare_digest(user.transaction_pin, pin):
            cache.delete(key)
            return Response({"ok": True}, status=status.HTTP_200_OK)

        cache.set(key, attempts + 1, ATTEMPT_WINDOW_SECONDS)
        return Response({"ok": False, "error": "wrong_pin"}, status=status.HTTP_403_FORBIDDEN)
