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,
    Product,
    ProductVariant,
    Order,
    OrderItem,
    StockMovement,
    OrderStatusHistory,
    Delivery,
    DeliveryZone,
    SiteSetting
)


checkout_bp = Blueprint("checkout", __name__)


# ============================================================
# CHECKOUT PAGE
# ============================================================

@checkout_bp.route("/checkout")
def checkout():

    cart_session_id = session.get(
        "cart_session_id"
    )

    if not cart_session_id:
        flash(
            "Your cart is 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 empty.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    cart_items = CartItem.query.filter_by(
        cart_id=cart.id
    ).all()

    if not cart_items:
        flash(
            "Your cart is empty.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    subtotal = Decimal("0.00")

    for item in cart_items:

        variant = db.session.get(
            ProductVariant,
            item.variant_id
        )

        if not variant:
            flash(
                "One of the products in your cart "
                "is no longer available.",
                "error"
            )

            return redirect(
                url_for("cart.cart")
            )

        if not variant.is_available:
            flash(
                f"{variant.product.name} ({variant.mass}) "
                "is no longer available.",
                "error"
            )

            return redirect(
                url_for("cart.cart")
            )

        if not variant.product.is_active:
            flash(
                f"{variant.product.name} "
                "is no longer available.",
                "error"
            )

            return redirect(
                url_for("cart.cart")
            )

        if item.quantity <= 0:
            flash(
                "Invalid quantity in your cart.",
                "error"
            )

            return redirect(
                url_for("cart.cart")
            )

        if item.quantity > variant.stock_quantity:
            flash(
                f"Only {variant.stock_quantity} item(s) of "
                f"{variant.product.name} ({variant.mass}) "
                "are currently available.",
                "error"
            )

            return redirect(
                url_for("cart.cart")
            )

        subtotal += (
            variant.price * item.quantity
        )

    delivery_zones = DeliveryZone.query.filter_by(
        is_active=True
    ).order_by(
        DeliveryZone.name.asc()
    ).all()

    site_setting = SiteSetting.query.first()

    return render_template(
        "checkout.html",
        cart=cart,
        cart_items=cart_items,
        subtotal=subtotal,
        delivery_zones=delivery_zones,
        site_setting=site_setting
    )


# ============================================================
# PLACE ORDER
# ============================================================

@checkout_bp.route(
    "/checkout/place-order",
    methods=["POST"]
)
def place_order():

    cart_session_id = session.get(
        "cart_session_id"
    )

    if not cart_session_id:
        flash(
            "Your cart is 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 empty.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    cart_items = CartItem.query.filter_by(
        cart_id=cart.id
    ).all()

    if not cart_items:
        flash(
            "Your cart is empty.",
            "error"
        )

        return redirect(
            url_for("cart.cart")
        )

    # ========================================================
    # CUSTOMER INFORMATION
    # ========================================================

    first_name = request.form.get(
        "first_name",
        ""
    ).strip()

    last_name = request.form.get(
        "last_name",
        ""
    ).strip()

    phone = request.form.get(
        "phone",
        ""
    ).strip()

    email = request.form.get(
        "email",
        ""
    ).strip()

    # ========================================================
    # DELIVERY INFORMATION
    # ========================================================

    delivery_method = request.form.get(
        "delivery_method",
        ""
    ).strip()

    delivery_zone_id = request.form.get(
        "delivery_zone_id"
    )

    delivery_address = request.form.get(
        "delivery_address",
        ""
    ).strip()

    delivery_notes = request.form.get(
        "delivery_notes",
        ""
    ).strip()

    # ========================================================
    # BASIC VALIDATION
    # ========================================================

    if not first_name:
        flash(
            "First name is required.",
            "error"
        )

        return redirect(
            url_for("checkout.checkout")
        )

    if not last_name:
        flash(
            "Last name is required.",
            "error"
        )

        return redirect(
            url_for("checkout.checkout")
        )

    if not phone:
        flash(
            "Phone number is required.",
            "error"
        )

        return redirect(
            url_for("checkout.checkout")
        )

    if not delivery_method:
        flash(
            "Please select a delivery method.",
            "error"
        )

        return redirect(
            url_for("checkout.checkout")
        )

    # ========================================================
    # DELIVERY METHOD
    # ========================================================

    if delivery_method not in (
        "delivery",
        "pickup"
    ):
        flash(
            "Invalid delivery method.",
            "error"
        )

        return redirect(
            url_for("checkout.checkout")
        )

    delivery_fee = Decimal("0.00")
    delivery_zone = None

    if delivery_method == "delivery":

        if not delivery_zone_id:
            flash(
                "Please select a delivery area.",
                "error"
            )

            return redirect(
                url_for("checkout.checkout")
            )

        try:

            delivery_zone_id = int(
                delivery_zone_id
            )

        except (ValueError, TypeError):

            flash(
                "Invalid delivery area.",
                "error"
            )

            return redirect(
                url_for("checkout.checkout")
            )

        delivery_zone = db.session.get(
            DeliveryZone,
            delivery_zone_id
        )

        if not delivery_zone:
            flash(
                "The selected delivery area does not exist.",
                "error"
            )

            return redirect(
                url_for("checkout.checkout")
            )

        if not delivery_zone.is_active:
            flash(
                "The selected delivery area is no longer available.",
                "error"
            )

            return redirect(
                url_for("checkout.checkout")
            )

        if not delivery_address:
            flash(
                "Delivery address is required.",
                "error"
            )

            return redirect(
                url_for("checkout.checkout")
            )

        delivery_fee = delivery_zone.fee

    else:

        delivery_address = None
        delivery_fee = Decimal("0.00")

    # ========================================================
    # CUSTOMER EMAIL
    # ========================================================

    if email:
        email = email.lower()

    # ========================================================
    # RE-CHECK CART AND LOCK STOCK
    #
    # The product variants are locked while the order is
    # being created. This prevents two customers from buying
    # the same stock simultaneously.
    # ========================================================

    variant_ids = sorted(
        {
            item.variant_id
            for item in cart_items
        }
    )

    locked_variants = (
        ProductVariant.query
        .filter(
            ProductVariant.id.in_(variant_ids)
        )
        .order_by(
            ProductVariant.id.asc()
        )
        .with_for_update()
        .all()
    )

    variants_by_id = {
        variant.id: variant
        for variant in locked_variants
    }

    subtotal = Decimal("0.00")
    validated_items = []

    for item in cart_items:

        variant = variants_by_id.get(
            item.variant_id
        )

        if not variant:

            db.session.rollback()

            flash(
                "One of the products in your cart "
                "is no longer available.",
                "error"
            )

            return redirect(
                url_for("cart.cart")
            )

        product = db.session.get(
            Product,
            variant.product_id
        )

        if not product or not product.is_active:

            db.session.rollback()

            flash(
                f"The selected product is "
                "no longer available.",
                "error"
            )

            return redirect(
                url_for("cart.cart")
            )

        if not variant.is_available:

            db.session.rollback()

            flash(
                f"{product.name} ({variant.mass}) "
                "is no longer available.",
                "error"
            )

            return redirect(
                url_for("cart.cart")
            )

        if item.quantity <= 0:

            db.session.rollback()

            flash(
                "Invalid quantity in your cart.",
                "error"
            )

            return redirect(
                url_for("cart.cart")
            )

        if variant.stock_quantity <= 0:

            db.session.rollback()

            flash(
                f"{product.name} ({variant.mass}) "
                "is currently out of stock.",
                "error"
            )

            return redirect(
                url_for("cart.cart")
            )

        if item.quantity > variant.stock_quantity:

            db.session.rollback()

            flash(
                f"Only {variant.stock_quantity} item(s) of "
                f"{product.name} ({variant.mass}) "
                "are currently available.",
                "error"
            )

            return redirect(
                url_for("cart.cart")
            )

        item_subtotal = (
            variant.price * item.quantity
        )

        subtotal += item_subtotal

        validated_items.append({
            "cart_item": item,
            "variant": variant,
            "product": product,
            "quantity": item.quantity,
            "subtotal": item_subtotal
        })

    # ========================================================
    # TOTAL
    # ========================================================

    total = subtotal + delivery_fee

    # ========================================================
    # CREATE ORDER NUMBER
    # ========================================================

    order_number = (
        "GV-"
        + uuid.uuid4().hex[:10].upper()
    )

    # ========================================================
    # CREATE ORDER
    #
    # This is a guest order.
    #
    # Customer information is stored directly on the order.
    # There is no customer account and no customer_id.
    # ========================================================

    order = Order(
        order_number=order_number,
        customer_name=f"{first_name} {last_name}",
        customer_email=email or None,
        customer_phone=phone,
        delivery_method=delivery_method,
        delivery_address=delivery_address,
        delivery_notes=delivery_notes or None,
        subtotal=subtotal,
        delivery_fee=delivery_fee,
        total=total,
        order_status="pending",
        payment_status="pending"
    )

    db.session.add(
        order
    )

    db.session.flush()

    # ========================================================
    # CREATE ORDER ITEMS + REDUCE STOCK
    # ========================================================

    for item_data in validated_items:

        variant = item_data["variant"]
        product = item_data["product"]
        quantity = item_data["quantity"]
        item_subtotal = item_data["subtotal"]

        order_item = OrderItem(
            order_id=order.id,
            variant_id=variant.id,
            product_name=product.name,
            mass=variant.mass,
            quantity=quantity,
            unit_price=variant.price,
            subtotal=item_subtotal
        )

        db.session.add(
            order_item
        )

        # Reduce stock.

        variant.stock_quantity -= quantity

        # Record stock movement.

        stock_movement = StockMovement(
            variant_id=variant.id,
            quantity=-quantity,
            movement_type="order",
            reference=order.order_number
        )

        db.session.add(
            stock_movement
        )

    # ========================================================
    # CREATE ORDER STATUS HISTORY
    # ========================================================

    status_history = OrderStatusHistory(
        order_id=order.id,
        status="pending",
        changed_by=None,
        note="Guest order placed."
    )

    db.session.add(
        status_history
    )

    # ========================================================
    # CREATE DELIVERY RECORD
    # ========================================================

    delivery = Delivery(
        order_id=order.id,
        delivery_method=delivery_method,
        delivery_status="pending",
        address=delivery_address,
        fee=delivery_fee
    )

    db.session.add(
        delivery
    )

    # ========================================================
    # CLEAR CART IN THE SAME TRANSACTION
    #
    # The order, stock reduction, stock movement, delivery,
    # order history and cart deletion succeed or fail together.
    # ========================================================

    CartItem.query.filter_by(
        cart_id=cart.id
    ).delete(
        synchronize_session=False
    )

    # ========================================================
    # SAVE EVERYTHING
    # ========================================================

    try:

        db.session.commit()

    except Exception:

        db.session.rollback()

        flash(
            "Your order could not be placed. "
            "Please try again.",
            "error"
        )

        return redirect(
            url_for("checkout.checkout")
        )

    # ========================================================
    # SAVE ORDER NUMBER
    #
    # This will be used by the payment system later.
    # ========================================================

    session["pending_order_number"] = (
        order.order_number
    )

    # ========================================================
    # PAYMENT NOT YET INTEGRATED
    #
    # For now, show the order confirmation.
    # Payment integration will replace this later.
    # ========================================================

    return redirect(
        url_for(
            "checkout.order_confirmation",
            order_number=order.order_number
        )
    )


# ============================================================
# ORDER CONFIRMATION
# ============================================================

@checkout_bp.route(
    "/order-confirmation/<order_number>"
)
def order_confirmation(order_number):

    pending_order_number = session.get(
        "pending_order_number"
    )

    if not pending_order_number:
        return redirect(
            url_for("product.products")
        )

    if pending_order_number != order_number:
        return redirect(
            url_for("product.products")
        )

    order = Order.query.filter_by(
        order_number=order_number
    ).first()

    if not order:

        session.pop(
            "pending_order_number",
            None
        )

        return redirect(
            url_for("product.products")
        )

    return render_template(
        "order_confirmation.html",
        order=order,
        site_setting=SiteSetting.query.first()
    )