# from flask import Blueprint, request, jsonify
# from mongoengine.errors import ValidationError, DoesNotExist
# from datetime import datetime

# from utils.jwt_service import jwt_required
# from models.user import User
# from models.course import Course
# from models.cart_item import CartItem
# from models.sub_exam import SubExam # noqa: F401

# cart_bp = Blueprint("cart_bp", __name__)

# def _get_payload():
#     """Accept JSON or form-data."""
#     if request.is_json:
#         return request.get_json(silent=True) or {}
#     return request.form.to_dict()

# @cart_bp.route("/cart/add", methods=["POST"])
# # @jwt_required
# def add_to_cart():
#     """
#     Body:
#       JSON or form-data:
#         - course_id: <MongoID>
#       user_id is taken from JWT (request.user_id)
#     """
#     data = _get_payload()
#     course_id = data.get("course_id")
#     # user_id = getattr(request, "user_id", None)
#     user_id = data.get("user_id")

#     # We’ll maintain a running count for this user (0 if user missing)
#     cart_count = 0

#     # STEP 1: Validate user exists & active
#     if not user_id:
#         return jsonify({
#             "status": False,
#             "message": "Invalid user id",
#             "cart_items_count": cart_count
#         }), 401

#     try:
#         user = User.objects(id=user_id).only("id", "status", "coins_wallet_balance").first()
#     except (ValidationError, DoesNotExist):
#         user = None

#     if not user:
#         return jsonify({
#             "status": False,
#             "message": "Invalid user id",
#             "cart_items_count": cart_count
#         }), 404

#     # count cart items for this user (before any further step)
#     cart_count = CartItem.objects(user_id=user).count()

#     if int(user.status or 0) != 1:
#         return jsonify({
#             "status": False,
#             "message": "User account not active",
#             "cart_items_count": cart_count
#         }), 403

#     # STEP 2: Validate course exists
#     if not course_id:
#         return jsonify({
#             "status": False,
#             "message": "Invalid course id",
#             "cart_items_count": cart_count
#         }), 400

#     try:
#         course = Course.objects(id=course_id).first()
#     except (ValidationError, DoesNotExist):
#         course = None

#     if not course:
#         return jsonify({
#             "status": False,
#             "message": "Invalid course id",
#             "cart_items_count": cart_count
#         }), 404

#     # STEP 3: Check course is active
#     if int(course.status or 0) != 1:
#         return jsonify({
#             "status": False,
#             "message": "Course is inactive",
#             "cart_items_count": cart_count
#         }), 400

#     # STEP 4: Check wallet balance enough for this purchase
#     user_coins = int(user.coins_wallet_balance or 0)
#     required_coins = int(course.course_fee or 0)
#     if user_coins < required_coins:
#         return jsonify({
#             "status": False,
#             "message": "Insufficient coins",
#             "cart_items_count": cart_count
#         }), 400

#     # STEP 5: Add to cart
#     CartItem(user_id=user, course_id=course, created_date=datetime.utcnow()).save()

#     # Updated count
#     cart_count = CartItem.objects(user_id=user).count()

#     return jsonify({
#         "status": True,
#         "message": "Course added to cart",
#         "cart_items_count": cart_count
#     }), 200



# routes/cart.py
from flask import Blueprint, request, jsonify
from mongoengine.errors import ValidationError, DoesNotExist
from datetime import datetime,timedelta
from models.video_courses_model import VideoCourseSubject
from models.ebooks_model import Ebook
from models.mock_test_model import MockTest
from models.old_papers_model import OldPapers

from utils.jwt_service import jwt_required
from models.user import User
from models.course import Course
from models.cart_item import CartItem
from models.sub_exam import SubExam  # register doc class


cart_bp = Blueprint("cart_bp", __name__)

def _get_payload():
    return request.get_json(silent=True) or request.form.to_dict()

def _active_cart_count(user):
    return CartItem.objects(user_id=user, status=1).count()

