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

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

const identity = {
  name: "Alice",
  email: "alice@example.com",
  subject: "clerk_alice_123",
};

const baseAddress = {
  firstName: "Alice",
  lastName: "Smith",
  phone: "+447911123456",
  addressLine1: "10 Downing Street",
  city: "London",
  postalCode: "SW1A 2AA",
  country: "GB",
};

describe("checkout.getShippingAddresses", () => {
  it("returns only shipping addresses (excludes billing)", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity(identity);
    await asA.mutation(api.users.store, {});

    await asA.mutation(api.addresses.add, {
      ...baseAddress,
      type: "shipping",
      isDefault: true,
    });
    await asA.mutation(api.addresses.add, {
      ...baseAddress,
      type: "billing",
      addressLine1: "1 Treasury Place",
      isDefault: false,
    });
    await asA.mutation(api.addresses.add, {
      ...baseAddress,
      type: "shipping",
      addressLine1: "221B Baker Street",
      isDefault: false,
    });

    const result = await asA.query(api.checkout.getShippingAddresses, {});
    expect(result).toHaveLength(2);
    expect(result.every((a) => a.type === "shipping")).toBe(true);
  });

  it("sorts default address first, then by recency descending", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity(identity);
    await asA.mutation(api.users.store, {});

    await asA.mutation(api.addresses.add, {
      ...baseAddress,
      type: "shipping",
      addressLine1: "First added",
      isDefault: false,
    });
    await asA.mutation(api.addresses.add, {
      ...baseAddress,
      type: "shipping",
      addressLine1: "Second added (default)",
      isDefault: true,
    });
    await asA.mutation(api.addresses.add, {
      ...baseAddress,
      type: "shipping",
      addressLine1: "Third added",
      isDefault: false,
    });

    const result = await asA.query(api.checkout.getShippingAddresses, {});
    expect(result).toHaveLength(3);
    expect(result[0].addressLine1).toBe("Second added (default)");
    expect(result[0].isDefault).toBe(true);
    expect(result[1].addressLine1).toBe("Third added");
    expect(result[2].addressLine1).toBe("First added");
  });

  it("returns empty array when user has no shipping addresses", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity(identity);
    await asA.mutation(api.users.store, {});

    const result = await asA.query(api.checkout.getShippingAddresses, {});
    expect(result).toEqual([]);
  });

  it("returns empty array when user only has billing addresses", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity(identity);
    await asA.mutation(api.users.store, {});

    await asA.mutation(api.addresses.add, {
      ...baseAddress,
      type: "billing",
      isDefault: true,
    });

    const result = await asA.query(api.checkout.getShippingAddresses, {});
    expect(result).toEqual([]);
  });

  it("includes isValidated field on returned addresses", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity(identity);
    await asA.mutation(api.users.store, {});

    await asA.mutation(api.addresses.add, {
      ...baseAddress,
      type: "shipping",
      isDefault: true,
      isValidated: true,
    });
    await asA.mutation(api.addresses.add, {
      ...baseAddress,
      type: "shipping",
      addressLine1: "221B Baker Street",
      isDefault: false,
    });

    const result = await asA.query(api.checkout.getShippingAddresses, {});
    expect(result).toHaveLength(2);
    expect(result[0].isValidated).toBe(true);
    expect(result[1].isValidated).toBe(false);
  });

  it("includes additionalInformation when present", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity(identity);
    await asA.mutation(api.users.store, {});

    await asA.mutation(api.addresses.add, {
      ...baseAddress,
      type: "shipping",
      additionalInformation: "Flat 4B",
      isDefault: true,
    });

    const result = await asA.query(api.checkout.getShippingAddresses, {});
    expect(result).toHaveLength(1);
    expect(result[0].additionalInformation).toBe("Flat 4B");
  });

  it("returned addresses have no state or addressLine2 fields", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity(identity);
    await asA.mutation(api.users.store, {});

    await asA.mutation(api.addresses.add, {
      ...baseAddress,
      type: "shipping",
      isDefault: true,
    });

    const result = await asA.query(api.checkout.getShippingAddresses, {});
    const addr = result[0] as Record<string, unknown>;
    expect(addr).not.toHaveProperty("state");
    expect(addr).not.toHaveProperty("addressLine2");
  });

  it("throws for unauthenticated users", async () => {
    const t = convexTest(schema, modules);
    await expect(
      t.query(api.checkout.getShippingAddresses, {}),
    ).rejects.toThrow(/Unauthenticated/i);
  });

  it("does not return addresses belonging to other users", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity(identity);
    const asB = t.withIdentity({
      name: "Bob",
      email: "bob@example.com",
      subject: "clerk_bob_456",
    });
    await asA.mutation(api.users.store, {});
    await asB.mutation(api.users.store, {});

    await asA.mutation(api.addresses.add, {
      ...baseAddress,
      type: "shipping",
      isDefault: true,
    });
    await asB.mutation(api.addresses.add, {
      ...baseAddress,
      type: "shipping",
      addressLine1: "Bob's address",
      isDefault: true,
    });

    const resultA = await asA.query(api.checkout.getShippingAddresses, {});
    expect(resultA).toHaveLength(1);
    expect(resultA[0].addressLine1).toBe("10 Downing Street");

    const resultB = await asB.query(api.checkout.getShippingAddresses, {});
    expect(resultB).toHaveLength(1);
    expect(resultB[0].addressLine1).toBe("Bob's address");
  });
});

