import { describe, expect, it, vi } from "vitest";
import type { Page } from "playwright";
import { fillCargoStep } from "@/workers/rpa/kernel/steps/cargo-step";

vi.mock("@/workers/rpa/fill-cargo-in-drawer", () => ({
  fillCargoInDrawer: vi.fn().mockResolvedValue({
    weightDisplay: {
      displayValue: "500",
      unit: "lb",
      weightLbPerPallet: 500,
    },
    expected: {
      palletCount: 2,
      weight: {
        displayValue: "500",
        unit: "lb",
        weightLbPerPallet: 500,
        palletCount: 2,
      },
    },
  }),
}));

vi.mock("@/workers/rpa/cargo-lock", () => ({
  assertBothAddressesLocked: vi.fn().mockResolvedValue(undefined),
  settleAddressFieldsForCargo: vi.fn().mockResolvedValue(undefined),
  waitForCargoCommittedState: vi.fn().mockResolvedValue({
    hasSummaryChip: true,
    visibleEditableCargoInputs: 0,
    editorOpen: false,
    internalPalletCount: 2,
    internalWeight: 500,
    widgetTextPreview: "",
  }),
}));

const stateCalls: string[] = [];

function mockCtx() {
  const page = {
    waitForTimeout: vi.fn().mockResolvedValue(undefined),
    locator: vi.fn().mockReturnValue({
      last: vi.fn().mockReturnValue({
        locator: vi.fn().mockReturnValue({ first: vi.fn() }),
      }),
      filter: vi.fn().mockReturnValue({ nth: vi.fn() }),
      first: vi.fn(),
    }),
    url: vi.fn().mockReturnValue("https://example.com"),
  } as unknown as Page;

  return {
    page,
    selectors: {
      specs: vi.fn().mockReturnValue(["input"]),
      optionalEnv: vi.fn().mockReturnValue(undefined),
      resolve: vi.fn().mockReturnValue("#widget"),
      widget: vi.fn(),
    },
    exec: {
      evaluateOnLocator: vi.fn().mockResolvedValue(false),
    },
    runtime: {
      evaluateOnLocator: vi.fn(),
    },
    getWidgetScrollAt: vi.fn().mockReturnValue(0),
    setWidgetScrollAt: vi.fn(),
    state: {
      triggerCargoSection: vi.fn().mockResolvedValue(undefined),
      settleAddressEditing: vi.fn(async () => {
        stateCalls.push("settle");
      }),
    },
  };
}

describe("fillCargoStep UI flow", () => {
  it("顺序：fillCargoInDrawer → waitForCargoCommittedState", async () => {
    const { fillCargoInDrawer } = await import("@/workers/rpa/fill-cargo-in-drawer");
    const { waitForCargoCommittedState } = await import("@/workers/rpa/cargo-lock");

    const ctx = mockCtx() as never;
    await fillCargoStep(ctx, {
      cargoHash: "t",
      pickup: {} as never,
      delivery: {} as never,
      weightLb: 500,
      dimsIn: { l: 48, w: 40, h: 48 },
      palletCount: 2,
      cargoType: "general_freight",
    });

    expect(fillCargoInDrawer).toHaveBeenCalled();
    expect(waitForCargoCommittedState).toHaveBeenCalled();
  });

  it("fillCargoInDrawer 失败时向上抛出", async () => {
    const { fillCargoInDrawer } = await import("@/workers/rpa/fill-cargo-in-drawer");
    const { RpaError } = await import("@/modules/rpa/errors");
    vi.mocked(fillCargoInDrawer).mockRejectedValueOnce(
      new RpaError("STRUCT_CHANGE", "货物 drawer 内未找到任何重量输入框", {
        retryable: true,
      }),
    );
    const ctx = mockCtx() as never;
    await expect(
      fillCargoStep(ctx, {
        cargoHash: "t",
        pickup: {} as never,
        delivery: {} as never,
        weightLb: 500,
        dimsIn: { l: 48, w: 40, h: 48 },
        palletCount: 2,
        cargoType: "general_freight",
      }),
    ).rejects.toMatchObject({ code: "STRUCT_CHANGE" });
  });
});
