from decimal import Decimal
import uuid

from flask import (
    Blueprint,
    render_template,
    request,
    redirect,
    url_for,
    flash,
    session
)

from extensions import db

from models import (
    Cart,
    CartItem,
    ProductVariant
)


cart_bp = Blueprint("cart", __name__)


# ============================================================
# CART CONSTANTS
# ============================================================

# Prevent deliberately huge quantities from being submitted.
# Stock validation below remains the final authority.
MAX_CART_QUANTITY = 10000


# ============================================================
# VIEW CART
# ============================================================

@cart_bp.route("/cart")
def cart():

    cart_session_id = session.get(
        "cart_session_id"
    )

    if not cart_session_id:
        return render_template(
            "cart.html",
            cart=None,
            cart_items=[],
            subtotal=Decimal("0.00")
        )

    cart = Cart.query.filter_by(
        session_id=cart_session_id
    ).first()

    if not cart:
        return render_template(
            "cart.html",
            cart=None,
            cart_items=[],
            subtotal=Decimal("0.00")
        )

    cart_items = CartItem.query.filter_by(
        cart_id=cart.id
    ).all()

    subtotal = Decimal("0.00")

    for item in cart_items:

        variant = db.session.get(
            ProductVariant,
            item.variant_id
        )

        # Ignore invalid cart items.
        if not variant:
            continue

        # Ignore invalid quantities.
        if item.quantity <= 0:
            continue

        subtotal += (
            variant.price * item.quantity
        )

    return render_template(
        "cart.html",
        cart=cart,
        cart_items=cart_items,
        subtotal=subtotal
    )


# ============================================================
# ADD TO CART
# ============================================================

@cart_bp.route(
    "/cart/add",
    methods=["POST"]
)
def add_to_cart():

    variant_id = request.form.get(
        "variant_id"
    )

    quantity = request.form.get(
        "quantity",
        "1"
    )

    if not variant_id:
        flash(
            "Please select a product size.",
            "error"
        )

        return redirect(
            url_for("product.products")
        )

    try:

        variant_id = int(variant_id)
        quantity = int(quantity)

    except (ValueError, TypeError):

        flash(
            "Invalid product selection.",
            "error"
        )

        return redirect(
            url_for("product.products")
        )

    # --------------------------------------------------------
    # SERVER-SIDE QUANTITY VALIDATION
    # --------------------------------------------------------

    if quantity < 1:

        flash(
            "Quantity must be at least 1.",
            "error"
        )

        return redirect(
            url_for("product.products")
        )

    if quantity > MAX_CART_QUANTITY:

        flash(
            "The requested quantity is too large.",
            "error"
        )

        return redirect(
            url_for("product.products")
        )

    # --------------------------------------------------------
    # FIND VARIANT
    # --------------------------------------------------------

    variant = db.session.get(
        ProductVariant,
        variant_id
    )

    if not variant:

        flash(
            "Product size not found.",
            "error"
        )

        return redirect(
            url_for("product.products")
        )

    product = variant.product

    if not product:

        flash(
            "The selected product could not be found.",
            "error"
        )

        return redirect(
            url_for("product.products")
        )

    # --------------------------------------------------------
    # PRODUCT AVAILABILITY
    # --------------------------------------------------------

    if not product.is_active:

        flash(
            "This product is currently unavailable.",
            "error"
        )

        return redirect(
            url_for("product.products")
        )

    if not variant.is_available:

        flash(
            "This product size is currently unavailable.",
            "error"
        )

        return redirect(
            url_for(
                "product.product_detail",
                slug=product.slug
            )
        )

    if variant.stock_quantity <= 0:

        flash(
            "This product size is currently out of stock.",
            "error"
        )

        return redirect(
            url_for(
                "product.product_detail",
                slug=product.slug
            )
        )

    # --------------------------------------------------------
    # STOCK VALIDATION
    # --------------------------------------------------------

    if quantity > variant.stock_quantity:

        flash(
            f"Only {variant.stock_quantity} item(s) "
            "are currently available.",
            "error"
        )

        return redirect(
            url_for(
                "product.product_detail",
                slug=product.slug
            )
        )

    # ========================================================
    # CREATE CART SESSION
    # ========================================================

    cart_session_id = session.get(
        "cart_session_id"
    )

    if not cart_session_id:

        cart_session_id = uuid.uuid4().hex

        session["cart_session_id"] = (
            cart_session_id
        )

    # ========================================================
    # FIND OR CREATE CART
    # ========================================================

    cart = Cart.query.filter_by(
        session_id=cart_session_id
    ).first()

    if not cart:

        cart = Cart(
            session_id=cart_session_id
        )

        db.session.add(cart)

        db.session.flush()

    # ========================================================
    # FIND EXISTING CART ITEM
    # ========================================================

    cart_item = CartItem.query.filter_by(
        cart_id=cart.id,
        variant_id=variant.id
    ).first()

    if cart_item:

        # Protect against an already-invalid database value.
        existing_quantity = max(
            int(cart_item.quantity),
            0
        )

        new_quantity = (
            existing_quantity + quantity
        )

        # Hard server-side limit.
        if new_quantity > MAX_CART_QUANTITY:

            flash(
                "The requested quantity is too large.",
                "error"
            )

            return redirect(
                url_for(
                    "product.product_detail",
                    slug=product.slug
                )
            )

        # Never allow the cart to exceed current stock.
        if new_quantity > variant.stock_quantity:

            flash(
                f"Only {variant.stock_quantity} item(s) "
                "are currently available.",
                "error"
            )

            return redirect(
                url_for(
                    "product.product_detail",
                    slug=product.slug
                )
            )

        cart_item.quantity = new_quantity

    else:

        cart_item = CartItem(
            cart_id=cart.id,
            variant_id=variant.id,
            quantity=quantity
        )

        db.session.add(cart_item)

    # ========================================================
    # SAVE
    # ========================================================

    try:

        db.session.commit()

        flash(
            "Product added to your cart.",
            "success"
        )

    except Exception:

        db.session.rollback()

        flash(
            "The product could not be added "
            "to your cart.",
            "error"
        )

    return redirect(
        url_for("cart.cart")
    )