// ─── getAddressById (internal query) ─────────────────────────────────────────

describe("checkout.getAddressById", () => {
  it("returns the address document when it exists", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity(identity);
    await asA.mutation(api.users.store, {});

    const addressId = await asA.mutation(api.addresses.add, {
      ...baseAddress,
      type: "shipping",
      isDefault: true,
    });

    const result = await t.query(internal.checkout.getAddressById, {
      addressId,
    });
    expect(result.addressLine1).toBe("10 Downing Street");
    expect(result.fullName).toBe("Alice Smith");
    expect(result.city).toBe("London");
    expect(result.postalCode).toBe("SW1A 2AA");
    expect(result.country).toBe("GB");
  });

  it("throws when the address does not exist", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity(identity);
    await asA.mutation(api.users.store, {});

    const addressId = await asA.mutation(api.addresses.add, {
      ...baseAddress,
      type: "shipping",
      isDefault: true,
    });
    await asA.mutation(api.addresses.remove, { id: addressId });

    await expect(
      t.query(internal.checkout.getAddressById, { addressId }),
    ).rejects.toThrow(/Address not found/i);
  });

  it("returns additionalInformation when present on address", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity(identity);
    await asA.mutation(api.users.store, {});

    const addressId = await asA.mutation(api.addresses.add, {
      ...baseAddress,
      type: "shipping",
      additionalInformation: "Flat 4B",
      isDefault: true,
    });

    const result = await t.query(internal.checkout.getAddressById, {
      addressId,
    });
    expect(result.additionalInformation).toBe("Flat 4B");
  });
});

// ─── getCurrentUserId (internal query) ───────────────────────────────────────

describe("checkout.getCurrentUserId", () => {
  it("returns the user ID for an authenticated user", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity(identity);
    const userId = await asA.mutation(api.users.store, {});

    const result = await asA.query(internal.checkout.getCurrentUserId);
    expect(result).toBe(userId);
  });

  it("throws for unauthenticated requests", async () => {
    const t = convexTest(schema, modules);
    await expect(
      t.query(internal.checkout.getCurrentUserId),
    ).rejects.toThrow(/Unauthenticated/i);
  });
});

// ─── validateCartInternal (internal query) ───────────────────────────────────

