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

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

async function setupUserAndData(t: ReturnType<typeof convexTest>) {
  const asCustomer = t.withIdentity({
    name: "Alice",
    email: "alice@example.com",
    subject: "clerk_alice_123",
  });
  const userId = await asCustomer.mutation(api.users.store, {});

  let categoryId: any;
  let variantId: any;
  await t.run(async (ctx) => {
    categoryId = await ctx.db.insert("categories", {
      name: "Toys",
      slug: "toys",
    });
    const productId = await ctx.db.insert("products", {
      name: "Ball",
      slug: "ball",
      status: "active",
      categoryId,
      tags: [],
      parentCategorySlug: "toys",
      childCategorySlug: "toys",
    });
    variantId = await ctx.db.insert("productVariants", {
      productId,
      name: "Red Ball",
      sku: "BALL-RED-001",
      price: 999,
      stockQuantity: 50,
      attributes: { color: "Red" },
      isActive: true,
      weight: 200,
      weightUnit: "g",
    });
  });

  return { asCustomer, variantId, userId };
}

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

describe("orders", () => {
  it("listMine returns only the current user's orders", async () => {
    const t = convexTest(schema, modules);
    const { asCustomer, variantId, userId } = await setupUserAndData(t);

    await makeConfirmedOrder(t, userId, variantId);

    const result = await asCustomer.query(api.orders.listMine, {
      paginationOpts: { numItems: 10, cursor: null },
    });
    expect(result.page).toHaveLength(1);
  });

  it("listMine returns empty for user with no orders", async () => {
    const t = convexTest(schema, modules);
    const asCustomer = t.withIdentity({
      name: "Bob",
      email: "bob@example.com",
      subject: "clerk_bob_123",
    });
    await asCustomer.mutation(api.users.store, {});

    const result = await asCustomer.query(api.orders.listMine, {
      paginationOpts: { numItems: 10, cursor: null },
    });
    expect(result.page).toHaveLength(0);
  });

  it("getById throws if order belongs to a different user", async () => {
    const t = convexTest(schema, modules);
    const { asCustomer, variantId, userId } = await setupUserAndData(t);

    const orderId = await makeConfirmedOrder(t, userId, variantId);

    const asBob = t.withIdentity({
      name: "Bob",
      email: "bob@example.com",
      subject: "clerk_bob_123",
    });
    await asBob.mutation(api.users.store, {});

    await expect(
      asBob.query(api.orders.getById, { id: orderId }),
    ).rejects.toThrow("Unauthorized: order does not belong to you");
  });

  it("create creates order and order items atomically", async () => {
    const t = convexTest(schema, modules);
    const { asCustomer, variantId, userId } = await setupUserAndData(t);

    const orderId = await asCustomer.mutation(api.orders.create, {
      shippingAddressSnapshot: {
        fullName: "Alice Customer",
        firstName: "Alice",
        lastName: "Customer",
        addressLine1: "123 Main St",
        city: "Springfield",
        postalCode: "62701",
        country: "US",
      },
      billingAddressSnapshot: {
        firstName: "Alice",
        lastName: "Customer",
        addressLine1: "123 Main St",
        city: "Springfield",
        postalCode: "62701",
        country: "US",
      },
      items: [
        {
          variantId,
          productName: "Ball",
          variantName: "Red Ball",
          sku: "BALL-RED-001",
          quantity: 3,
          unitPrice: 999,
        },
      ],
      shippingCost: 500,
      discount: 100,
    });

    const order = await asCustomer.query(api.orders.getById, { id: orderId });
    expect(order).not.toBeNull();
    expect(order.subtotal).toBe(2997);
    expect(order.total).toBe(2997 + 500 - 100 + 0); // subtotal + shipping - discount + tax
    expect(order.items).toHaveLength(1);
    expect(order.items[0].totalPrice).toBe(2997);
  });

  it("updateStatus throws for non-admin users", async () => {
    const t = convexTest(schema, modules);
    const { asCustomer, variantId, userId } = await setupUserAndData(t);

    const orderId = await makeConfirmedOrder(t, userId, variantId);

    await expect(
      asCustomer.mutation(api.orders.updateStatus, {
        id: orderId,
        status: "shipped",
      }),
    ).rejects.toThrow("Unauthorized: admin access required");
  });

  it("createFromCart creates order and clears cart on success", async () => {
    const t = convexTest(schema, modules);
    const { asCustomer, variantId, userId } = await setupUserAndData(t);

    await asCustomer.mutation(api.carts.addItem, { variantId, quantity: 2 });
    const orderId = await asCustomer.mutation(api.orders.createFromCart, {
      shippingAddressSnapshot: {
        fullName: "Alice Customer",
        firstName: "Alice",
        lastName: "Customer",
        addressLine1: "123 Main St",
        city: "Springfield",
        postalCode: "62701",
        country: "US",
      },
      billingAddressSnapshot: {
        firstName: "Alice",
        lastName: "Customer",
        addressLine1: "123 Main St",
        city: "Springfield",
        postalCode: "62701",
        country: "US",
      },
      shippingCost: 500,
      discount: 0,
    });

    expect(orderId).toBeTruthy();
    const order = await asCustomer.query(api.orders.getById, { id: orderId });
    expect(order.items).toHaveLength(1);
    expect(order.items[0].quantity).toBe(2);

    const cart = await asCustomer.query(api.carts.get, {});
    expect(cart!.items).toHaveLength(0);
  });

  it("createFromCart throws when item is out of stock; cart unchanged", async () => {

    const t = convexTest(schema, modules);
    const { asCustomer, variantId, userId } = await setupUserAndData(t);

    await asCustomer.mutation(api.carts.addItem, { variantId, quantity: 10 });
    await t.run(async (ctx) => {
      await ctx.db.patch(variantId, { stockQuantity: 2 });
    });

    await expect(
      asCustomer.mutation(api.orders.createFromCart, {
        shippingAddressSnapshot: {
          fullName: "Alice",
          firstName: "Alice",
          lastName: "Customer",
          addressLine1: "123 Main St",
          city: "Springfield",
          postalCode: "62701",
          country: "US",
        },
        billingAddressSnapshot: {
          firstName: "Alice",
          lastName: "Customer",
          addressLine1: "123 Main St",
          city: "Springfield",
          postalCode: "62701",
          country: "US",
        },
        shippingCost: 0,
        discount: 0,
      }),
    ).rejects.toThrow("One or more items are out of stock");

    const cart = await asCustomer.query(api.carts.get, {});
    expect(cart!.items).toHaveLength(1);
    expect(cart!.items[0].quantity).toBe(10);
  });
});

