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

export const list = query({
  args: {},
  handler: async (ctx) => {
    const user = await Users.getCurrentUserOrThrow(ctx);
    const rows = await ctx.db
      .query("wishlists")
      .withIndex("by_user", (q) => q.eq("userId", user._id))
      .collect();

    if (rows.length === 0) return [];

    const productIds = [...new Set(rows.map((r) => r.productId))];
    const products = (
      await Promise.all(productIds.map((id) => ctx.db.get(id)))
    ).filter((p): p is Doc<"products"> => p != null);

    const enriched = await enrichProducts(ctx, products);
    const productMap = new Map(
      enriched.map((p) => [p._id, p]),
    );

    return rows.map((row) => {
      const product = productMap.get(row.productId);
      const variant = row.variantId && product?.variants
        ? product.variants.find((v: { _id: Id<"productVariants"> }) => v._id === row.variantId)
        : undefined;
      return { ...row, product, variant };
    });
  },
});

function findExistingEntry(
  rows: { variantId?: Id<"productVariants"> }[],
  variantId?: Id<"productVariants">,
) {
  return rows.find((r) => {
    if (variantId === undefined && r.variantId === undefined) return true;
    return r.variantId === variantId;
  });
}

export const add = mutation({
  args: {
    productId: v.id("products"),
    variantId: v.optional(v.id("productVariants")),
    notifyOnPriceDrop: v.optional(v.boolean()),
    notifyOnBackInStock: v.optional(v.boolean()),
  },
  handler: async (ctx, args) => {
    const user = await Users.getCurrentUserOrThrow(ctx);
    const existing = await ctx.db
      .query("wishlists")
      .withIndex("by_user_and_product", (q) =>
        q.eq("userId", user._id).eq("productId", args.productId),
      )
      .collect();

    const found = findExistingEntry(existing, args.variantId);
    if (found)
      return { id: (found as { _id: Id<"wishlists"> })._id, alreadyExisted: true };

    let priceWhenAdded = 0;
    if (args.variantId) {
      const variant = await ctx.db.get(args.variantId);
      if (variant && variant.productId === args.productId) {
        priceWhenAdded = variant.price;
      }
    } else {
      const variants = await ctx.db
        .query("productVariants")
        .withIndex("by_product_and_active", (q) =>
          q.eq("productId", args.productId).eq("isActive", true),
        )
        .first();
      if (variants) priceWhenAdded = variants.price;
    }

    const id = await ctx.db.insert("wishlists", {
      userId: user._id,
      productId: args.productId,
      variantId: args.variantId,
      addedAt: Date.now(),
      notifyOnPriceDrop: args.notifyOnPriceDrop ?? false,
      notifyOnBackInStock: args.notifyOnBackInStock ?? false,
      priceWhenAdded,
    });
    return { id, alreadyExisted: false };
  },
});

export const remove = mutation({
  args: { id: v.id("wishlists") },
  handler: async (ctx, { id }) => {
    const doc = await ctx.db.get(id);
    if (!doc) throw new Error("Wishlist item not found");
    await Users.requireOwnership(ctx, doc.userId);
    await ctx.db.delete(id);
  },
});

export const toggle = mutation({
  args: {
    productId: v.id("products"),
    variantId: v.optional(v.id("productVariants")),
  },
  handler: async (ctx, args) => {
    const user = await Users.getCurrentUserOrThrow(ctx);
    const existing = await ctx.db
      .query("wishlists")
      .withIndex("by_user_and_product", (q) =>
        q.eq("userId", user._id).eq("productId", args.productId),
      )
      .collect();

    const found = findExistingEntry(existing, args.variantId) as
      | { _id: Id<"wishlists"> }
      | undefined;
    if (found) {
      await ctx.db.delete(found._id);
      return { removed: true };
    }

    const id = await ctx.db.insert("wishlists", {
      userId: user._id,
      productId: args.productId,
      variantId: args.variantId,
      addedAt: Date.now(),
      notifyOnPriceDrop: false,
      notifyOnBackInStock: false,
      priceWhenAdded: await (async () => {
        if (args.variantId) {
          const v = await ctx.db.get(args.variantId);
          return v && v.productId === args.productId ? v.price : 0;
        }
        const first = await ctx.db
          .query("productVariants")
          .withIndex("by_product_and_active", (q) =>
            q.eq("productId", args.productId).eq("isActive", true),
          )
          .first();
        return first?.price ?? 0;
      })(),
    });
    return { added: true, id };
  },
});

export const count = query({
  args: {},
  handler: async (ctx) => {
    const user = await Users.getCurrentUser(ctx);
    if (!user) return 0;

    const rows = await ctx.db
      .query("wishlists")
      .withIndex("by_user", (q) => q.eq("userId", user._id))
      .collect();
    return rows.length;
  },
});

export const isWishlisted = query({
  args: {
    productId: v.id("products"),
    variantId: v.optional(v.id("productVariants")),
  },
  handler: async (ctx, args) => {
    const user = await Users.getCurrentUserOrThrow(ctx);
    const rows = await ctx.db
      .query("wishlists")
      .withIndex("by_user_and_product", (q) =>
        q.eq("userId", user._id).eq("productId", args.productId),
      )
      .collect();
    return !!findExistingEntry(rows, args.variantId);
  },
});