# ============================================================
# UPDATE CART
# ============================================================

@cart_bp.route(
    "/cart/update",
    methods=["POST"]
)
def update_cart():

    cart_item_id = request.form.get(
        "cart_item_id"
    )

    quantity = request.form.get(
        "quantity"
    )

    if not cart_item_id or not quantity:

        flash(
            "Invalid cart item.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    try:

        cart_item_id = int(
            cart_item_id
        )

        quantity = int(
            quantity
        )

    except (ValueError, TypeError):

        flash(
            "Invalid cart information.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    # --------------------------------------------------------
    # SERVER-SIDE QUANTITY VALIDATION
    # --------------------------------------------------------

    if quantity < 1:

        flash(
            "Quantity must be at least 1.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    if quantity > MAX_CART_QUANTITY:

        flash(
            "The requested quantity is too large.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    # ========================================================
    # FIND CURRENT CART
    # ========================================================

    cart_session_id = session.get(
        "cart_session_id"
    )

    if not cart_session_id:

        flash(
            "Your cart could not be found.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    cart = Cart.query.filter_by(
        session_id=cart_session_id
    ).first()

    if not cart:

        flash(
            "Your cart could not be found.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    # ========================================================
    # IMPORTANT:
    # CART ITEM MUST BELONG TO THIS CART
    # ========================================================

    cart_item = CartItem.query.filter_by(
        id=cart_item_id,
        cart_id=cart.id
    ).first()

    if not cart_item:

        flash(
            "Cart item not found.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    # ========================================================
    # FIND CURRENT VARIANT
    # ========================================================

    variant = db.session.get(
        ProductVariant,
        cart_item.variant_id
    )

    if not variant:

        flash(
            "The selected product is "
            "no longer available.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    product = variant.product

    if not product:

        flash(
            "The selected product is "
            "no longer available.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    # ========================================================
    # PRODUCT AVAILABILITY
    # ========================================================

    if not product.is_active:

        flash(
            "This product is currently unavailable.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    if not variant.is_available:

        flash(
            "This product size is currently unavailable.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    if variant.stock_quantity <= 0:

        flash(
            "This product is currently out of stock.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    # ========================================================
    # STOCK VALIDATION
    # ========================================================

    if quantity > variant.stock_quantity:

        flash(
            f"Only {variant.stock_quantity} item(s) "
            "are currently available.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    # ========================================================
    # UPDATE
    # ========================================================

    cart_item.quantity = quantity

    try:

        db.session.commit()

        flash(
            "Cart updated successfully.",
            "success"
        )

    except Exception:

        db.session.rollback()

        flash(
            "The cart could not be updated.",
            "error"
        )

    return redirect(
        url_for("cart.cart")
    )


# ============================================================
# REMOVE FROM CART
# ============================================================

@cart_bp.route(
    "/cart/remove",
    methods=["POST"]
)
def remove_from_cart():

    cart_item_id = request.form.get(
        "cart_item_id"
    )

    if not cart_item_id:

        flash(
            "Invalid cart item.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    try:

        cart_item_id = int(
            cart_item_id
        )

    except (ValueError, TypeError):

        flash(
            "Invalid cart item.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    # ========================================================
    # FIND CURRENT CART
    # ========================================================

    cart_session_id = session.get(
        "cart_session_id"
    )

    if not cart_session_id:

        flash(
            "Your cart is already empty.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    cart = Cart.query.filter_by(
        session_id=cart_session_id
    ).first()

    if not cart:

        flash(
            "Your cart is already empty.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    # ========================================================
    # IMPORTANT:
    # CART ITEM MUST BELONG TO THIS CART
    # ========================================================

    cart_item = CartItem.query.filter_by(
        id=cart_item_id,
        cart_id=cart.id
    ).first()

    if not cart_item:

        flash(
            "Cart item not found.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    # ========================================================
    # DELETE
    # ========================================================

    try:

        db.session.delete(
            cart_item
        )

        db.session.commit()

        flash(
            "Item removed from your cart.",
            "success"
        )

    except Exception:

        db.session.rollback()

        flash(
            "The item could not be removed "
            "from your cart.",
            "error"
        )

    return redirect(
        url_for("cart.cart")
    )


# ============================================================
# CLEAR CART
# ============================================================

@cart_bp.route(
    "/cart/clear",
    methods=["POST"]
)
def clear_cart():

    cart_session_id = session.get(
        "cart_session_id"
    )

    if not cart_session_id:

        flash(
            "Your cart is already empty.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    cart = Cart.query.filter_by(
        session_id=cart_session_id
    ).first()

    if not cart:

        flash(
            "Your cart is already empty.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    try:

        CartItem.query.filter_by(
            cart_id=cart.id
        ).delete(
            synchronize_session=False
        )

        db.session.commit()

        flash(
            "Your cart has been cleared.",
            "success"
        )

    except Exception:

        db.session.rollback()

        flash(
            "The cart could not be cleared.",
            "error"
        )

    return redirect(
        url_for("cart.cart")
    )