import os
import re
import uuid
from decimal import Decimal, InvalidOperation

from flask import (
    Blueprint,
    render_template,
    request,
    redirect,
    url_for,
    flash,
    session,
    current_app
)

from extensions import db

from models import (
    Product,
    ProductVariant,
    ProductImage,
    Category,
    StockMovement,
    OrderItem,
    SiteSetting
)


product_bp = Blueprint("product", __name__)


# =========================================================
# EDIT PRODUCT
# =========================================================

@product_bp.route(
    "/admin/products/<int:product_id>/edit",
    methods=["GET", "POST"]
)
def edit_product(product_id):

    if "user_id" not in session:
        return redirect(url_for("auth.login"))

    if session.get("role") not in ["owner", "admin"]:
        flash(
            "You do not have permission to edit products.",
            "error"
        )
        return redirect(
            url_for("dashboard.admin_dashboard")
        )

    product = db.session.get(
        Product,
        product_id
    )

    if not product:
        flash(
            "Product not found.",
            "error"
        )
        return redirect(
            url_for("product.admin_products")
        )

    categories = Category.query.filter_by(
        is_active=True
    ).order_by(
        Category.name.asc()
    ).all()

    if request.method == "GET":

        return render_template(
            "admin/edit_product.html",
            product=product,
            categories=categories
        )

    # =====================================================
    # BASIC PRODUCT INFORMATION
    # =====================================================

    name = request.form.get(
        "name",
        ""
    ).strip()

    description = request.form.get(
        "description",
        ""
    ).strip()

    category_id = request.form.get(
        "category_id",
        ""
    ).strip()

    meta_title = request.form.get(
        "meta_title",
        ""
    ).strip()

    meta_description = request.form.get(
        "meta_description",
        ""
    ).strip()

    is_active = (
        request.form.get("is_active") == "1"
    )

    # =====================================================
    # VALIDATE BASIC INFORMATION
    # =====================================================

    if not name:
        flash(
            "Product name is required.",
            "error"
        )
        return redirect(
            url_for(
                "product.edit_product",
                product_id=product.id
            )
        )

    if not description:
        flash(
            "Product description is required.",
            "error"
        )
        return redirect(
            url_for(
                "product.edit_product",
                product_id=product.id
            )
        )

    if not category_id:
        flash(
            "Please select a category.",
            "error"
        )
        return redirect(
            url_for(
                "product.edit_product",
                product_id=product.id
            )
        )

    try:
        category_id_value = int(category_id)

    except ValueError:
        flash(
            "Invalid category.",
            "error"
        )
        return redirect(
            url_for(
                "product.edit_product",
                product_id=product.id
            )
        )

    category = db.session.get(
        Category,
        category_id_value
    )

    if not category:
        flash(
            "Selected category does not exist.",
            "error"
        )
        return redirect(
            url_for(
                "product.edit_product",
                product_id=product.id
            )
        )

    if not category.is_active:
        flash(
            "The selected category is inactive.",
            "error"
        )
        return redirect(
            url_for(
                "product.edit_product",
                product_id=product.id
            )
        )

    # =====================================================
    # KEEP EXISTING SLUG
    # =====================================================

    if not product.slug:

        slug = re.sub(
            r"[^a-z0-9]+",
            "-",
            name.lower()
        ).strip("-")

        if not slug:
            flash(
                "A valid product URL could not be created.",
                "error"
            )
            return redirect(
                url_for(
                    "product.edit_product",
                    product_id=product.id
                )
            )

        existing_slug = Product.query.filter(
            Product.slug == slug,
            Product.id != product.id
        ).first()

        if existing_slug:
            flash(
                "A product with this URL already exists.",
                "error"
            )
            return redirect(
                url_for(
                    "product.edit_product",
                    product_id=product.id
                )
            )

        product.slug = slug

    # =====================================================
    # UPDATE PRODUCT
    # =====================================================

    product.name = name
    product.description = description
    product.category_id = category.id
    product.meta_title = meta_title or None
    product.meta_description = meta_description or None
    product.is_active = is_active

    # =====================================================
    # EXISTING VARIANTS
    # =====================================================

    variant_ids = request.form.getlist(
        "variant_id[]"
    )

    variant_masses = request.form.getlist(
        "variant_mass[]"
    )

    variant_prices = request.form.getlist(
        "variant_price[]"
    )

    variant_stocks = request.form.getlist(
        "variant_stock[]"
    )

    variant_skus = request.form.getlist(
        "variant_sku[]"
    )

    variant_available = request.form.getlist(
        "variant_available[]"
    )

    if not (
        len(variant_ids)
        == len(variant_masses)
        == len(variant_prices)
        == len(variant_stocks)
        == len(variant_skus)
    ):

        db.session.rollback()

        flash(
            "There was a problem with the existing product sizes.",
            "error"
        )

        return redirect(
            url_for(
                "product.edit_product",
                product_id=product.id
            )
        )

    submitted_variant_ids = set()

    # =====================================================
    # UPDATE EXISTING VARIANTS
    # =====================================================

    for index in range(len(variant_ids)):

        variant_id = variant_ids[index].strip()
        mass = variant_masses[index].strip()
        price = variant_prices[index].strip()
        stock = variant_stocks[index].strip()
        sku = variant_skus[index].strip()

        if not variant_id:
            db.session.rollback()
            flash(
                "A product size is missing its ID.",
                "error"
            )
            return redirect(
                url_for(
                    "product.edit_product",
                    product_id=product.id
                )
            )

        try:
            variant_id_value = int(variant_id)

        except ValueError:
            db.session.rollback()
            flash(
                "Invalid product size.",
                "error"
            )
            return redirect(
                url_for(
                    "product.edit_product",
                    product_id=product.id
                )
            )

        variant = db.session.get(
            ProductVariant,
            variant_id_value
        )

        if not variant:
            db.session.rollback()
            flash(
                "One of the product sizes could not be found.",
                "error"
            )
            return redirect(
                url_for(
                    "product.edit_product",
                    product_id=product.id
                )
            )

        if variant.product_id != product.id:
            db.session.rollback()
            flash(
                "Invalid product size.",
                "error"
            )
            return redirect(
                url_for(
                    "product.edit_product",
                    product_id=product.id
                )
            )

        submitted_variant_ids.add(
            variant.id
        )

        if not mass:
            db.session.rollback()
            flash(
                "Every product size must have a mass.",
                "error"
            )
            return redirect(
                url_for(
                    "product.edit_product",
                    product_id=product.id
                )
            )

        try:
            price_value = Decimal(price)

        except (InvalidOperation, ValueError):
            db.session.rollback()
            flash(
                "Every product size must have a valid price.",
                "error"
            )
            return redirect(
                url_for(
                    "product.edit_product",
                    product_id=product.id
                )
            )

        if price_value < 0:
            db.session.rollback()
            flash(
                "Price cannot be negative.",
                "error"
            )
            return redirect(
                url_for(
                    "product.edit_product",
                    product_id=product.id
                )
            )

        try:
            stock_value = int(stock)

        except ValueError:
            db.session.rollback()
            flash(
                "Stock quantity must be a whole number.",
                "error"
            )
            return redirect(
                url_for(
                    "product.edit_product",
                    product_id=product.id
                )
            )

        if stock_value < 0:
            db.session.rollback()
            flash(
                "Stock quantity cannot be negative.",
                "error"
            )
            return redirect(
                url_for(
                    "product.edit_product",
                    product_id=product.id
                )
            )

        if sku:

            existing_sku = ProductVariant.query.filter(
                ProductVariant.sku == sku,
                ProductVariant.id != variant.id
            ).first()

            if existing_sku:
                db.session.rollback()
                flash(
                    f"SKU '{sku}' is already being used.",
                    "error"
                )
                return redirect(
                    url_for(
                        "product.edit_product",
                        product_id=product.id
                    )
                )

        old_stock = variant.stock_quantity

        stock_difference = (
            stock_value - old_stock
        )

        variant.mass = mass
        variant.price = price_value
        variant.stock_quantity = stock_value
        variant.sku = sku or None

        variant.is_available = (
            str(variant.id) in variant_available
        )

        if stock_difference != 0:

            movement_type = (
                "adjustment_in"
                if stock_difference > 0
                else "adjustment_out"
            )

            stock_movement = StockMovement(
                variant_id=variant.id,
                quantity=stock_difference,
                movement_type=movement_type,
                reference="Product edit"
            )

            db.session.add(
                stock_movement
            )

    # =====================================================
    # REMOVE VARIANTS
    # =====================================================

    existing_variants = ProductVariant.query.filter_by(
        product_id=product.id
    ).all()

    for variant in existing_variants:

        if variant.id not in submitted_variant_ids:

            order_item = OrderItem.query.filter_by(
                variant_id=variant.id
            ).first()

            if order_item:

                variant.is_available = False

            else:

                StockMovement.query.filter_by(
                    variant_id=variant.id
                ).delete(
                    synchronize_session=False
                )

                db.session.delete(
                    variant
                )

    # =====================================================
    # ADD NEW VARIANTS
    # =====================================================

    new_masses = request.form.getlist(
        "new_mass[]"
    )

    new_prices = request.form.getlist(
        "new_price[]"
    )

    new_stocks = request.form.getlist(
        "new_stock[]"
    )

    new_skus = request.form.getlist(
        "new_sku[]"
    )

    if not (
        len(new_masses)
        == len(new_prices)
        == len(new_stocks)
        == len(new_skus)
    ):

        db.session.rollback()

        flash(
            "There was a problem with the new product sizes.",
            "error"
        )

        return redirect(
            url_for(
                "product.edit_product",
                product_id=product.id
            )
        )

    for index in range(len(new_masses)):

        mass = new_masses[index].strip()
        price = new_prices[index].strip()
        stock = new_stocks[index].strip()
        sku = new_skus[index].strip()

        if not mass and not price and not stock and not sku:
            continue

        if not mass:
            db.session.rollback()
            flash(
                "Every new product size must have a mass.",
                "error"
            )
            return redirect(
                url_for(
                    "product.edit_product",
                    product_id=product.id
                )
            )

        try:
            price_value = Decimal(price)
            stock_value = int(stock)

        except (InvalidOperation, ValueError):
            db.session.rollback()
            flash(
                "New product sizes must have a valid price and stock quantity.",
                "error"
            )
            return redirect(
                url_for(
                    "product.edit_product",
                    product_id=product.id
                )
            )

        if price_value < 0:
            db.session.rollback()
            flash(
                "Price cannot be negative.",
                "error"
            )
            return redirect(
                url_for(
                    "product.edit_product",
                    product_id=product.id
                )
            )

        if stock_value < 0:
            db.session.rollback()
            flash(
                "Stock quantity cannot be negative.",
                "error"
            )
            return redirect(
                url_for(
                    "product.edit_product",
                    product_id=product.id
                )
            )

        if sku:

            existing_sku = ProductVariant.query.filter_by(
                sku=sku
            ).first()

            if existing_sku:
                db.session.rollback()
                flash(
                    f"SKU '{sku}' is already being used.",
                    "error"
                )
                return redirect(
                    url_for(
                        "product.edit_product",
                        product_id=product.id
                    )
                )

        new_variant = ProductVariant(
            product_id=product.id,
            mass=mass,
            price=price_value,
            stock_quantity=stock_value,
            sku=sku or None,
            is_available=True
        )

        db.session.add(
            new_variant
        )

        db.session.flush()

        if stock_value > 0:

            stock_movement = StockMovement(
                variant_id=new_variant.id,
                quantity=stock_value,
                movement_type="initial_stock",
                reference="Product edit"
            )

            db.session.add(
                stock_movement
            )

    # =====================================================
    # IMAGE UPLOADS
    # =====================================================

    image_files = request.files.getlist(
        "images"
    )

    valid_images = []

    allowed_extensions = {
        "jpg",
        "jpeg",
        "png",
        "webp"
    }

    for image in image_files:

        if not image:
            continue

        if not image.filename:
            continue

        if "." not in image.filename:

            db.session.rollback()

            flash(
                "Invalid image file.",
                "error"
            )

            return redirect(
                url_for(
                    "product.edit_product",
                    product_id=product.id
                )
            )

        extension = (
            image.filename
            .rsplit(".", 1)[-1]
            .lower()
        )

        if extension not in allowed_extensions:

            db.session.rollback()

            flash(
                "Only JPG, JPEG, PNG and WEBP images are allowed.",
                "error"
            )

            return redirect(
                url_for(
                    "product.edit_product",
                    product_id=product.id
                )
            )

        valid_images.append(
            image
        )

    # =====================================================
    # SAVE NEW IMAGES
    # =====================================================

    saved_files = []

    upload_folder = os.path.join(
        current_app.root_path,
        "static",
        "product_images"
    )

    os.makedirs(
        upload_folder,
        exist_ok=True
    )

    for image in valid_images:

        extension = (
            image.filename
            .rsplit(".", 1)[-1]
            .lower()
        )

        filename = (
            f"{uuid.uuid4().hex}.{extension}"
        )

        file_path = os.path.join(
            upload_folder,
            filename
        )

        try:

            image.save(
                file_path
            )

        except Exception:

            db.session.rollback()

            for saved_file in saved_files:

                try:
                    if os.path.exists(saved_file):
                        os.remove(saved_file)
                except Exception:
                    pass

            flash(
                "One or more images could not be saved.",
                "error"
            )

            return redirect(
                url_for(
                    "product.edit_product",
                    product_id=product.id
                )
            )

        saved_files.append(
            file_path
        )

        product_image = ProductImage(
            product_id=product.id,
            filename=filename,
            is_primary=False
        )

        db.session.add(
            product_image
        )

    # =====================================================
    # ENSURE PRIMARY IMAGE
    # =====================================================

    current_images = ProductImage.query.filter_by(
        product_id=product.id
    ).all()

    if current_images:

        has_primary = any(
            image.is_primary
            for image in current_images
        )

        if not has_primary:
            current_images[0].is_primary = True

    # =====================================================
    # COMMIT
    # =====================================================

    try:

        db.session.commit()

    except Exception:

        db.session.rollback()

        for file_path in saved_files:

            try:
                if os.path.exists(file_path):
                    os.remove(file_path)
            except Exception:
                pass

        flash(
            "The product could not be updated.",
            "error"
        )

        return redirect(
            url_for(
                "product.edit_product",
                product_id=product.id
            )
        )

    flash(
        "Product updated successfully.",
        "success"
    )

    return redirect(
        url_for(
            "product.admin_products"
        )
    )


