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

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

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

describe("addresses", () => {
  it("add sets default correctly and second add unsets previous default", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity({
      name: "Alice",
      email: "alice@example.com",
      subject: "clerk_alice_123",
    });
    await asA.mutation(api.users.store, {});

    await asA.mutation(api.addresses.add, {
      ...shippingAddress,
      isDefault: true,
    });
    const list1 = await asA.query(api.addresses.list, {});
    expect(list1).toHaveLength(1);
    expect(list1[0].isDefault).toBe(true);

    await asA.mutation(api.addresses.add, {
      ...shippingAddress,
      addressLine1: "221B Baker Street",
      isDefault: true,
    });
    const list2 = await asA.query(api.addresses.list, {});
    expect(list2).toHaveLength(2);
    expect(list2[0].isDefault).toBe(true);
    expect(list2[1].isDefault).toBe(false);
  });

  it("delete default address promotes next to default", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity({
      name: "Alice",
      email: "alice@example.com",
      subject: "clerk_alice_123",
    });
    await asA.mutation(api.users.store, {});

    await asA.mutation(api.addresses.add, {
      ...shippingAddress,
      isDefault: true,
    });
    await asA.mutation(api.addresses.add, {
      ...shippingAddress,
      addressLine1: "221B Baker Street",
      isDefault: false,
    });
    const listBefore = await asA.query(api.addresses.list, {});
    expect(listBefore).toHaveLength(2);
    const defaultId = listBefore[0]._id;

    await asA.mutation(api.addresses.remove, { id: defaultId });

    const listAfter = await asA.query(api.addresses.list, {});
    expect(listAfter).toHaveLength(1);
    expect(listAfter[0].isDefault).toBe(true);
  });

  it("add derives fullName from firstName + lastName", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity({
      name: "Alice",
      email: "alice@example.com",
      subject: "clerk_alice_123",
    });
    await asA.mutation(api.users.store, {});

    await asA.mutation(api.addresses.add, {
      ...shippingAddress,
      isDefault: true,
    });
    const list = await asA.query(api.addresses.list, {});
    expect(list[0].fullName).toBe("Alice Smith");
  });

  it("add persists additionalInformation when provided", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity({
      name: "Alice",
      email: "alice@example.com",
      subject: "clerk_alice_123",
    });
    await asA.mutation(api.users.store, {});

    await asA.mutation(api.addresses.add, {
      ...shippingAddress,
      additionalInformation: "Flat 4B",
      isDefault: true,
    });
    const list = await asA.query(api.addresses.list, {});
    expect(list[0].additionalInformation).toBe("Flat 4B");
  });

  it("add stores undefined for additionalInformation when not provided", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity({
      name: "Alice",
      email: "alice@example.com",
      subject: "clerk_alice_123",
    });
    await asA.mutation(api.users.store, {});

    await asA.mutation(api.addresses.add, {
      ...shippingAddress,
      isDefault: true,
    });
    const list = await asA.query(api.addresses.list, {});
    expect(list[0].additionalInformation).toBeUndefined();
  });

  it("address records do not have state or addressLine2 fields", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity({
      name: "Alice",
      email: "alice@example.com",
      subject: "clerk_alice_123",
    });
    await asA.mutation(api.users.store, {});

    await asA.mutation(api.addresses.add, {
      ...shippingAddress,
      isDefault: true,
    });
    const list = await asA.query(api.addresses.list, {});
    const addr = list[0] as Record<string, unknown>;
    expect(addr).not.toHaveProperty("state");
    expect(addr).not.toHaveProperty("addressLine2");
  });

  it("firstName and lastName are required and stored", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity({
      name: "Alice",
      email: "alice@example.com",
      subject: "clerk_alice_123",
    });
    await asA.mutation(api.users.store, {});

    await asA.mutation(api.addresses.add, {
      ...shippingAddress,
      isDefault: true,
    });
    const list = await asA.query(api.addresses.list, {});
    expect(list[0].firstName).toBe("Alice");
    expect(list[0].lastName).toBe("Smith");
  });

  it("isValidated defaults to false when passed via add mutation", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity({
      name: "Alice",
      email: "alice@example.com",
      subject: "clerk_alice_123",
    });
    await asA.mutation(api.users.store, {});

    await asA.mutation(api.addresses.add, {
      ...shippingAddress,
      isDefault: true,
    });
    const list = await asA.query(api.addresses.list, {});
    expect(list).toHaveLength(1);
    expect(list[0].isValidated).toBe(false);
  });

  it("add mutation persists isValidated: true when provided", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity({
      name: "Alice",
      email: "alice@example.com",
      subject: "clerk_alice_123",
    });
    await asA.mutation(api.users.store, {});

    await asA.mutation(api.addresses.add, {
      ...shippingAddress,
      isDefault: true,
      isValidated: true,
    });
    const list = await asA.query(api.addresses.list, {});
    expect(list[0].isValidated).toBe(true);
  });

  it("update mutation patches isValidated", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity({
      name: "Alice",
      email: "alice@example.com",
      subject: "clerk_alice_123",
    });
    await asA.mutation(api.users.store, {});

    const id = await asA.mutation(api.addresses.add, {
      ...shippingAddress,
      isDefault: true,
    });

    await asA.mutation(api.addresses.update, { id, isValidated: true });
    const list1 = await asA.query(api.addresses.list, {});
    expect(list1[0].isValidated).toBe(true);

    await asA.mutation(api.addresses.update, { id, isValidated: false });
    const list2 = await asA.query(api.addresses.list, {});
    expect(list2[0].isValidated).toBe(false);
  });

  it("update mutation patches additionalInformation", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity({
      name: "Alice",
      email: "alice@example.com",
      subject: "clerk_alice_123",
    });
    await asA.mutation(api.users.store, {});

    const id = await asA.mutation(api.addresses.add, {
      ...shippingAddress,
      isDefault: true,
    });

    await asA.mutation(api.addresses.update, { id, additionalInformation: "Floor 3" });
    const list = await asA.query(api.addresses.list, {});
    expect(list[0].additionalInformation).toBe("Floor 3");
  });

  it("markValidated sets isValidated to true", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity({
      name: "Alice",
      email: "alice@example.com",
      subject: "clerk_alice_123",
    });
    await asA.mutation(api.users.store, {});

    const id = await asA.mutation(api.addresses.add, {
      ...shippingAddress,
      isDefault: true,
    });
    expect((await asA.query(api.addresses.list, {}))[0].isValidated).toBe(false);

    await asA.mutation(api.addresses.markValidated, { id, isValidated: true });
    const list = await asA.query(api.addresses.list, {});
    expect(list[0].isValidated).toBe(true);
  });

  it("markValidated throws if user does not own address", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity({
      name: "Alice",
      email: "alice@example.com",
      subject: "clerk_alice_123",
    });
    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, {});

    const id = await asA.mutation(api.addresses.add, {
      ...shippingAddress,
      isDefault: true,
    });

    await expect(
      asB.mutation(api.addresses.markValidated, { id, isValidated: true }),
    ).rejects.toThrow(/Unauthorized|does not belong/);
  });

  it("markValidated throws for non-existent address", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity({
      name: "Alice",
      email: "alice@example.com",
      subject: "clerk_alice_123",
    });
    await asA.mutation(api.users.store, {});

    const fakeId = await asA.mutation(api.addresses.add, {
      ...shippingAddress,
      isDefault: true,
    });
    await asA.mutation(api.addresses.remove, { id: fakeId });

    await expect(
      asA.mutation(api.addresses.markValidated, { id: fakeId, isValidated: true }),
    ).rejects.toThrow(/not found/i);
  });

  it("update throws if user does not own the address", async () => {
    const t = convexTest(schema, modules);
    const asA = t.withIdentity({
      name: "Alice",
      email: "alice@example.com",
      subject: "clerk_alice_123",
    });
    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, {});

    const id = await asA.mutation(api.addresses.add, {
      ...shippingAddress,
      isDefault: true,
    });

    await expect(
      asB.mutation(api.addresses.update, { id, fullName: "Hacker" }),
    ).rejects.toThrow(/Unauthorized|does not belong/);

    await asA.mutation(api.addresses.update, { id, fullName: "Alice Updated" });
    const list = await asA.query(api.addresses.list, {});
    expect(list[0].fullName).toBe("Alice Updated");
  });
});