// ─── Helper reused across cancel + statusFilter tests ────────────────────────

/**
 * Inserts a "confirmed" order + its items directly into the DB.
 * Uses t.run() to bypass the create mutation validator so we can
 * set all required schema fields precisely for cancel/statusFilter tests.
 */
async function makeConfirmedOrder(
  t: ReturnType<typeof convexTest>,
  userId: any,
  variantId: any,
  quantity = 2,
) {
  let orderId: any;
  await t.run(async (ctx) => {
    orderId = await ctx.db.insert("orders", {
      orderNumber: `ORD-TEST-${Math.random().toString(36).slice(2, 7).toUpperCase()}`,
      userId,
      email: "alice@example.com",
      status: "confirmed",
      paymentStatus: "paid",
      subtotal: 999 * quantity,
      tax: 0,
      shipping: 500,
      discount: 0,
      total: 999 * quantity + 500,
      currency: "GBP",
      shippingAddressSnapshot: {
        fullName: "Alice Smith",
        firstName: "Alice",
        lastName: "Smith",
        addressLine1: "1 Test Lane",
        city: "London",
        postalCode: "E1 1AA",
        country: "GB",
      },
      billingAddressSnapshot: {
        firstName: "Alice",
        lastName: "Smith",
        addressLine1: "1 Test Lane",
        city: "London",
        postalCode: "E1 1AA",
        country: "GB",
      },
      shippoShipmentId: "shp_test",
      shippingMethod: "Standard",
      shippingServiceCode: "std",
      carrier: "DPD",
      createdAt: Date.now(),
      updatedAt: Date.now(),
    });
    await ctx.db.insert("orderItems", {
      orderId,
      variantId,
      productName: "Ball",
      variantName: "Red Ball",
      sku: "BALL-RED-001",
      quantity,
      unitPrice: 999,
      totalPrice: 999 * quantity,
    });
  });
  return orderId;
}