@cart_bp.route("/cart/add", methods=["POST"])
@jwt_required
def add_to_cart():
    """
    Add a course to the current user's cart (soft, status=1).
    Body (JSON or form-data):
      - course_id: <MongoID>
    user_id comes from JWT.
    """
    data = _get_payload()
    course_id = data.get("course_id")
    user_id = getattr(request, "user_id", None)
    # user_id = data.get("user_id")

    # STEP 1: validate user
    if not user_id:
        return jsonify({"status": False, "message": "Invalid user id", "cart_items_count": 0}), 401

    try:
        user = User.objects(id=user_id).only("id", "status", "coins_wallet_balance").first()
    except (ValidationError, DoesNotExist):
        user = None
    if not user:
        return jsonify({"status": False, "message": "Invalid user id", "cart_items_count": 0}), 404

    cart_count = _active_cart_count(user)

    if int(user.status or 0) != 1:
        return jsonify({"status": False, "message": "User account not active", "cart_items_count": cart_count}), 403

    # STEP 2: validate course id
    if not course_id:
        return jsonify({"status": False, "message": "Invalid course id", "cart_items_count": cart_count}), 400

    try:
        course = Course.objects(id=course_id).first()
    except (ValidationError, DoesNotExist):
        course = None
    if not course:
        return jsonify({"status": False, "message": "Invalid course id", "cart_items_count": cart_count}), 404

    # STEP 3: course active?
    if int(course.status or 0) != 1:
        return jsonify({"status": False, "message": "Course is inactive", "cart_items_count": cart_count}), 400

    # STEP 4: wallet enough?
    #user_coins = int(user.coins_wallet_balance or 0)
    #required_coins = int(course.course_fee or 0)
    #if user_coins < required_coins:
        #return jsonify({"status": False, "message": "Insufficient coins", "cart_items_count": cart_count}), 400

    # STEP 5: upsert cart item (idempotent add)
    existing = CartItem.objects(user_id=user, course_id=course).first()
    if existing:
        if existing.status == 1:
            # already in cart
            return jsonify({
                "status": True,
                "message": "Course already in cart",
                "cart_items_count": cart_count
            }), 200
        # reactivate previously removed/purchased item
        existing.update(set__status=1, set__created_date=datetime.utcnow())
    else:
        CartItem(user_id=user, course_id=course, status=1, created_date=datetime.utcnow()).save()

    cart_count = _active_cart_count(user)
    return jsonify({
        "status": True,
        "message": "Course added to cart",
        "cart_items_count": cart_count
    }), 200


@cart_bp.route("/cart/items", methods=["POST"])
@jwt_required
def get_cart_items():
    """
    Fetch active cart items for current user, and auto-soft-remove items whose course is inactive/missing.
    Body: none (user_id from JWT)
    """
    user_id = getattr(request, "user_id", None)
    # user_id = request.get_json(silent=True).get("user_id") if request.is_json else request.form.get("user_id")
    if not user_id:
        return jsonify({"status": False, "message": "Invalid user id", "cart_items_count": 0}), 401

    try:
        user = User.objects(id=user_id).only("id", "status").first()
    except (ValidationError, DoesNotExist):
        user = None
    if not user:
        return jsonify({"status": False, "message": "Invalid user id", "cart_items_count": 0}), 404

    if int(user.status or 0) != 1:
        current_count = _active_cart_count(user)
        return jsonify({
            "status": False,
            "message": "User account not active",
            "cart_items_count": current_count
        }), 403

    # Load active items
    items = list(CartItem.objects(user_id=user, status=1).order_by("-created_date"))
    to_soft_remove = []
    payload = []

    for it in items:
        course = it.course_id  # ReferenceField('Course')
        # Soft-remove if missing or inactive
        if (not course) or int(getattr(course, "status", 0)) != 1:
            to_soft_remove.append(it.id)
            continue

        payload.append({
            "cart_item_id": str(it.id),
            "added_at": it.created_date.isoformat() if it.created_date else None,
            "course": {
                "id": str(course.id),
                "course_title": course.course_title,
                "course_fee": int(course.course_fee or 0),
                "course_image": course.course_image,
                "discounted_fee": int(course.discounted_fee or 0),
                "duration": int(course.duration or 0),
                "status": int(course.status or 0),
                "position": int(course.position or 0),
                "exam_id": str(course.exam_id.id) if getattr(course, "exam_id", None) else None,
                "sub_exam_id": str(course.sub_exam_id.id) if getattr(course, "sub_exam_id", None) else None,
                "language": course.language,
                "video_count": VideoCourseSubject.objects(course_id=course.id, status=1).count(),
                "created_date": course.created_date.isoformat() if course.created_date else None,
                "ebook_count": Ebook.objects(course_id=course.id, status=1).count(),
                "mock_test_count": MockTest.objects(course_id=course.id, status=1).count(),
                "old_papers_count": OldPapers.objects(course_id=course.id, status=1).count()
            }
        })

    # Soft-remove invalid entries
    if to_soft_remove:
        CartItem.objects(id__in=to_soft_remove).update(set__status=0)

    return jsonify({
        "status": True,
        "message": "Cart fetched successfully",
        "cart_items_count": len(payload),
        "items": payload
    }), 200