# =========================================================
# ADD PRODUCT
# =========================================================

@product_bp.route(
    "/admin/products/add",
    methods=["GET", "POST"]
)
def add_product():

    if "user_id" not in session:
        return redirect(
            url_for("auth.login")
        )

    if session.get("role") not in ["owner", "admin"]:

        flash(
            "You do not have permission to add products.",
            "error"
        )

        return redirect(
            url_for("dashboard.admin_dashboard")
        )

    categories = Category.query.filter_by(
        is_active=True
    ).order_by(
        Category.name.asc()
    ).all()

    if request.method == "GET":

        return render_template(
            "admin/add_product.html",
            categories=categories
        )

    name = request.form.get(
        "name",
        ""
    ).strip()

    description = request.form.get(
        "description",
        ""
    ).strip()

    category_id = request.form.get(
        "category_id",
        ""
    ).strip()

    meta_title = request.form.get(
        "meta_title",
        ""
    ).strip()

    meta_description = request.form.get(
        "meta_description",
        ""
    ).strip()

    if not name:

        flash(
            "Product name is required.",
            "error"
        )

        return redirect(
            url_for("product.add_product")
        )

    if not description:

        flash(
            "Product description is required.",
            "error"
        )

        return redirect(
            url_for("product.add_product")
        )

    if not category_id:

        flash(
            "Please select a category.",
            "error"
        )

        return redirect(
            url_for("product.add_product")
        )

    try:

        category_id_value = int(category_id)

    except ValueError:

        flash(
            "Invalid category.",
            "error"
        )

        return redirect(
            url_for("product.add_product")
        )

    category = db.session.get(
        Category,
        category_id_value
    )

    if not category:

        flash(
            "Selected category does not exist.",
            "error"
        )

        return redirect(
            url_for("product.add_product")
        )

    if not category.is_active:

        flash(
            "The selected category is inactive.",
            "error"
        )

        return redirect(
            url_for("product.add_product")
        )

    slug = re.sub(
        r"[^a-z0-9]+",
        "-",
        name.lower()
    ).strip("-")

    if not slug:

        flash(
            "A valid product URL could not be created.",
            "error"
        )

        return redirect(
            url_for("product.add_product")
        )

    existing_product = Product.query.filter_by(
        slug=slug
    ).first()

    if existing_product:

        flash(
            "A product with this name already exists.",
            "error"
        )

        return redirect(
            url_for("product.add_product")
        )

    product = Product(
        category_id=category.id,
        name=name,
        slug=slug,
        description=description,
        meta_title=meta_title or None,
        meta_description=meta_description or None,
        is_active=True
    )

    db.session.add(
        product
    )

    masses = request.form.getlist(
        "mass[]"
    )

    prices = request.form.getlist(
        "price[]"
    )

    stocks = request.form.getlist(
        "stock[]"
    )

    skus = request.form.getlist(
        "sku[]"
    )

    if not masses:

        db.session.rollback()

        flash(
            "At least one product size is required.",
            "error"
        )

        return redirect(
            url_for("product.add_product")
        )

    if not (
        len(masses)
        == len(prices)
        == len(stocks)
        == len(skus)
    ):

        db.session.rollback()

        flash(
            "There was a problem with the product sizes.",
            "error"
        )

        return redirect(
            url_for("product.add_product")
        )

    for index in range(len(masses)):

        mass = masses[index].strip()
        price = prices[index].strip()
        stock = stocks[index].strip()
        sku = skus[index].strip()

        if not mass:

            db.session.rollback()

            flash(
                "Every product size must have a mass.",
                "error"
            )

            return redirect(
                url_for("product.add_product")
            )

        if not price:

            db.session.rollback()

            flash(
                "Every product size must have a price.",
                "error"
            )

            return redirect(
                url_for("product.add_product")
            )

        if not stock:

            db.session.rollback()

            flash(
                "Every product size must have a stock quantity.",
                "error"
            )

            return redirect(
                url_for("product.add_product")
            )

        try:

            price_value = Decimal(price)
            stock_value = int(stock)

        except (InvalidOperation, ValueError):

            db.session.rollback()

            flash(
                "Price and stock must contain valid numbers.",
                "error"
            )

            return redirect(
                url_for("product.add_product")
            )

        if price_value < 0:

            db.session.rollback()

            flash(
                "Price cannot be negative.",
                "error"
            )

            return redirect(
                url_for("product.add_product")
            )

        if stock_value < 0:

            db.session.rollback()

            flash(
                "Stock cannot be negative.",
                "error"
            )

            return redirect(
                url_for("product.add_product")
            )

        if sku:

            existing_sku = ProductVariant.query.filter_by(
                sku=sku
            ).first()

            if existing_sku:

                db.session.rollback()

                flash(
                    f"SKU '{sku}' already exists.",
                    "error"
                )

                return redirect(
                    url_for("product.add_product")
                )

        variant = ProductVariant(
            product=product,
            mass=mass,
            price=price_value,
            stock_quantity=stock_value,
            sku=sku or None,
            is_available=True
        )

        db.session.add(
            variant
        )

    image_files = request.files.getlist(
        "images"
    )

    valid_images = []

    allowed_extensions = {
        "jpg",
        "jpeg",
        "png",
        "webp"
    }

    for image in image_files:

        if not image:
            continue

        if not image.filename:
            continue

        if "." not in image.filename:

            db.session.rollback()

            flash(
                "Invalid image file.",
                "error"
            )

            return redirect(
                url_for("product.add_product")
            )

        extension = (
            image.filename
            .rsplit(".", 1)[-1]
            .lower()
        )

        if extension not in allowed_extensions:

            db.session.rollback()

            flash(
                "Only JPG, JPEG, PNG and WEBP images are allowed.",
                "error"
            )

            return redirect(
                url_for("product.add_product")
            )

        valid_images.append(
            image
        )

    if not valid_images:

        db.session.rollback()

        flash(
            "At least one product image is required.",
            "error"
        )

        return redirect(
            url_for("product.add_product")
        )

    db.session.flush()

    upload_folder = os.path.join(
        current_app.root_path,
        "static",
        "product_images"
    )

    os.makedirs(
        upload_folder,
        exist_ok=True
    )

    saved_files = []

    try:

        for index, image in enumerate(valid_images):

            extension = (
                image.filename
                .rsplit(".", 1)[-1]
                .lower()
            )

            filename = (
                f"{uuid.uuid4().hex}.{extension}"
            )

            file_path = os.path.join(
                upload_folder,
                filename
            )

            image.save(
                file_path
            )

            saved_files.append(
                file_path
            )

            product_image = ProductImage(
                product_id=product.id,
                filename=filename,
                is_primary=(index == 0)
            )

            db.session.add(
                product_image
            )

        for variant in product.variants:

            if variant.stock_quantity > 0:

                stock_movement = StockMovement(
                    variant_id=variant.id,
                    quantity=variant.stock_quantity,
                    movement_type="initial_stock",
                    reference="Product creation"
                )

                db.session.add(
                    stock_movement
                )

        db.session.commit()

    except Exception:

        db.session.rollback()

        for file_path in saved_files:

            try:
                if os.path.exists(file_path):
                    os.remove(file_path)
            except Exception:
                pass

        flash(
            "The product could not be saved.",
            "error"
        )

        return redirect(
            url_for("product.add_product")
        )

    flash(
        "Product added successfully.",
        "success"
    )

    return redirect(
        url_for("product.admin_products")
    )


