/**
 * @vitest-environment jsdom
 */
import { cleanup, render, screen, waitFor } from "@testing-library/react";
import { getPromoRuleList } from "@workspace/ui/services/admin/promo";
import type { AxiosResponse } from "axios";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import PromoPage from ".";

vi.mock("react-i18next", () => ({
  useTranslation: () => ({
    t: (_key: string, fallback: string): string => fallback,
  }),
}));

vi.mock("@/stores/subscribe", () => ({
  useSubscribe: () => ({
    subscribes: [
      {
        id: 8,
        name: "Monthly Plan",
        unit_price: 1200,
        sold: 0,
      },
    ],
    getSubscribeName: (id?: number) => (id === 8 ? "Monthly Plan" : "--"),
  }),
}));

vi.mock("@/utils/common", () => ({
  formatDate: (timestamp: number) => `date-${timestamp}`,
}));

vi.mock("@workspace/ui/services/admin/promo", () => ({
  createPromoPrice: vi.fn(),
  createPromoRule: vi.fn(),
  deletePromoPrice: vi.fn(),
  deletePromoRule: vi.fn(),
  getPromoPriceList: vi.fn(),
  getPromoRuleList: vi.fn(),
  getPromoUsageList: vi.fn(),
  updatePromoRule: vi.fn(),
}));

const mockedGetPromoRuleList = vi.mocked(getPromoRuleList);

function createRuleListResponse(
  data: API.GetPromoRuleListResponse
): AxiosResponse<API.Response & { data?: API.GetPromoRuleListResponse }> {
  return {
    data: {
      code: 200,
      data,
    },
    status: 200,
    statusText: "OK",
    headers: {},
    config: {
      headers: {} as AxiosResponse["config"]["headers"],
    },
  };
}

beforeEach(() => {
  localStorage.setItem("timezone", "UTC");
});

afterEach(() => {
  cleanup();
  vi.clearAllMocks();
});

describe("PromoPage", () => {
  it("renders promo rules with type, priority, status, and validity period", async () => {
    mockedGetPromoRuleList.mockResolvedValue(
      createRuleListResponse({
        total: 1,
        list: [
          {
            id: 1,
            name: "Return User Price",
            type: "inactive_user",
            params: { inactive_months: 3 },
            priority: 5,
            enabled: true,
            start_time: 1_716_800_000,
            end_time: 1_716_900_000,
          },
        ],
      })
    );

    render(<PromoPage />);

    await waitFor(() =>
      expect(mockedGetPromoRuleList).toHaveBeenCalledWith({
        page: 1,
        size: 200,
        search: undefined,
        type: undefined,
        enabled: undefined,
      })
    );
    expect(await screen.findByText("Return User Price")).not.toBeNull();
    expect(screen.getByText("inactive_user")).not.toBeNull();
    expect(screen.getByText("5")).not.toBeNull();
    expect(screen.getByText("date-1716800000")).not.toBeNull();
    expect(screen.getByText("date-1716900000")).not.toBeNull();
  });

  it("shows the empty state when there are no promo rules", async () => {
    mockedGetPromoRuleList.mockResolvedValue(
      createRuleListResponse({
        total: 0,
        list: [],
      })
    );

    render(<PromoPage />);

    expect(await screen.findByText("No promo rules")).not.toBeNull();
  });

  it("shows a loading state while promo rules are being fetched", () => {
    let resolveRequest:
      | ((
          value: AxiosResponse<
            API.Response & { data?: API.GetPromoRuleListResponse }
          >
        ) => void)
      | undefined;
    mockedGetPromoRuleList.mockReturnValue(
      new Promise((resolve) => {
        resolveRequest = resolve;
      })
    );

    render(<PromoPage />);

    expect(screen.getByRole("status", { name: "Loading data" })).not.toBeNull();
    expect(resolveRequest).toBeTypeOf("function");
  });

  it("shows an error state when promo rules fail to load", async () => {
    mockedGetPromoRuleList.mockRejectedValue(new Error("network failed"));

    render(<PromoPage />);

    await waitFor(() => expect(mockedGetPromoRuleList).toHaveBeenCalled());
    expect(
      await screen.findByText("Failed to load promo rules")
    ).not.toBeNull();
    expect(screen.queryByText("No promo rules")).toBeNull();
  });
});