@cart_bp.route("/cart/remove", methods=["POST"])
@jwt_required
def remove_from_cart():
    """
    Remove a course from the current user's cart (soft remove: status -> 0).
    Body (JSON or form-data):
      - course_id: <MongoID>
    user_id is taken from JWT.
    """
    data = _get_payload()
    course_id = data.get("course_id")
    user_id = getattr(request, "user_id", None)
    # user_id = data.get("user_id")

    # Validate user
    if not user_id:
        return jsonify({"status": False, "message": "Invalid user id", "cart_items_count": 0}), 401

    try:
        user = User.objects(id=user_id).only("id", "status").first()
    except (ValidationError, DoesNotExist):
        user = None
    if not user:
        return jsonify({"status": False, "message": "Invalid user id", "cart_items_count": 0}), 404

    # Current active count before action
    current_count = _active_cart_count(user)

    if int(user.status or 0) != 1:
        return jsonify({
            "status": False,
            "message": "User account not active",
            "cart_items_count": current_count
        }), 403

    # Validate course_id presence
    if not course_id:
        return jsonify({
            "status": False,
            "message": "Invalid course id",
            "cart_items_count": current_count
        }), 400

    # Soft-remove (set status=0) any active cart items matching this course
    try:
        # You can filter ReferenceField by id string in MongoEngine
        updated = CartItem.objects(user_id=user, course_id=course_id, status=1).update(set__status=0)
    except ValidationError:
        # invalid ObjectId, treat as not found
        updated = 0

    # Recompute active count after attempted removal
    new_count = _active_cart_count(user)

    if updated == 0:
        return jsonify({
            "status": False,
            "message": "Course not in cart",
            "cart_items_count": new_count
        }), 404

    return jsonify({
        "status": True,
        "message": "Course removed from cart",
        "cart_items_count": new_count
    }), 200



from flask import request, jsonify
from datetime import datetime,timedelta
from pymongo import ReturnDocument
from mongoengine.errors import ValidationError, DoesNotExist

from utils.jwt_service import jwt_required
from models.user import User
from models.course import Course
from models.cart_item import CartItem
from models.transaction_history import TransactionHistory
from models.user_course_purchase_history import UserCoursePurchase
from models.sub_exam import SubExam  # ensure doc is registered

# @cart_bp.route("/cart/checkout-all", methods=["POST"])
# @jwt_required
# def checkout_all_cart():
#     """
#     Bulk purchase all eligible (active) cart items for current user.
#     Body: none (user_id from JWT)
#     Steps:
#       - Clean cart: remove (soft) items whose course is missing/inactive
#       - Skip already purchased courses
#       - Sum total coins required
#       - Atomic wallet decrement (all-or-nothing)
#       - Create purchase records + transaction logs
#       - Mark purchased cart items as status=2
#     """
#     user_id = getattr(request, "user_id", None)
#     # user_id = request.get_json(silent=True).get("user_id") if request.is_json else request.form.get("user_id")
#     if not user_id:
#         return jsonify({"status": False, "message": "Invalid user id"}), 401