# =========================================================
# TOGGLE PRODUCT STATUS
# =========================================================

@product_bp.route(
    "/admin/products/<int:product_id>/toggle-status",
    methods=["POST"]
)
def toggle_product_status(product_id):

    if "user_id" not in session:
        return redirect(
            url_for("auth.login")
        )

    if session.get("role") not in ["owner", "admin"]:

        flash(
            "You do not have permission to change product status.",
            "error"
        )

        return redirect(
            url_for("dashboard.admin_dashboard")
        )

    product = db.session.get(
        Product,
        product_id
    )

    if not product:

        flash(
            "Product not found.",
            "error"
        )

        return redirect(
            url_for("product.admin_products")
        )

    product.is_active = not product.is_active

    try:

        db.session.commit()

        if product.is_active:

            flash(
                "Product activated successfully.",
                "success"
            )

        else:

            flash(
                "Product deactivated successfully.",
                "success"
            )

    except Exception:

        db.session.rollback()

        flash(
            "The product status could not be changed.",
            "error"
        )

    return redirect(
        url_for("product.admin_products")
    )


# =========================================================
# DELETE PRODUCT
# =========================================================

@product_bp.route(
    "/admin/products/<int:product_id>/delete",
    methods=["POST"]
)
def delete_product(product_id):

    if "user_id" not in session:
        return redirect(
            url_for("auth.login")
        )

    if session.get("role") not in ["owner", "admin"]:

        flash(
            "You do not have permission to delete products.",
            "error"
        )

        return redirect(
            url_for("dashboard.admin_dashboard")
        )

    product = db.session.get(
        Product,
        product_id
    )

    if not product:

        flash(
            "Product not found.",
            "error"
        )

        return redirect(
            url_for("product.admin_products")
        )

    variants = ProductVariant.query.filter_by(
        product_id=product.id
    ).all()

    variant_ids = [
        variant.id
        for variant in variants
    ]

    if variant_ids:

        ordered_variant = OrderItem.query.filter(
            OrderItem.variant_id.in_(variant_ids)
        ).first()

        if ordered_variant:

            flash(
                "This product cannot be deleted because it has already been included in an order. Disable it instead.",
                "error"
            )

            return redirect(
                url_for("product.admin_products")
            )

    images = ProductImage.query.filter_by(
        product_id=product.id
    ).all()

    image_paths = []

    for image in images:

        image_path = os.path.join(
            current_app.root_path,
            "static",
            "product_images",
            image.filename
        )

        image_paths.append(
            image_path
        )

    try:

        for variant in variants:

            StockMovement.query.filter_by(
                variant_id=variant.id
            ).delete(
                synchronize_session=False
            )

        ProductImage.query.filter_by(
            product_id=product.id
        ).delete(
            synchronize_session=False
        )

        ProductVariant.query.filter_by(
            product_id=product.id
        ).delete(
            synchronize_session=False
        )

        db.session.delete(
            product
        )

        db.session.commit()

    except Exception:

        db.session.rollback()

        flash(
            "The product could not be deleted.",
            "error"
        )

        return redirect(
            url_for("product.admin_products")
        )

    image_file_error = False

    for image_path in image_paths:

        try:

            if os.path.exists(image_path):
                os.remove(image_path)

        except Exception:

            image_file_error = True

    if image_file_error:

        flash(
            "Product deleted, but one or more image files could not be removed.",
            "error"
        )

    else:

        flash(
            "Product deleted successfully.",
            "success"
        )

    return redirect(
        url_for("product.admin_products")
    )


