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

const mockCancelFn = vi.fn();
const mockAddItemFn = vi.fn();
const mockRouterPush = vi.fn();

vi.mock("convex/react", () => ({
  useMutation: (ref: string) => {
    if (ref === "mock-cancel-ref") return mockCancelFn;
    if (ref === "mock-addItem-ref") return mockAddItemFn;
    return vi.fn();
  },
}));

vi.mock("../../../../../convex/_generated/api", () => ({
  api: {
    orders: { cancel: "mock-cancel-ref" },
    carts: { addItem: "mock-addItem-ref" },
  },
}));

vi.mock("next/navigation", () => ({
  useRouter: () => ({ push: mockRouterPush }),
}));

const sampleItems = [
  {
    _id: "item_1",
    variantId: "var_1",
    productName: "Dog Toy",
    variantName: "Red",
    sku: "TOY-RED-001",
    quantity: 2,
    unitPrice: 999,
    totalPrice: 1998,
  },
  {
    _id: "item_2",
    variantId: "var_2",
    productName: "Cat Treat",
    variantName: "Tuna",
    sku: "TREAT-TUN-001",
    quantity: 1,
    unitPrice: 499,
    totalPrice: 499,
  },
];

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

  // ── cancelOrder ────────────────────────────────────────────────────────────

  describe("cancelOrder", () => {
    it("calls the cancel mutation with the correct order id", async () => {
      mockCancelFn.mockResolvedValue({ success: true });
      const { result } = renderHook(() => useOrderActions());

      await act(async () => {
        await result.current.cancelOrder("order_abc");
      });

      expect(mockCancelFn).toHaveBeenCalledOnce();
      expect(mockCancelFn).toHaveBeenCalledWith({ id: "order_abc" });
    });

    it("returns { success: true } when the mutation resolves", async () => {
      mockCancelFn.mockResolvedValue({ success: true });
      const { result } = renderHook(() => useOrderActions());

      let outcome: { success: boolean } | undefined;
      await act(async () => {
        outcome = await result.current.cancelOrder("order_abc");
      });

      expect(outcome).toEqual({ success: true });
    });

    it("returns { success: false } when the mutation throws", async () => {
      mockCancelFn.mockRejectedValue(
        new Error("Order has already been cancelled."),
      );
      const { result } = renderHook(() => useOrderActions());

      let outcome: { success: boolean } | undefined;
      await act(async () => {
        outcome = await result.current.cancelOrder("order_abc");
      });

      expect(outcome).toEqual({ success: false });
    });

    it("isCancelling is false before and after a successful call", async () => {
      mockCancelFn.mockResolvedValue({ success: true });
      const { result } = renderHook(() => useOrderActions());

      expect(result.current.isCancelling).toBe(false);

      await act(async () => {
        await result.current.cancelOrder("order_abc");
      });

      expect(result.current.isCancelling).toBe(false);
    });

    it("isCancelling is false after a failed call (finally block)", async () => {
      mockCancelFn.mockRejectedValue(new Error("fail"));
      const { result } = renderHook(() => useOrderActions());

      await act(async () => {
        await result.current.cancelOrder("order_abc");
      });

      expect(result.current.isCancelling).toBe(false);
    });

    it("does not call addItem or navigate when cancelling", async () => {
      mockCancelFn.mockResolvedValue({ success: true });
      const { result } = renderHook(() => useOrderActions());

      await act(async () => {
        await result.current.cancelOrder("order_abc");
      });

      expect(mockAddItemFn).not.toHaveBeenCalled();
      expect(mockRouterPush).not.toHaveBeenCalled();
    });
  });

  // ── reorderItems ───────────────────────────────────────────────────────────

  describe("reorderItems", () => {
    it("calls addItem for every item in the order", async () => {
      mockAddItemFn.mockResolvedValue(undefined);
      const { result } = renderHook(() => useOrderActions());

      await act(async () => {
        await result.current.reorderItems(sampleItems, "session_123");
      });

      expect(mockAddItemFn).toHaveBeenCalledTimes(2);
      expect(mockAddItemFn).toHaveBeenCalledWith({
        variantId: "var_1",
        quantity: 2,
        sessionId: "session_123",
      });
      expect(mockAddItemFn).toHaveBeenCalledWith({
        variantId: "var_2",
        quantity: 1,
        sessionId: "session_123",
      });
    });

    it("navigates to /cart after all items are processed", async () => {
      mockAddItemFn.mockResolvedValue(undefined);
      const { result } = renderHook(() => useOrderActions());

      await act(async () => {
        await result.current.reorderItems(sampleItems);
      });

      expect(mockRouterPush).toHaveBeenCalledOnce();
      expect(mockRouterPush).toHaveBeenCalledWith("/cart");
    });

    it("passes sessionId as undefined when not provided", async () => {
      mockAddItemFn.mockResolvedValue(undefined);
      const { result } = renderHook(() => useOrderActions());

      await act(async () => {
        await result.current.reorderItems([sampleItems[0]]);
      });

      expect(mockAddItemFn).toHaveBeenCalledWith({
        variantId: "var_1",
        quantity: 2,
        sessionId: undefined,
      });
    });

    it("skips items that fail and continues adding the rest", async () => {
      mockAddItemFn
        .mockRejectedValueOnce(new Error("Variant no longer active"))
        .mockResolvedValueOnce(undefined);

      const { result } = renderHook(() => useOrderActions());

      await act(async () => {
        await result.current.reorderItems(sampleItems, "session_abc");
      });

      // Both items attempted — one failed silently
      expect(mockAddItemFn).toHaveBeenCalledTimes(2);
      // Still navigates to cart even with a partial failure
      expect(mockRouterPush).toHaveBeenCalledWith("/cart");
    });

    it("still navigates to cart even if all items fail", async () => {
      mockAddItemFn.mockRejectedValue(new Error("Out of stock"));
      const { result } = renderHook(() => useOrderActions());

      await act(async () => {
        await result.current.reorderItems(sampleItems);
      });

      expect(mockRouterPush).toHaveBeenCalledWith("/cart");
    });

    it("isReordering is false before and after a successful call", async () => {
      mockAddItemFn.mockResolvedValue(undefined);
      const { result } = renderHook(() => useOrderActions());

      expect(result.current.isReordering).toBe(false);

      await act(async () => {
        await result.current.reorderItems(sampleItems);
      });

      expect(result.current.isReordering).toBe(false);
    });

    it("handles an empty items array gracefully", async () => {
      const { result } = renderHook(() => useOrderActions());

      await act(async () => {
        await result.current.reorderItems([]);
      });

      expect(mockAddItemFn).not.toHaveBeenCalled();
      expect(mockRouterPush).toHaveBeenCalledWith("/cart");
    });

    it("does not call the cancel mutation when reordering", async () => {
      mockAddItemFn.mockResolvedValue(undefined);
      const { result } = renderHook(() => useOrderActions());

      await act(async () => {
        await result.current.reorderItems(sampleItems);
      });

      expect(mockCancelFn).not.toHaveBeenCalled();
    });
  });
});