#     # Load user
#     try:
#         user = User.objects(id=user_id).only("id", "status", "coins_wallet_balance").first()
#     except (ValidationError, DoesNotExist):
#         user = None
#     if not user:
#         return jsonify({"status": False, "message": "Invalid user id"}), 404
#     if int(user.status or 0) != 1:
#         return jsonify({"status": False, "message": "User account not active"}), 403

#     # Load active cart items
#     cart_items = list(CartItem.objects(user_id=user, status=1).order_by("created_date"))

#     eligible = []            # (cart_item, course) pairs to purchase
#     removed_inactive = []    # cart_item ids soft-removed (course missing/inactive)
#     already_purchased = []   # cart_item ids skipped because already purchased

#     for it in cart_items:
#         course = it.course_id
#         if (not course) or int(getattr(course, "status", 0)) != 1:
#             removed_inactive.append(it.id)
#             continue

#         # skip courses already purchased
#         if UserCoursePurchase.objects(user_id=user, course_id=course, status=1).first():
#             already_purchased.append(it.id)
#             continue

#         eligible.append((it, course))

#     # Soft-remove invalid/missing courses
#     if removed_inactive:
#         CartItem.objects(id__in=removed_inactive).update(set__status=0)
#     # Mark already purchased items as purchased in cart
#     if already_purchased:
#         CartItem.objects(id__in=already_purchased).update(set__status=2)

#     if not eligible:
#         return jsonify({
#             "status": False,
#             "message": "No eligible items to purchase",
#             "removed_inactive": len(removed_inactive),
#             "already_purchased": len(already_purchased),
#             "wallet_balance": int(user.coins_wallet_balance or 0)
#         }), 400

#     # Total fee for eligible items
#     # deterministically order by cart created_date to compute rolling balances
#     eligible.sort(key=lambda pair: pair[0].created_date or datetime.utcnow())
#     total_fee = sum(int(pair[1].course_fee or 0) for pair in eligible if int(pair[1].course_fee or 0) > 0)

#     # Atomic wallet decrement (all-or-nothing)
#     balance_after = int(user.coins_wallet_balance or 0)
#     balance_before = balance_after
#     if total_fee > 0:
#         coll = User._get_collection()
#         after_doc = coll.find_one_and_update(
#             {"_id": user.id, "coins_wallet_balance": {"$gte": total_fee}},
#             {"$inc": {"coins_wallet_balance": -total_fee}},
#             return_document=ReturnDocument.AFTER,
#         )
#         if not after_doc:
#             return jsonify({
#                 "status": False,
#                 "message": "Insufficient coins",
#                 "required": total_fee,
#                 "wallet_balance": int(user.coins_wallet_balance or 0),
#                 "removed_inactive": len(removed_inactive),
#                 "already_purchased": len(already_purchased),
#                 "eligible_count": len(eligible)
#             }), 400
#         balance_after = int(after_doc.get("coins_wallet_balance") or 0)
#         balance_before = balance_after + total_fee

#     # Create purchases + transactions; update cart status→2
#     purchased_items = []
#     rolling_before = balance_before

#     for it, course in eligible:
#         fee = int(course.course_fee or 0)

#         # Purchase record
#         purchase = UserCoursePurchase(
#             user_id=user,
#             course_id=course,
#             purchase_coin_amount=fee,
#             purchase_date=datetime.utcnow(),
#             status=1
#         ).save()

#         # Transaction per course (only if >0)
#         if fee > 0:
#             tx = TransactionHistory(
#                 parent_id=None,
#                 user_id=user,
#                 transaction_type="course_purchase",
#                 entry_type="debit",
#                 amount=fee,
#                 description=f"Course purchase: {course.course_title}",
#                 related_user_id=None,
#                 balance_before=rolling_before,
#                 balance_after=rolling_before - fee,
#                 transaction_status=1
#             ).save()
#             rolling_before -= fee
#             tx_id = str(tx.id)
#         else:
#             tx_id = None

#         # Mark cart item as purchased
#         it.update(set__status=2)

#         purchased_items.append({
#             "course_id": str(course.id),
#             "title": course.course_title,
#             "amount": fee,
#             "purchase_id": str(purchase.id),
#             "transaction_id": tx_id
#         })