describe("orders.cancel", () => {
  it("cancels a confirmed order and sets status to cancelled", async () => {
    const t = convexTest(schema, modules);
    const { asCustomer, variantId, userId } = await setupUserAndData(t);
    const orderId = await makeConfirmedOrder(t, userId, variantId);

    const result = await asCustomer.mutation(api.orders.cancel, { id: orderId });
    expect(result).toEqual({ success: true });

    const order = await asCustomer.query(api.orders.getById, { id: orderId });
    expect(order.status).toBe("cancelled");
  });

  it("restores variant stock after cancellation", async () => {
    const t = convexTest(schema, modules);
    const { asCustomer, variantId, userId } = await setupUserAndData(t);

    let stockBefore: number | undefined;
    await t.run(async (ctx) => {
      const v = await ctx.db.get(variantId);
      stockBefore = v?.stockQuantity;
    });

    const orderId = await makeConfirmedOrder(t, userId, variantId, 3);
    await asCustomer.mutation(api.orders.cancel, { id: orderId });

    await t.run(async (ctx) => {
      const v = await ctx.db.get(variantId);
      expect(v?.stockQuantity).toBe(stockBefore! + 3);
    });
  });

  it("throws when order is in processing status", async () => {
    const t = convexTest(schema, modules);
    const { asCustomer, variantId, userId } = await setupUserAndData(t);
    const orderId = await makeConfirmedOrder(t, userId, variantId);
    await t.run(async (ctx) => {
      await ctx.db.patch(orderId, { status: "processing" });
    });

    await expect(
      asCustomer.mutation(api.orders.cancel, { id: orderId }),
    ).rejects.toThrow("Order has progressed past the cancellation window");
  });

  it("throws when order is in pending status", async () => {
    const t = convexTest(schema, modules);
    const { asCustomer, variantId, userId } = await setupUserAndData(t);
    const orderId = await makeConfirmedOrder(t, userId, variantId);
    await t.run(async (ctx) => {
      await ctx.db.patch(orderId, { status: "pending" });
    });

    await expect(
      asCustomer.mutation(api.orders.cancel, { id: orderId }),
    ).rejects.toThrow("Order is still awaiting payment confirmation.");
  });

  it("throws when trying to cancel an already cancelled order", async () => {
    const t = convexTest(schema, modules);
    const { asCustomer, variantId, userId } = await setupUserAndData(t);
    const orderId = await makeConfirmedOrder(t, userId, variantId);
    await asCustomer.mutation(api.orders.cancel, { id: orderId });

    await expect(
      asCustomer.mutation(api.orders.cancel, { id: orderId }),
    ).rejects.toThrow("Order is already cancelled.");
  });

  it("throws when cancelling another user's order", async () => {
    const t = convexTest(schema, modules);
    const { asCustomer, variantId, userId } = await setupUserAndData(t);
    const orderId = await makeConfirmedOrder(t, userId, variantId);

    const asBob = t.withIdentity({
      name: "Bob",
      email: "bob@example.com",
      subject: "clerk_bob_cancel",
    });
    await asBob.mutation(api.users.store, {});

    await expect(
      asBob.mutation(api.orders.cancel, { id: orderId }),
    ).rejects.toThrow("Unauthorized: order does not belong to you");
  });

  it("throws when order id does not exist", async () => {
    const t = convexTest(schema, modules);
    const asCustomer = t.withIdentity({
      name: "Alice",
      email: "alice@example.com",
      subject: "clerk_alice_ghost",
    });
    await asCustomer.mutation(api.users.store, {});

    // Use a valid-looking but non-existent order id by creating+deleting an order
    const { variantId, userId } = await setupUserAndData(t);
    const orderId = await makeConfirmedOrder(t, userId, variantId);
    await t.run(async (ctx) => {
      await ctx.db.delete(orderId);
    });

    await expect(
      asCustomer.mutation(api.orders.cancel, { id: orderId }),
    ).rejects.toThrow("Order not found");
  });
});

