import { describe, expect, it, vi, beforeEach } from "vitest";
import type { QuoteItem, QuoteRequest } from "@/modules/providers/quote-provider";
import { RpaError } from "@/modules/rpa/errors";
import type { RPAContext } from "@/workers/rpa/kernel/context";

const directMock = vi.fn();
const widgetMock = vi.fn();

vi.mock("@/workers/rpa/quote-capture/axel-quote-direct", () => ({
  scrapeQuotesViaDirectAxel: (...args: unknown[]) => directMock(...args),
}));

vi.mock("@/workers/rpa/quote-capture/quote-schema-validator", () => ({
  validateQuoteSchema: (items: QuoteItem[]) => items,
}));

vi.mock("@/lib/rpa/env", async (importOriginal) => {
  const actual = await importOriginal<typeof import("@/lib/rpa/env")>();
  return {
    ...actual,
    isWidgetQuoteFallbackEnabled: vi.fn(() => true),
  };
});

vi.mock("@/workers/rpa/kernel/steps/quote-step", () => ({
  scrapeQuotesStep: (...args: unknown[]) => widgetMock(...args),
}));

import { isWidgetQuoteFallbackEnabled } from "@/lib/rpa/env";

import { captureQuotesDirectFirst } from "@/workers/rpa/quote-capture/quote-strategy";

function sampleItems(): QuoteItem[] {
  return [
    {
      serviceLevel: "standard",
      rateOption: "lowest",
      carrier: "Test",
      transitDays: "3-5",
      transitDescription: "3-5 business days",
      rawFreight: 100,
      surcharges: 10,
      rawTotal: 110,
    },
  ];
}

function makeCtx(): RPAContext {
  return {
    page: {} as RPAContext["page"],
    state: {
      axelPlaceLocations: {
        pickup: { placeId: "ChIJ-pickup", description: "pickup" },
        delivery: { placeId: "ChIJ-delivery", description: "delivery" },
      },
    },
  } as RPAContext;
}

const req = {} as QuoteRequest;

describe("captureQuotesDirectFirst", () => {
  beforeEach(() => {
    directMock.mockReset();
    widgetMock.mockReset();
    vi.mocked(isWidgetQuoteFallbackEnabled).mockReturnValue(true);
  });

  it("Direct 成功时不调用 Widget", async () => {
    directMock.mockResolvedValue(sampleItems());
    const result = await captureQuotesDirectFirst(makeCtx(), req);
    expect(result.source).toBe("direct");
    expect(widgetMock).not.toHaveBeenCalled();
  });

  it("Direct 失败且允许 fallback 时才调用 Widget", async () => {
    directMock.mockRejectedValue(new RpaError("RPA_DATA_INVALID", "direct fail"));
    widgetMock.mockResolvedValue(sampleItems());
    const result = await captureQuotesDirectFirst(makeCtx(), req);
    expect(result.source).toBe("widget");
    expect(widgetMock).toHaveBeenCalledWith(
      expect.anything(),
      expect.objectContaining({ widgetFallbackAfterDirectFailure: true }),
    );
  });

  it("关闭 Widget fallback 时 Direct 失败直接抛错", async () => {
    vi.mocked(isWidgetQuoteFallbackEnabled).mockReturnValue(false);
    directMock.mockRejectedValue(new RpaError("RPA_DATA_INVALID", "direct fail"));
    await expect(captureQuotesDirectFirst(makeCtx(), req)).rejects.toThrow(
      /direct fail/,
    );
    expect(widgetMock).not.toHaveBeenCalled();
  });
});
