import { convexTest } from "convex-test";
import { describe, it, expect } from "vitest";
import { api } from "./_generated/api";
import schema from "./schema";
import type { Doc } from "./_generated/dataModel";

const modules = import.meta.glob("./**/*.ts");

async function setupAdminUser(t: ReturnType<typeof convexTest>) {
  const asAdmin = t.withIdentity({
    name: "Admin",
    email: "admin@example.com",
    subject: "clerk_admin_123",
  });
  const userId = await asAdmin.mutation(api.users.store, {});
  await t.run(async (ctx) => {
    await ctx.db.patch(userId, { role: "admin" });
  });
  return asAdmin;
}

async function setupCategory(t: ReturnType<typeof convexTest>) {
  let categoryId: import("./_generated/dataModel").Id<"categories">;
  await t.run(async (ctx) => {
    categoryId = await ctx.db.insert("categories", {
      name: "Pet Food",
      slug: "pet-food",
    });
  });
  return categoryId!;
}

async function setupProduct(
  t: ReturnType<typeof convexTest>,
  asAdmin: ReturnType<ReturnType<typeof convexTest>["withIdentity"]>,
) {
  const categoryId = await setupCategory(t);
  return await asAdmin.mutation(api.products.create, {
    name: "Review Product",
    slug: "review-product",
    status: "active",
    categoryId,
    tags: [],
  });
}