describe("orders.listMine with statusFilter", () => {
  it("returns all orders when no statusFilter is provided", async () => {
    const t = convexTest(schema, modules);
    const { asCustomer, variantId, userId } = await setupUserAndData(t);

    await makeConfirmedOrder(t, userId, variantId);
    // Create a second order in default "pending" status via t.run
    await t.run(async (ctx) => {
      await ctx.db.insert("orders", {
        orderNumber: `ORD-PENDING-${Date.now()}`,
        userId,
        email: "alice@example.com",
        status: "pending",
        paymentStatus: "pending",
        subtotal: 999,
        tax: 0,
        shipping: 0,
        discount: 0,
        total: 999,
        currency: "GBP",
        shippingAddressSnapshot: {
          fullName: "Alice Smith",
          firstName: "Alice",
          lastName: "Smith",
          addressLine1: "1 Test Lane",
          city: "London",
          postalCode: "E1 1AA",
          country: "GB",
        },
        billingAddressSnapshot: {
          firstName: "Alice",
          lastName: "Smith",
          addressLine1: "1 Test Lane",
          city: "London",
          postalCode: "E1 1AA",
          country: "GB",
        },
        shippoShipmentId: "",
        shippingMethod: "",
        shippingServiceCode: "",
        carrier: "",
        createdAt: Date.now(),
        updatedAt: Date.now(),
      });
    });

    const result = await asCustomer.query(api.orders.listMine, {
      paginationOpts: { numItems: 10, cursor: null },
    });
    expect(result.page).toHaveLength(2);
  });

  it("filters to a single status when statusFilter contains one value", async () => {
    const t = convexTest(schema, modules);
    const { asCustomer, variantId, userId } = await setupUserAndData(t);

    // "confirmed" order
    await makeConfirmedOrder(t, userId, variantId);
    // "pending" order via t.run
    await t.run(async (ctx) => {
      await ctx.db.insert("orders", {
        orderNumber: `ORD-PENDING-${Date.now()}`,
        userId,
        email: "alice@example.com",
        status: "pending",
        paymentStatus: "pending",
        subtotal: 999,
        tax: 0,
        shipping: 0,
        discount: 0,
        total: 999,
        currency: "GBP",
        shippingAddressSnapshot: {
          fullName: "Alice Smith",
          firstName: "Alice",
          lastName: "Smith",
          addressLine1: "1 Test Lane",
          city: "London",
          postalCode: "E1 1AA",
          country: "GB",
        },
        billingAddressSnapshot: {
          firstName: "Alice",
          lastName: "Smith",
          addressLine1: "1 Test Lane",
          city: "London",
          postalCode: "E1 1AA",
          country: "GB",
        },
        shippoShipmentId: "",
        shippingMethod: "",
        shippingServiceCode: "",
        carrier: "",
        createdAt: Date.now(),
        updatedAt: Date.now(),
      });
    });

    const result = await asCustomer.query(api.orders.listMine, {
      paginationOpts: { numItems: 10, cursor: null },
      statusFilter: ["confirmed"],
    });

    expect(result.page).toHaveLength(1);
    expect(result.page[0].status).toBe("confirmed");
  });

  it("filters to multiple statuses when statusFilter contains several values", async () => {
    const t = convexTest(schema, modules);
    const { asCustomer, variantId, userId } = await setupUserAndData(t);

    // confirmed order
    await makeConfirmedOrder(t, userId, variantId);

    // processing order
    const orderId2 = await makeConfirmedOrder(t, userId, variantId);
    await t.run(async (ctx) => {
      await ctx.db.patch(orderId2, { status: "processing" });
    });

    // delivered order
    const orderId3 = await makeConfirmedOrder(t, userId, variantId);
    await t.run(async (ctx) => {
      await ctx.db.patch(orderId3, { status: "delivered" });
    });

    const result = await asCustomer.query(api.orders.listMine, {
      paginationOpts: { numItems: 10, cursor: null },
      statusFilter: ["confirmed", "processing"],
    });

    expect(result.page).toHaveLength(2);
    expect(result.page.map((o: any) => o.status).sort()).toEqual([
      "confirmed",
      "processing",
    ]);
  });

  it("returns empty page when no orders match the statusFilter", async () => {
    const t = convexTest(schema, modules);
    const { asCustomer, variantId, userId } = await setupUserAndData(t);
    await makeConfirmedOrder(t, userId, variantId);

    const result = await asCustomer.query(api.orders.listMine, {
      paginationOpts: { numItems: 10, cursor: null },
      statusFilter: ["delivered"],
    });

    expect(result.page).toHaveLength(0);
  });
});