describe("checkout.validateCartInternal", () => {
  async function setupProductAndVariant(
    t: ReturnType<typeof convexTest>,
    overrides?: { stockQuantity?: number; price?: number; isActive?: boolean },
  ) {
    let productId: Id<"products">;
    let variantId: Id<"productVariants">;
    let categoryId: Id<"categories">;

    await t.run(async (ctx) => {
      categoryId = await ctx.db.insert("categories", {
        name: "Dog Food",
        slug: "dog-food",
      });
      productId = await ctx.db.insert("products", {
        name: "Premium Kibble",
        slug: "premium-kibble",
        status: "active",
        categoryId,
        tags: [],
        parentCategorySlug: "dogs",
        childCategorySlug: "dog-food",
      });
      variantId = await ctx.db.insert("productVariants", {
        productId,
        name: "1kg Bag",
        sku: "PK-001",
        price: overrides?.price ?? 2499,
        stockQuantity: overrides?.stockQuantity ?? 50,
        isActive: overrides?.isActive ?? true,
        weight: 1000,
        weightUnit: "g",
        length: 30,
        width: 20,
        height: 10,
        dimensionUnit: "cm",
      });
      await ctx.db.insert("productImages", {
        productId,
        url: "https://example.com/kibble.jpg",
        position: 0,
      });
    });

    return { productId: productId!, variantId: variantId!, categoryId: categoryId! };
  }

  it("returns null when the user has no cart", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity(identity);
    const userId = await asA.mutation(api.users.store, {});

    const result = await t.query(internal.checkout.validateCartInternal, {
      userId,
    });
    expect(result).toBeNull();
  });

  it("returns null when the cart has no items", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity(identity);
    const userId = await asA.mutation(api.users.store, {});

    await t.run(async (ctx) => {
      await ctx.db.insert("carts", {
        userId,
        items: [],
        createdAt: Date.now(),
        updatedAt: Date.now(),
        expiresAt: Date.now() + 86400000,
      });
    });

    const result = await t.query(internal.checkout.validateCartInternal, {
      userId,
    });
    expect(result).toBeNull();
  });

  it("returns a valid cart validation result with enriched items", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity(identity);
    const userId = await asA.mutation(api.users.store, {});
    const { productId, variantId } = await setupProductAndVariant(t);

    await t.run(async (ctx) => {
      await ctx.db.insert("carts", {
        userId,
        items: [{ productId, variantId, quantity: 2, price: 2499 }],
        createdAt: Date.now(),
        updatedAt: Date.now(),
        expiresAt: Date.now() + 86400000,
      });
    });

    const result = await t.query(internal.checkout.validateCartInternal, {
      userId,
    });
    expect(result).not.toBeNull();
    expect(result!.valid).toBe(true);
    expect(result!.items).toHaveLength(1);
    expect(result!.items[0].productName).toBe("Premium Kibble");
    expect(result!.items[0].variantName).toBe("1kg Bag");
    expect(result!.items[0].quantity).toBe(2);
    expect(result!.items[0].weight).toBe(1000);
    expect(result!.items[0].weightUnit).toBe("g");
    expect(result!.subtotal).toBe(2499 * 2);
  });

  it("returns issues for out-of-stock items", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity(identity);
    const userId = await asA.mutation(api.users.store, {});
    const { productId, variantId } = await setupProductAndVariant(t, {
      stockQuantity: 0,
    });

    await t.run(async (ctx) => {
      await ctx.db.insert("carts", {
        userId,
        items: [{ productId, variantId, quantity: 1, price: 2499 }],
        createdAt: Date.now(),
        updatedAt: Date.now(),
        expiresAt: Date.now() + 86400000,
      });
    });

    const result = await t.query(internal.checkout.validateCartInternal, {
      userId,
    });
    expect(result).not.toBeNull();
    expect(result!.valid).toBe(false);
    expect(result!.issues).toHaveLength(1);
    expect(result!.issues[0].type).toBe("out_of_stock");
  });

  it("resolves cart by sessionId when userId not provided", async () => {
    const t = convexTest(schema, modules);
    const { productId, variantId } = await setupProductAndVariant(t);

    await t.run(async (ctx) => {
      await ctx.db.insert("carts", {
        sessionId: "sess_abc123",
        items: [{ productId, variantId, quantity: 1, price: 2499 }],
        createdAt: Date.now(),
        updatedAt: Date.now(),
        expiresAt: Date.now() + 86400000,
      });
    });

    const result = await t.query(internal.checkout.validateCartInternal, {
      sessionId: "sess_abc123",
    });
    expect(result).not.toBeNull();
    expect(result!.valid).toBe(true);
    expect(result!.items).toHaveLength(1);
  });
});
