/**
 * @vitest-environment happy-dom
 */
import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { useAddressMutations } from "./useAddressMutations";
import type { AddressFormData } from "./types";

const mockAddFn = vi.fn();
const mockUpdateFn = vi.fn();
const mockSetDefaultFn = vi.fn();

vi.mock("convex/react", () => ({
  useMutation: (ref: string) => {
    if (ref === "mock-add-ref") return mockAddFn;
    if (ref === "mock-update-ref") return mockUpdateFn;
    if (ref === "mock-setDefault-ref") return mockSetDefaultFn;
    return vi.fn();
  },
}));

vi.mock("../../../../../convex/_generated/api", () => ({
  api: {
    addresses: {
      add: "mock-add-ref",
      update: "mock-update-ref",
      setDefault: "mock-setDefault-ref",
    },
  },
}));

const sampleFormData: AddressFormData = {
  firstName: "Alice",
  lastName: "Smith",
  phone: "7911123456",
  addressLine1: "10 Downing Street",
  additionalInformation: "Flat 4B",
  city: "London",
  postalCode: "SW1A 2AA",
  country: "GB",
};

describe("useAddressMutations", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe("addAddress", () => {
    it("calls the add mutation with UK-shaped fields and returns the new id", async () => {
      mockAddFn.mockResolvedValue("new_addr_id");
      const { result } = renderHook(() => useAddressMutations());

      let id: string | undefined;
      await act(async () => {
        id = await result.current.addAddress({
          ...sampleFormData,
          isValidated: true,
        });
      });

      expect(id).toBe("new_addr_id");
      expect(mockAddFn).toHaveBeenCalledTimes(1);
      expect(mockAddFn).toHaveBeenCalledWith({
        type: "shipping",
        firstName: "Alice",
        lastName: "Smith",
        phone: "7911123456",
        addressLine1: "10 Downing Street",
        additionalInformation: "Flat 4B",
        city: "London",
        postalCode: "SW1A 2AA",
        country: "GB",
        isDefault: false,
        isValidated: true,
      });
    });

    it("does not send fullName, state, or addressLine2", async () => {
      mockAddFn.mockResolvedValue("addr_id");
      const { result } = renderHook(() => useAddressMutations());

      await act(async () => {
        await result.current.addAddress({
          ...sampleFormData,
          isValidated: true,
        });
      });

      const callArgs = mockAddFn.mock.calls[0][0];
      expect(callArgs).not.toHaveProperty("fullName");
      expect(callArgs).not.toHaveProperty("state");
      expect(callArgs).not.toHaveProperty("addressLine2");
    });

    it("passes isValidated: false for unverified addresses", async () => {
      mockAddFn.mockResolvedValue("addr_unverified");
      const { result } = renderHook(() => useAddressMutations());

      await act(async () => {
        await result.current.addAddress({
          ...sampleFormData,
          isValidated: false,
        });
      });

      expect(mockAddFn).toHaveBeenCalledWith(
        expect.objectContaining({ isValidated: false }),
      );
    });

    it("always sets type to 'shipping' and isDefault to false", async () => {
      mockAddFn.mockResolvedValue("addr_id");
      const { result } = renderHook(() => useAddressMutations());

      await act(async () => {
        await result.current.addAddress({
          ...sampleFormData,
          isValidated: true,
        });
      });

      expect(mockAddFn).toHaveBeenCalledWith(
        expect.objectContaining({ type: "shipping", isDefault: false }),
      );
    });
  });

  describe("updateAddress", () => {
    it("calls the update mutation with id and partial UK fields", async () => {
      mockUpdateFn.mockResolvedValue("addr_123");
      const { result } = renderHook(() => useAddressMutations());

      await act(async () => {
        await result.current.updateAddress("addr_123", {
          city: "Manchester",
          postalCode: "M1 1AA",
          isValidated: false,
        });
      });

      expect(mockUpdateFn).toHaveBeenCalledTimes(1);
      const callArgs = mockUpdateFn.mock.calls[0][0];
      expect(callArgs.id).toBe("addr_123");
      expect(callArgs.city).toBe("Manchester");
      expect(callArgs.postalCode).toBe("M1 1AA");
      expect(callArgs.isValidated).toBe(false);
    });

    it("passes additionalInformation when provided", async () => {
      mockUpdateFn.mockResolvedValue("addr_123");
      const { result } = renderHook(() => useAddressMutations());

      await act(async () => {
        await result.current.updateAddress("addr_123", {
          additionalInformation: "Suite 300",
        });
      });

      const callArgs = mockUpdateFn.mock.calls[0][0];
      expect(callArgs.additionalInformation).toBe("Suite 300");
    });

    it("does not include undefined fields in the mutation call", async () => {
      mockUpdateFn.mockResolvedValue("addr_123");
      const { result } = renderHook(() => useAddressMutations());

      await act(async () => {
        await result.current.updateAddress("addr_123", {
          postalCode: "EC1A 1BB",
        });
      });

      const callArgs = mockUpdateFn.mock.calls[0][0];
      expect(callArgs.id).toBe("addr_123");
      expect(callArgs.postalCode).toBe("EC1A 1BB");
      expect(callArgs).not.toHaveProperty("firstName");
      expect(callArgs).not.toHaveProperty("city");
      expect(callArgs).not.toHaveProperty("state");
      expect(callArgs).not.toHaveProperty("addressLine2");
    });
  });

  describe("setDefault", () => {
    it("calls the setDefault mutation with the address id", async () => {
      mockSetDefaultFn.mockResolvedValue(undefined);
      const { result } = renderHook(() => useAddressMutations());

      await act(async () => {
        await result.current.setDefault("addr_456");
      });

      expect(mockSetDefaultFn).toHaveBeenCalledTimes(1);
      expect(mockSetDefaultFn).toHaveBeenCalledWith({ id: "addr_456" });
    });
  });
});