#     return jsonify({
#         "status": True,
#         "message": "Checkout completed",
#         "purchased_count": len(purchased_items),
#         "total_spent": total_fee,
#         "wallet_balance": balance_after,
#         "removed_inactive": len(removed_inactive),
#         "already_purchased": len(already_purchased),
#         "purchased_items": purchased_items
#     }), 200

# @cart_bp.route("/cart/checkout-all", methods=["POST"])
# @jwt_required
# def checkout_all_cart():
#     """
#     Bulk purchase all eligible (active) cart items for current user.
#     Uses discounted_fee when > 0 else course_fee.
#     Zero-fee courses are allowed (no TransactionHistory entry).
#     """
#     user_id = getattr(request, "user_id", None)
#     if not user_id:
#         return jsonify({"status": False, "message": "Invalid user id"}), 401

#     # Load user
#     try:
#         user = User.objects(id=user_id).only("id", "status", "coins_wallet_balance").first()
#     except (ValidationError, DoesNotExist):
#         user = None
#     if not user:
#         return jsonify({"status": False, "message": "Invalid user id"}), 404
#     if int(user.status or 0) != 1:
#         return jsonify({"status": False, "message": "User account not active"}), 403

#     # Load active cart items
#     cart_items = list(CartItem.objects(user_id=user, status=1).order_by("created_date"))

#     eligible = []            # (cart_item, course) pairs to purchase
#     removed_inactive = []    # cart_item ids soft-removed (course missing/inactive)
#     already_purchased = []   # cart_item ids skipped because already purchased

#     for it in cart_items:
#         course = it.course_id
#         if (not course) or int(getattr(course, "status", 0)) != 1:
#             removed_inactive.append(it.id)
#             continue

#         # skip courses already purchased
#         if UserCoursePurchase.objects(user_id=user, course_id=course, status=1).first():
#             already_purchased.append(it.id)
#             continue

#         eligible.append((it, course))

#     # Soft-remove invalid/missing courses
#     if removed_inactive:
#         CartItem.objects(id__in=removed_inactive).update(set__status=0)
#     # Mark already purchased items as purchased in cart
#     if already_purchased:
#         CartItem.objects(id__in=already_purchased).update(set__status=2)

#     if not eligible:
#         return jsonify({
#             "status": False,
#             "message": "No eligible items to purchase",
#             "removed_inactive": len(removed_inactive),
#             "already_purchased": len(already_purchased),
#             "wallet_balance": int(user.coins_wallet_balance or 0)
#         }), 400

#     # deterministically order by cart created_date to compute rolling balances
#     eligible.sort(key=lambda pair: pair[0].created_date or datetime.utcnow())

#     # Build effective fee list (discounted_fee if > 0 else course_fee)
#     enriched = []
#     for it, course in eligible:
#         discounted = int(getattr(course, "discounted_fee", 0) or 0)
#         regular = int(getattr(course, "course_fee", 0) or 0)
#         effective_fee = discounted if discounted > 0 else regular
#         enriched.append((it, course, effective_fee))

#     # Total fee for eligible items (sum of positive effective fees)
#     total_fee = sum(fee for _, _, fee in enriched if fee > 0)

#     # Atomic wallet decrement (all-or-nothing)
#     balance_after = int(user.coins_wallet_balance or 0)
#     balance_before = balance_after
#     if total_fee > 0:
#         coll = User._get_collection()
#         after_doc = coll.find_one_and_update(
#             {"_id": user.id, "coins_wallet_balance": {"$gte": total_fee}},
#             {"$inc": {"coins_wallet_balance": -total_fee}},
#             return_document=ReturnDocument.AFTER,
#         )
#         if not after_doc:
#             return jsonify({
#                 "status": False,
#                 "message": "Insufficient coins",
#                 "required": total_fee,
#                 "wallet_balance": int(user.coins_wallet_balance or 0),
#                 "removed_inactive": len(removed_inactive),
#                 "already_purchased": len(already_purchased),
#                 "eligible_count": len(enriched)
#             }), 400
#         balance_after = int(after_doc.get("coins_wallet_balance") or 0)
#         balance_before = balance_after + total_fee