# =========================================================
# DELETE PRODUCT IMAGE
# =========================================================

@product_bp.route(
    "/admin/products/<int:product_id>/images/<int:image_id>/delete",
    methods=["POST"]
)
def delete_product_image(product_id, image_id):

    if "user_id" not in session:
        return redirect(
            url_for("auth.login")
        )

    if session.get("role") not in ["owner", "admin"]:

        flash(
            "You do not have permission to delete product images.",
            "error"
        )

        return redirect(
            url_for("dashboard.admin_dashboard")
        )

    product = db.session.get(
        Product,
        product_id
    )

    if not product:

        flash(
            "Product not found.",
            "error"
        )

        return redirect(
            url_for("product.admin_products")
        )

    image = db.session.get(
        ProductImage,
        image_id
    )

    if not image or image.product_id != product.id:

        flash(
            "Product image not found.",
            "error"
        )

        return redirect(
            url_for(
                "product.edit_product",
                product_id=product.id
            )
        )

    image_count = ProductImage.query.filter_by(
        product_id=product.id
    ).count()

    if image_count <= 1:

        flash(
            "A product must have at least one image.",
            "error"
        )

        return redirect(
            url_for(
                "product.edit_product",
                product_id=product.id
            )
        )

    image_path = os.path.join(
        current_app.root_path,
        "static",
        "product_images",
        image.filename
    )

    try:

        db.session.delete(
            image
        )

        db.session.commit()

    except Exception:

        db.session.rollback()

        flash(
            "The product image could not be deleted.",
            "error"
        )

        return redirect(
            url_for(
                "product.edit_product",
                product_id=product.id
            )
        )

    try:

        if os.path.exists(image_path):
            os.remove(image_path)

    except Exception:

        flash(
            "Image removed from the database, but the physical image file could not be deleted.",
            "error"
        )

        return redirect(
            url_for(
                "product.edit_product",
                product_id=product.id
            )
        )

    flash(
        "Product image deleted successfully.",
        "success"
    )

    return redirect(
        url_for(
            "product.edit_product",
            product_id=product.id
        )
    )


