import { query, mutation, action, internalMutation } from "./_generated/server";
import { v } from "convex/values";
import * as Users from "./model/users";
import { recalculateProductRating } from "./model/products";
import type { Id } from "./_generated/dataModel";
import type { QueryCtx } from "./_generated/server";
import { internal, api } from "./_generated/api";

type CtxWithDb = Pick<QueryCtx, "db">;

/**
 * Returns true if the user has at least one delivered order that contains
 * an order item whose variant belongs to the given product.
 */
async function hasVerifiedPurchase(
  ctx: CtxWithDb,
  userId: Id<"users">,
  productId: Id<"products">,
): Promise<boolean> {
  const orders = await ctx.db
    .query("orders")
    .withIndex("by_user", (q) => q.eq("userId", userId))
    .collect();

  const deliveredOrders = orders.filter((o) => o.status === "delivered");

  for (const order of deliveredOrders) {
    const items = await ctx.db
      .query("orderItems")
      .withIndex("by_order", (q) => q.eq("orderId", order._id))
      .collect();
    for (const item of items) {
      const variant = await ctx.db.get(item.variantId);
      if (variant?.productId === productId) return true;
    }
  }
  return false;
}

export const listByProduct = query({
  args: {
    productId: v.id("products"),
    limit: v.optional(v.number()),
    offset: v.optional(v.number()),
  },
  handler: async (ctx, args) => {
    const limit = args.limit ?? 20;
    const offset = args.offset ?? 0;
    const all = await ctx.db
      .query("reviews")
      .withIndex("by_product_approved", (q) =>
        q.eq("productId", args.productId).eq("isApproved", true),
      )
      .collect();
    all.sort((a, b) => b.createdAt - a.createdAt);
    const total = all.length;
    const page = all.slice(offset, offset + limit);
    const users = await Promise.all(
      page.map((r) => ctx.db.get(r.userId)),
    );
    const withUser = page.map((r, i) => ({
      ...r,
      userName: users[i]?.name,
    }));
    return { page: withUser, total, hasMore: offset + limit < total };
  },
});

export const create = mutation({
  args: {
    productId: v.id("products"),
    rating: v.number(),
    title: v.string(),
    content: v.string(),
    images: v.optional(v.array(v.string())),
  },
  handler: async (ctx, args) => {
    const user = await Users.getCurrentUserOrThrow(ctx);
    if (args.rating < 1 || args.rating > 5) {
      throw new Error("Rating must be between 1 and 5");
    }
    const existing = await ctx.db
      .query("reviews")
      .withIndex("by_user", (q) => q.eq("userId", user._id))
      .collect();
    if (existing.some((r) => r.productId === args.productId)) {
      throw new Error("You have already reviewed this product");
    }
    const verifiedPurchase = await hasVerifiedPurchase(ctx, user._id, args.productId);
    const now = Date.now();
    return await ctx.db.insert("reviews", {
      productId: args.productId,
      userId: user._id,
      rating: args.rating,
      title: args.title,
      content: args.content,
      images: args.images,
      verifiedPurchase,
      helpfulCount: 0,
      createdAt: now,
      updatedAt: now,
      isApproved: false,
    });
  },
});

export const listForAdmin = query({
  args: {
    limit: v.optional(v.number()),
    offset: v.optional(v.number()),
    productId: v.optional(v.id("products")),
    isApproved: v.optional(v.boolean()),
  },
  handler: async (ctx, args) => {
    await Users.requireAdmin(ctx);
    const limit = args.limit ?? 20;
    const offset = args.offset ?? 0;
    let list;
    if (args.productId !== undefined && args.isApproved !== undefined) {
      list = await ctx.db
        .query("reviews")
        .withIndex("by_product_approved", (idx) =>
          idx.eq("productId", args.productId!).eq("isApproved", args.isApproved!),
        )
        .collect();
    } else if (args.productId !== undefined) {
      list = await ctx.db
        .query("reviews")
        .withIndex("by_product", (idx) =>
          idx.eq("productId", args.productId!),
        )
        .collect();
    } else if (args.isApproved !== undefined) {
      list = await ctx.db
        .query("reviews")
        .filter((q) => q.eq(q.field("isApproved"), args.isApproved))
        .collect();
    } else {
      list = await ctx.db.query("reviews").collect();
    }
    list.sort((a, b) => b.createdAt - a.createdAt);
    const total = list.length;
    const page = list.slice(offset, offset + limit);
    return { page, total, hasMore: offset + limit < total };
  },
});