#     # Create purchases + transactions; update cart status→2
#     purchased_items = []
#     rolling_before = balance_before

#     for it, course, fee in enriched:
#         # Purchase record (store the effective fee actually charged)
#         purchase = UserCoursePurchase(
#             user_id=user,
#             course_id=course,
#             purchase_coin_amount=int(fee or 0),
#             purchase_date=datetime.utcnow(),
#             status=1
#         ).save()

#         # Transaction per course (only if effective fee > 0)
#         if fee > 0:
#             tx = TransactionHistory(
#                 parent_id=None,
#                 user_id=user,
#                 transaction_type="course_purchase",
#                 entry_type="debit",
#                 amount=int(fee),
#                 description=f"Course purchase: {course.course_title}",
#                 related_user_id=None,
#                 balance_before=rolling_before,
#                 balance_after=rolling_before - int(fee),
#                 transaction_status=1
#             ).save()
#             rolling_before -= int(fee)
#             tx_id = str(tx.id)
#         else:
#             tx_id = None  # zero-fee purchase: no TransactionHistory

#         # Mark cart item as purchased
#         it.update(set__status=2)

#         purchased_items.append({
#             "course_id": str(course.id),
#             "title": course.course_title,
#             "amount": int(fee or 0),
#             "purchase_id": str(purchase.id),
#             "transaction_id": tx_id
#         })

#     return jsonify({
#         "status": True,
#         "message": "Checkout completed",
#         "purchased_count": len(purchased_items),
#         "total_spent": total_fee,
#         "wallet_balance": balance_after,
#         "removed_inactive": len(removed_inactive),
#         "already_purchased": len(already_purchased),
#         "purchased_items": purchased_items
#     }), 200