# =========================================================
# PUBLIC PRODUCTS
# =========================================================

@product_bp.route("/products")
def products():

    requested_slug = request.args.get(
        "requested"
    )

    products = (
        Product.query
        .filter_by(is_active=True)
        .order_by(Product.created_at.desc())
        .all()
    )

    # -----------------------------------------------------
    # BUILD SELLABLE VARIANT CARDS
    # -----------------------------------------------------

    product_variants = []

    for product in products:

        images = sorted(
            product.images,
            key=lambda image: (
                not image.is_primary,
                image.id
            )
        )

        primary_image = (
            images[0]
            if images
            else None
        )

        variants = (
            ProductVariant.query
            .filter_by(
                product_id=product.id,
                is_available=True
            )
            .filter(
                ProductVariant.stock_quantity > 0
            )
            .order_by(
                ProductVariant.price.asc()
            )
            .all()
        )

        for variant in variants:

            product_variants.append({
                "product": product,
                "variant": variant,
                "image": primary_image
            })

    requested_product = None
    requested_unavailable = False

    if requested_slug:

        requested_product = Product.query.filter_by(
            slug=requested_slug
        ).first()

        if not requested_product:

            requested_unavailable = True

        elif not requested_product.is_active:

            requested_unavailable = True

        else:

            available_variant = ProductVariant.query.filter_by(
                product_id=requested_product.id,
                is_available=True
            ).filter(
                ProductVariant.stock_quantity > 0
            ).first()

            if not available_variant:

                requested_unavailable = True

    site_setting = SiteSetting.query.first()

    return render_template(
        "products.html",
        products=products,
        product_variants=product_variants,
        requested_product=requested_product,
        requested_unavailable=requested_unavailable,
        requested_slug=requested_slug,
        site_setting=site_setting
    )


