import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
import type { Id } from "./_generated/dataModel";
import * as Users from "./model/users";
import * as CartsModel from "./model/carts";


type EnrichedItem = {
  variantId: string;
  productId: string;
  quantity: number;
  priceSnapshot: number;
  productName: string;
  variantName: string;
  imageUrl?: string;
  stockQuantity?: number;
  /** For PDP link: /shop/{parentCategorySlug}/{childCategorySlug}/{slug} */
  productSlug?: string;
  parentCategorySlug?: string;
  childCategorySlug?: string;
};

async function enrichCartItems(
  ctx: { db: import("./_generated/server").QueryCtx["db"] },
  items: { productId: Id<"products">; variantId?: Id<"productVariants">; quantity: number; price: number }[]
): Promise<EnrichedItem[]> {
  const enriched: EnrichedItem[] = [];
  for (const item of items) {
    const product = await ctx.db.get(item.productId);
    const variant = item.variantId ? await ctx.db.get(item.variantId) : null;
    if (!product || !variant) continue;
    const images = await ctx.db
      .query("productImages")
      .withIndex("by_product", (q) => q.eq("productId", item.productId))
      .collect();
    images.sort((a, b) => a.position - b.position);
    const imageUrl = images[0]?.url;
    enriched.push({
      variantId: item.variantId!,
      productId: item.productId,
      quantity: item.quantity,
      priceSnapshot: item.price,
      productName: product.name,
      variantName: variant.name,
      imageUrl,
      stockQuantity: variant.stockQuantity,
      productSlug: product.slug,
      parentCategorySlug: product.parentCategorySlug,
      childCategorySlug: product.childCategorySlug,
    });
  }
  return enriched;
}

export const get = query({
  args: { sessionId: v.optional(v.string()) },
  handler: async (ctx, args) => {
    const user = await Users.getCurrentUser(ctx);
    const userId = user?._id ?? null;
    const sessionId = args.sessionId;
    if (!userId && !sessionId) return null;

    const cart = await CartsModel.getCart(ctx, userId ?? undefined, sessionId);
    if (!cart || cart.items.length === 0) {
      return cart ? { ...cart, items: [] } : null;
    }

    const items = await enrichCartItems(ctx, cart.items);
    return { ...cart, items };
  },
});

export const addItem = mutation({
  args: {
    variantId: v.id("productVariants"),
    quantity: v.number(),
    sessionId: v.optional(v.string()),
  },
  handler: async (ctx, args) => {
    const user = await Users.getCurrentUser(ctx);
    const userId = user?._id;
    if (!userId && !args.sessionId) {
      throw new Error("Must be authenticated or provide sessionId");
    }
    if (args.quantity < 1) throw new Error("Quantity must be at least 1");

    const cart = await CartsModel.getOrCreateCart(ctx, userId, args.sessionId);
    const variant = await ctx.db.get(args.variantId);
    if (!variant) throw new Error("Variant not found");
    if (!variant.isActive) throw new Error("Variant is not available");

    const existingQty =
      cart.items.find((i) => i.variantId === args.variantId)?.quantity ?? 0;
    const newQty = existingQty + args.quantity;
    if (variant.stockQuantity < newQty) {
      throw new Error(
        `Insufficient stock: only ${variant.stockQuantity} available`
      );
    }

    const productId = variant.productId;
    const newItems = [...cart.items];
    const idx = newItems.findIndex((i) => i.variantId === args.variantId);
    if (idx >= 0) {
      newItems[idx] = {
        ...newItems[idx],
        quantity: newItems[idx].quantity + args.quantity,
      };
    } else {
      newItems.push({
        productId,
        variantId: args.variantId,
        quantity: args.quantity,
        price: variant.price,
      });
    }

    await ctx.db.patch(cart._id, {
      items: newItems,
      updatedAt: Date.now(),
    });
    return cart._id;
  },
});

export const updateItem = mutation({
  args: {
    variantId: v.id("productVariants"),
    quantity: v.number(),
    sessionId: v.optional(v.string()),
  },
  handler: async (ctx, args) => {
    const user = await Users.getCurrentUser(ctx);
    const userId = user?._id;
    if (!userId && !args.sessionId) {
      throw new Error("Must be authenticated or provide sessionId");
    }

    const cart = await CartsModel.getOrCreateCart(ctx, userId, args.sessionId);
    const idx = cart.items.findIndex((i) => i.variantId === args.variantId);
    if (idx < 0) return cart._id;

    if (args.quantity === 0) {
      const newItems = cart.items.filter((i) => i.variantId !== args.variantId);
      await ctx.db.patch(cart._id, { items: newItems, updatedAt: Date.now() });
      return cart._id;
    }

    const variant = await ctx.db.get(args.variantId);
    if (!variant) throw new Error("Variant not found");
    if (variant.stockQuantity < args.quantity) {
      throw new Error(
        `Insufficient stock: only ${variant.stockQuantity} available`
      );
    }

    const newItems = [...cart.items];
    newItems[idx] = { ...newItems[idx], quantity: args.quantity };
    await ctx.db.patch(cart._id, { items: newItems, updatedAt: Date.now() });
    return cart._id;
  },
});

export const removeItem = mutation({
  args: {
    variantId: v.id("productVariants"),
    sessionId: v.optional(v.string()),
  },
  handler: async (ctx, args) => {
    const user = await Users.getCurrentUser(ctx);
    const userId = user?._id;
    if (!userId && !args.sessionId) {
      throw new Error("Must be authenticated or provide sessionId");
    }

    const cart = await CartsModel.getOrCreateCart(ctx, userId, args.sessionId);
    const newItems = cart.items.filter((i) => i.variantId !== args.variantId);
    await ctx.db.patch(cart._id, { items: newItems, updatedAt: Date.now() });
    return cart._id;
  },
});

export const clear = mutation({
  args: { sessionId: v.optional(v.string()) },
  handler: async (ctx, args) => {
    const user = await Users.getCurrentUser(ctx);
    const userId = user?._id;
    if (!userId && !args.sessionId) {
      throw new Error("Must be authenticated or provide sessionId");
    }

    const cart = await CartsModel.getOrCreateCart(ctx, userId, args.sessionId);
    await ctx.db.patch(cart._id, { items: [], updatedAt: Date.now() });
    return cart._id;
  },
});

export const merge = mutation({
  args: { sessionId: v.optional(v.string()) },
  handler: async (ctx, args) => {
    const user = await Users.getCurrentUserOrThrow(ctx);
    const userCart = await CartsModel.getOrCreateCart(ctx, user._id, undefined);

    if (!args.sessionId) return userCart._id;
    const guestCart = await ctx.db
      .query("carts")
      .withIndex("by_session", (q) => q.eq("sessionId", args.sessionId!))
      .unique();
    if (!guestCart || guestCart.items.length === 0) return userCart._id;

    const mergedItems = [...userCart.items];
    for (const guestItem of guestCart.items) {
      const variantId = guestItem.variantId;
      if (!variantId) continue;
      const existing = mergedItems.find((i) => i.variantId === variantId);
      if (existing) {
        const variant = await ctx.db.get(variantId);
        if (variant) {
          const newQty = existing.quantity + guestItem.quantity;
          const cap = Math.min(newQty, variant.stockQuantity);
          existing.quantity = cap;
          existing.price = variant.price;
        }
      } else {
        mergedItems.push({ ...guestItem, variantId });
      }
    }

    await ctx.db.patch(userCart._id, {
      items: mergedItems,
      updatedAt: Date.now(),
    });

    await ctx.db.patch(guestCart._id, { items: [], updatedAt: Date.now() });
    return userCart._id;
  },
});