@cart_bp.route("/cart/checkout-all", methods=["POST"])
@jwt_required
def checkout_all_cart():
    """
    Bulk purchase ONLY the specified cart items for current user.
    Body (JSON or form-data):
      - course_ids: [ "<course_id>", "<course_id>", ... ]  (REQUIRED)
    Uses discounted_fee when > 0 else course_fee.
    Zero-fee courses are allowed (no TransactionHistory entry).
    """
    payload = request.get_json(silent=True) or request.form.to_dict(flat=False)
    # normalize course_ids from JSON or form-data (form-data may send multiple course_ids)
    course_ids = None
    if isinstance(payload, dict):
        if "course_ids" in payload:
            course_ids = payload.get("course_ids")
        elif "course_ids[]" in payload:  # common form-data pattern
            course_ids = payload.get("course_ids[]")

    # Ensure course_ids is a non-empty list
    if not course_ids or not isinstance(course_ids, (list, tuple)) or len(course_ids) == 0:
        return jsonify({"status": False, "message": "course_ids is required and must be a non-empty array"}), 400

    # user from JWT
    user_id = getattr(request, "user_id", None)
    # user_id=payload.get("user_id")
    if not user_id:
        return jsonify({"status": False, "message": "Invalid user id"}), 401

    # Load user
    try:
        user = User.objects(id=user_id).only("id", "status", "coins_wallet_balance").first()
    except (ValidationError, DoesNotExist):
        user = None
    if not user:
        return jsonify({"status": False, "message": "Invalid user id"}), 404
    if int(user.status or 0) != 1:
        return jsonify({"status": False, "message": "User account not active"}), 403

    # Load only the selected items from the user's active cart
    # First, fetch Course docs for provided IDs (skip invalid ObjectIds silently)
    try:
        selected_courses = list(Course.objects(id__in=course_ids))
    except ValidationError:
        selected_courses = []
    selected_course_ids = {str(c.id) for c in selected_courses}

    if not selected_course_ids:
        return jsonify({
            "status": False,
            "message": "No valid course ids found",
            "not_in_cart": len(course_ids),
            "wallet_balance": int(user.coins_wallet_balance or 0)
        }), 400

    # Fetch cart items for those selected courses only (status=1)
    cart_items = list(
        CartItem.objects(user_id=user, status=1, course_id__in=selected_courses).order_by("created_date")
    )

    # Build maps for quick checks
    in_cart_course_ids = {str(it.course_id.id) for it in cart_items if it.course_id}
    not_in_cart = [cid for cid in course_ids if cid not in in_cart_course_ids]

    eligible = []            # (cart_item, course) pairs to purchase
    removed_inactive = []    # cart_item ids soft-removed (course missing/inactive)
    already_purchased = []   # cart_item ids skipped because already purchased

    # Filter only those sent AND still in cart
    for it in cart_items:
        course = it.course_id
        # If course disappeared or inactive → soft remove
        if (not course) or int(getattr(course, "status", 0)) != 1:
            removed_inactive.append(it.id)
            continue

        # Skip if already purchased
        if UserCoursePurchase.objects(user_id=user, course_id=course, status=1).first():
            already_purchased.append(it.id)
            continue

        eligible.append((it, course))

    # Soft-remove invalid/missing courses among the selected
    if removed_inactive:
        CartItem.objects(id__in=removed_inactive).update(set__status=0)
    # Mark already purchased items as purchased in cart
    if already_purchased:
        CartItem.objects(id__in=already_purchased).update(set__status=2)

    if not eligible:
        return jsonify({
            "status": False,
            "message": "No eligible items to purchase for the provided course_ids",
            "selected_count": len(course_ids),
            "not_in_cart": len(not_in_cart),
            "removed_inactive": len(removed_inactive),
            "already_purchased": len(already_purchased),
            "wallet_balance": int(user.coins_wallet_balance or 0)
        }), 400

    # Deterministic order by cart created_date to compute rolling balances
    eligible.sort(key=lambda pair: pair[0].created_date or datetime.utcnow())

    # Build effective fee list (discounted_fee if > 0 else course_fee)
    enriched = []
    for it, course in eligible:
        discounted = int(getattr(course, "discounted_fee", 0) or 0)
        regular = int(getattr(course, "course_fee", 0) or 0)
        effective_fee = discounted if discounted > 0 else regular
        enriched.append((it, course, effective_fee))

    # Total fee (sum of positive effective fees only)
    total_fee = sum(fee for _, _, fee in enriched if fee > 0)

    # Atomic wallet decrement (all-or-nothing)
    balance_after = int(user.coins_wallet_balance or 0)
    balance_before = balance_after
    if total_fee > 0:
        coll = User._get_collection()
        after_doc = coll.find_one_and_update(
            {"_id": user.id, "coins_wallet_balance": {"$gte": total_fee}},
            {"$inc": {"coins_wallet_balance": -total_fee}},
            return_document=ReturnDocument.AFTER,
        )
        if not after_doc:
            return jsonify({
                "status": False,
                "message": "Insufficient coins",
                "required": total_fee,
                "wallet_balance": int(user.coins_wallet_balance or 0),
                "selected_count": len(course_ids),
                "eligible_count": len(enriched),
                "not_in_cart": len(not_in_cart),
                "removed_inactive": len(removed_inactive),
                "already_purchased": len(already_purchased),
            }), 400
        balance_after = int(after_doc.get("coins_wallet_balance") or 0)
        balance_before = balance_after + total_fee

    # Create purchases + transactions; update cart status→2
    purchased_items = []
    rolling_before = balance_before

    for it, course, fee in enriched:
        purchase = UserCoursePurchase(
            user_id=user,
            course_id=course,
            purchase_coin_amount=int(fee or 0),
            purchase_date=datetime.utcnow(),
            status=1
        ).save()

        # Transaction per course (only if effective fee > 0)
        if fee > 0:
            tx = TransactionHistory(
                parent_id=None,
                user_id=user,
                transaction_type="course_purchase",
                entry_type="debit",
                amount=int(fee),
                description=f"Course purchase: {course.course_title}",
                related_user_id=None,
                balance_before=rolling_before,
                balance_after=rolling_before - int(fee),
                transaction_status=1
            ).save()
            rolling_before -= int(fee)
            tx_id = str(tx.id)
        else:
            tx_id = None  # zero-fee purchase: no TransactionHistory

        # Mark cart item as purchased
        it.update(set__status=2)

        purchased_items.append({
            "course_id": str(course.id),
            "title": course.course_title,
            "amount": int(fee or 0),
            "purchase_id": str(purchase.id),
            "transaction_id": tx_id
        })

    return jsonify({
        "status": True,
        "message": "Checkout completed",
        "selected_count": len(course_ids),
        "eligible_count": len(enriched),
        "not_in_cart": len(not_in_cart),
        "removed_inactive": len(removed_inactive),
        "already_purchased": len(already_purchased),
        "purchased_count": len(purchased_items),
        "total_spent": total_fee,
        "wallet_balance": balance_after,
        "purchased_items": purchased_items
    }), 200