describe("reviews", () => {
  it("create adds review with isApproved false and duplicate throws", async () => {
    const t = convexTest(schema, modules);
    const asAdmin = await setupAdminUser(t);
    const productId = await setupProduct(t, asAdmin);

    const asCustomer = t.withIdentity({
      name: "Customer",
      email: "customer@example.com",
      subject: "clerk_customer_123",
    });
    await asCustomer.mutation(api.users.store, {});

    const reviewId = await asCustomer.mutation(api.reviews.create, {
      productId,
      rating: 5,
      title: "Great product",
      content: "Really liked it.",
    });
    expect(reviewId).toBeTruthy();

    const byProduct = await t.query(api.reviews.listByProduct, {
      productId,
      limit: 10,
      offset: 0,
    });
    expect(byProduct.page).toHaveLength(0);

    await expect(
      asCustomer.mutation(api.reviews.create, {
        productId,
        rating: 4,
        title: "Second",
        content: "Duplicate.",
      }),
    ).rejects.toThrow(/already reviewed/);
  });

  it("approve updates product averageRating and reviewCount", async () => {
    const t = convexTest(schema, modules);
    const asAdmin = await setupAdminUser(t);
    const productId = await setupProduct(t, asAdmin);

    const asCustomer = t.withIdentity({
      name: "Customer",
      email: "customer@example.com",
      subject: "clerk_customer_123",
    });
    await asCustomer.mutation(api.users.store, {});

    const reviewId = await asCustomer.mutation(api.reviews.create, {
      productId,
      rating: 5,
      title: "Great",
      content: "Content here.",
    });

    const productBefore = (await t.run(async (ctx) => ctx.db.get(productId))) as Doc<"products"> | null;
    expect(productBefore?.averageRating).toBeUndefined();
    expect(productBefore?.reviewCount).toBeUndefined();

    await asAdmin.mutation(api.reviews.approve, { id: reviewId });

    const productAfter = (await t.run(async (ctx) => ctx.db.get(productId))) as Doc<"products"> | null;
    expect(productAfter?.averageRating).toBe(5);
    expect(productAfter?.reviewCount).toBe(1);

    const byProduct = await t.query(api.reviews.listByProduct, {
      productId,
      limit: 10,
      offset: 0,
    });
    expect(byProduct.page).toHaveLength(1);
    expect(byProduct.page[0].rating).toBe(5);
  });

  it("create throws when rating out of range", async () => {
    const t = convexTest(schema, modules);
    const asAdmin = await setupAdminUser(t);
    const productId = await setupProduct(t, asAdmin);

    const asCustomer = t.withIdentity({
      name: "Customer",
      email: "customer@example.com",
      subject: "clerk_customer_123",
    });
    await asCustomer.mutation(api.users.store, {});

    await expect(
      asCustomer.mutation(api.reviews.create, {
        productId,
        rating: 0,
        title: "Bad",
        content: "Content.",
      }),
    ).rejects.toThrow(/1 and 5/);

    await expect(
      asCustomer.mutation(api.reviews.create, {
        productId,
        rating: 6,
        title: "Bad",
        content: "Content.",
      }),
    ).rejects.toThrow(/1 and 5/);
  });

  it("listForAdmin and approve and deleteReview require admin", async () => {
    const t = convexTest(schema, modules);
    const asAdmin = await setupAdminUser(t);
    const productId = await setupProduct(t, asAdmin);

    const asCustomer = t.withIdentity({
      name: "Customer",
      email: "customer@example.com",
      subject: "clerk_customer_123",
    });
    await asCustomer.mutation(api.users.store, {});
    const reviewId = await asCustomer.mutation(api.reviews.create, {
      productId,
      rating: 3,
      title: "OK",
      content: "Content.",
    });

    await expect(
      asCustomer.query(api.reviews.listForAdmin, { limit: 10, offset: 0 }),
    ).rejects.toThrow(/admin|Unauthorized/);

    await expect(
      asCustomer.mutation(api.reviews.approve, { id: reviewId }),
    ).rejects.toThrow(/admin|Unauthorized/);

    await expect(
      asCustomer.mutation(api.reviews.deleteReview, { id: reviewId }),
    ).rejects.toThrow(/admin|Unauthorized/);

    await asAdmin.mutation(api.reviews.approve, { id: reviewId });
    const adminList = await asAdmin.query(api.reviews.listForAdmin, {
      limit: 10,
      offset: 0,
    });
    expect(adminList.page.length).toBeGreaterThanOrEqual(1);
  });
  it("listByProductSorted sorts correctly", async () => {
    const t = convexTest(schema, modules);
    const asAdmin = await setupAdminUser(t);
    const productId = await setupProduct(t, asAdmin);

    const asCustomer1 = t.withIdentity({
      name: "Customer 1",
      email: "customer1@example.com",
      subject: "clerk_c1",
    });
    await asCustomer1.mutation(api.users.store, {});
    const r1 = await asCustomer1.mutation(api.reviews.create, {
      productId,
      rating: 3,
      title: "C1",
      content: "C1",
    });

    const asCustomer2 = t.withIdentity({
      name: "Customer 2",
      email: "customer2@example.com",
      subject: "clerk_c2",
    });
    await asCustomer2.mutation(api.users.store, {});
    const r2 = await asCustomer2.mutation(api.reviews.create, {
      productId,
      rating: 5,
      title: "C2",
      content: "C2",
    });

    await asAdmin.mutation(api.reviews.approve, { id: r1 });
    await asAdmin.mutation(api.reviews.approve, { id: r2 });

    const highest = await t.query(api.reviews.listByProductSorted, {
      productId,
      sortBy: "highest",
    });
    expect(highest.page.length).toBe(2);
    expect(highest.page[0].rating).toBe(5);

    const lowest = await t.query(api.reviews.listByProductSorted, {
      productId,
      sortBy: "lowest",
    });
    expect(lowest.page[0].rating).toBe(3);
  });

  it("hasUserReviewed works", async () => {
    const t = convexTest(schema, modules);
    const asAdmin = await setupAdminUser(t);
    const productId = await setupProduct(t, asAdmin);

    const asCustomer = t.withIdentity({
      name: "Customer",
      email: "cust@example.com",
      subject: "clerk_c_has",
    });
    await asCustomer.mutation(api.users.store, {});

    const hasBefore = await asCustomer.query(api.reviews.hasUserReviewed, { productId });
    expect(hasBefore).toBe(false);

    await asCustomer.mutation(api.reviews.create, {
      productId,
      rating: 4,
      title: "Has",
      content: "Has content",
    });

    const hasAfter = await asCustomer.query(api.reviews.hasUserReviewed, { productId });
    expect(hasAfter).toBe(true);
  });

  it("submitAndRecalculate works", async () => {
    const t = convexTest(schema, modules);
    const asAdmin = await setupAdminUser(t);
    const productId = await setupProduct(t, asAdmin);

    const asCustomer = t.withIdentity({
      name: "Customer",
      email: "cust_submit@example.com",
      subject: "clerk_c_submit",
    });
    await asCustomer.mutation(api.users.store, {});

    const reviewId = await asCustomer.action(api.reviews.submitAndRecalculate, {
      productId,
      rating: 4,
      title: "Title",
      content: "Content",
    });

    expect(reviewId).toBeTruthy();

    const product = (await t.run(async (ctx) => ctx.db.get(productId))) as Doc<"products"> | null;
    // Since isApproved is false initially, the stats are actually undefined (or unchanged)
    expect(product?.averageRating).toBeUndefined();
    expect(product?.reviewCount).toBeUndefined();
  });
});