# =========================================================
# PRODUCT DETAIL
# =========================================================

@product_bp.route(
    "/shop/<slug>"
)
def product_detail(slug):

    product = Product.query.filter_by(
        slug=slug,
        is_active=True
    ).first()

    if not product:

        return "Product not found", 404

    variants = (
        ProductVariant.query
        .filter_by(
            product_id=product.id,
            is_available=True
        )
        .filter(
            ProductVariant.stock_quantity > 0
        )
        .order_by(
            ProductVariant.price.asc()
        )
        .all()
    )

    images = ProductImage.query.filter_by(
        product_id=product.id
    ).order_by(
        ProductImage.is_primary.desc(),
        ProductImage.id.asc()
    ).all()

    site_setting = SiteSetting.query.first()

    return render_template(
        "product_detail.html",
        product=product,
        variants=variants,
        images=images,
        site_setting=site_setting
    )


# =========================================================
# ADMIN PRODUCTS
# =========================================================

@product_bp.route(
    "/admin/products"
)
def admin_products():

    if "user_id" not in session:

        return redirect(
            url_for("auth.login")
        )

    if session.get("role") not in ["owner", "admin"]:

        flash(
            "You do not have permission to view products.",
            "error"
        )

        return redirect(
            url_for("dashboard.admin_dashboard")
        )

    products = Product.query.order_by(
        Product.created_at.desc()
    ).all()

    return render_template(
        "admin/products.html",
        products=products
    )