@cart_bp.route("/courses/purchased", methods=["POST"])
@jwt_required
def purchased_courses():
    """
    Fetch purchased courses for the current user.
    Steps:
      1) Validate user exists and is active
      2) Fetch purchase list (status=1), compute course_expiring using Course.duration
         - "Expires on <D Mon YYYY>" if not expired
         - "Expired on <D Mon YYYY>" if expired
      3) Update last_opened_date for the returned purchases to now
    """
    user_id = getattr(request, "user_id", None)
    # user_id = request.get_json(silent=True).get("user_id") if request.is_json else request.form.get("user_id")
    if not user_id:
        return jsonify({"status": False, "message": "Invalid user id", "purchased_courses": []}), 401

    try:
        user = User.objects(id=user_id).only("id", "status").first()
    except (ValidationError, DoesNotExist):
        user = None
    if not user:
        return jsonify({"status": False, "message": "Invalid user id", "purchased_courses": []}), 404

    if int(user.status or 0) != 1:
        return jsonify({"status": False, "message": "User account not active", "purchased_courses": []}), 403

    # Fetch successful purchases
    purchases = list(
        UserCoursePurchase.objects(user_id=user, status=1).order_by("-purchase_date")
    )

    if not purchases:
        return jsonify({
            "status": False,
            "message": "No courses purchased yet",
            "purchased_courses": []
        }), 200

    now = datetime.utcnow()
    payload = []
    touched_ids = []

    for p in purchases:
        course = p.course_id
        if not course:
            # If course is missing, skip this entry silently
            continue

        # duration in days
        duration_days = int(getattr(course, "duration", 0) or 0)
        pdate = p.purchase_date or now

        # compute expiry
        if duration_days > 0:
            expire_date = pdate + timedelta(days=duration_days)
            expired = now > expire_date
            # Format like "1 Oct 2025" without OS-specific %-d
            exp_str = f"{expire_date.day} {expire_date.strftime('%b %Y')}"
            course_expiring = f"{'Expired' if expired else 'Expires'} on {exp_str}"
        else:
            # If duration is 0, treat as no-expiry (you can adjust if you want forced text)
            course_expiring = "Expires on N/A"

        payload.append({
            "course_id": str(course.id),
            "exam_id": str(course.exam_id.id) if getattr(course, "exam_id", None) else None,
            "sub_exam_id": str(course.sub_exam_id.id) if getattr(course, "sub_exam_id", None) else None,
            "course_title": course.course_title,
            "course_image": course.course_image,
            "course_fee": int(course.course_fee or 0),
            "duration": duration_days,
            "purchase_date": pdate.isoformat(),
            "status": int(p.status or 0),
            "course_expiring": course_expiring,
            "last_opened_date": now.isoformat()              # will be updated below
        })
        touched_ids.append(p.id)

    if not payload:
        return jsonify({
            "status": False,
            "message": "No courses purchased yet",
            "purchased_courses": []
        }), 200

    # Bulk update last_opened_date for returned items
    try:
        if touched_ids:
            UserCoursePurchase.objects(id__in=touched_ids).update(set__last_opened_date=now)
    except Exception:
        # non-fatal
        pass

    return jsonify({
        "status": True,
        "message": "Purchased courses fetched successfully",
        "purchased_courses": payload
    }), 200