export const approve = mutation({
  args: { id: v.id("reviews") },
  handler: async (ctx, { id }) => {
    await Users.requireAdmin(ctx);
    const review = await ctx.db.get(id);
    if (!review) throw new Error("Review not found");
    await ctx.db.patch(id, {
      isApproved: true,
      updatedAt: Date.now(),
    });
    await recalculateProductRating(ctx, review.productId);
  },
});

export const deleteReview = mutation({
  args: { id: v.id("reviews") },
  handler: async (ctx, { id }) => {
    await Users.requireAdmin(ctx);
    const review = await ctx.db.get(id);
    if (!review) throw new Error("Review not found");
    const productId = review.productId;
    await ctx.db.delete(id);
    await recalculateProductRating(ctx, productId);
  },
});

export const listByProductSorted = query({
  args: {
    productId: v.id("products"),
    limit: v.optional(v.number()),
    offset: v.optional(v.number()),
    sortBy: v.optional(
      v.union(
        v.literal("newest"),
        v.literal("oldest"),
        v.literal("highest"),
        v.literal("lowest"),
        v.literal("helpful"),
      ),
    ),
  },
  handler: async (ctx, args) => {
    const limit = args.limit ?? 10;
    const offset = args.offset ?? 0;
    const sortBy = args.sortBy ?? "newest";

    const all = await ctx.db
      .query("reviews")
      .withIndex("by_product_approved", (q) =>
        q.eq("productId", args.productId).eq("isApproved", true),
      )
      .collect();

    // Sort in-memory
    all.sort((a, b) => {
      switch (sortBy) {
        case "oldest":
          return a.createdAt - b.createdAt;
        case "highest":
          return b.rating - a.rating || b.createdAt - a.createdAt;
        case "lowest":
          return a.rating - b.rating || b.createdAt - a.createdAt;
        case "helpful":
          return b.helpfulCount - a.helpfulCount || b.createdAt - a.createdAt;
        default: // "newest"
          return b.createdAt - a.createdAt;
      }
    });

    const total = all.length;
    const page = all.slice(offset, offset + limit);
    const users = await Promise.all(page.map((r) => ctx.db.get(r.userId)));
    const withUser = page.map((r, i) => ({
      ...r,
      userName: users[i]?.name,
    }));
    return { page: withUser, total, hasMore: offset + limit < total };
  },
});

export const hasUserReviewed = query({
  args: { productId: v.id("products") },
  handler: async (ctx, args) => {
    const user = await Users.getCurrentUser(ctx);
    if (!user) return false;
    const userReviews = await ctx.db
      .query("reviews")
      .withIndex("by_user", (q) => q.eq("userId", user._id))
      .collect();
    return userReviews.some((r) => r.productId === args.productId);
  },
});

export const recalculate = internalMutation({
  args: { productId: v.id("products") },
  handler: async (ctx, args) => {
    await recalculateProductRating(ctx, args.productId);
  },
});

export const submitAndRecalculate = action({
  args: {
    productId: v.id("products"),
    rating: v.number(),
    title: v.string(),
    content: v.string(),
    images: v.optional(v.array(v.string())),
  },
  handler: async (ctx, args): Promise<Id<"reviews">> => {
    const reviewId = (await ctx.runMutation(api.reviews.create, {
      productId: args.productId,
      rating: args.rating,
      title: args.title,
      content: args.content,
      images: args.images,
    })) as Id<"reviews">;
    await ctx.runMutation(internal.reviews.recalculate, {
      productId: args.productId,
    });
    return reviewId;
  },
});