# =========================================================
# SET PRIMARY PRODUCT IMAGE
# =========================================================

@product_bp.route(
    "/admin/products/<int:product_id>/images/<int:image_id>/primary",
    methods=["POST"]
)
def set_primary_product_image(product_id, image_id):

    if "user_id" not in session:

        return redirect(
            url_for("auth.login")
        )

    if session.get("role") not in ["owner", "admin"]:

        flash(
            "You do not have permission to change product images.",
            "error"
        )

        return redirect(
            url_for("dashboard.admin_dashboard")
        )

    product = db.session.get(
        Product,
        product_id
    )

    if not product:

        flash(
            "Product not found.",
            "error"
        )

        return redirect(
            url_for("product.admin_products")
        )

    image = db.session.get(
        ProductImage,
        image_id
    )

    if not image or image.product_id != product.id:

        flash(
            "Product image not found.",
            "error"
        )

        return redirect(
            url_for(
                "product.edit_product",
                product_id=product.id
            )
        )

    try:

        ProductImage.query.filter_by(
            product_id=product.id
        ).update(
            {
                "is_primary": False
            }
        )

        image.is_primary = True

        db.session.commit()

        flash(
            "Primary product image updated successfully.",
            "success"
        )

    except Exception:

        db.session.rollback()

        flash(
            "The primary product image could not be changed.",
            "error"
        )

    return redirect(
        url_for(
            "product.edit_product",
            product_id=product.id
        )
    )