import { beforeEach, describe, expect, it, vi } from "vitest";

vi.mock("@/lib/prisma", () => ({
  prisma: {
    quoteRecord: {
      findFirst: vi.fn(),
      findMany: vi.fn(),
      update: vi.fn(),
    },
    alertLog: { create: vi.fn() },
  },
}));

vi.mock("@/modules/cache/redis-cache", () => ({
  getL3: vi.fn(),
}));

vi.mock("@/modules/pricing/engine", () => ({
  getMarkupPercent: vi.fn().mockResolvedValue(0),
  applyMarkupToQuotes: vi.fn((q: unknown[]) => q),
}));

import { prisma } from "@/lib/prisma";
import { getL3 } from "@/modules/cache/redis-cache";
import {
  sweepTimedOutQuote,
  sweepTimedOutQuotes,
} from "@/workers/scheduler/timeout-sweeper";

const FOUR_TIERS = [
  { service_level: "standard", rate_option: "lowest", raw_freight: 320, surcharges: 0, raw_total: 320, carrier: "M", transit_days: "5", transit_description: "x" },
  { service_level: "standard", rate_option: "fastest", raw_freight: 365, surcharges: 0, raw_total: 365, carrier: "M", transit_days: "3", transit_description: "x" },
  { service_level: "guaranteed", rate_option: "lowest", raw_freight: 380, surcharges: 0, raw_total: 380, carrier: "M", transit_days: "4", transit_description: "x" },
  { service_level: "guaranteed", rate_option: "fastest", raw_freight: 410, surcharges: 0, raw_total: 410, carrier: "M", transit_days: "3", transit_description: "x" },
];

const mockedFindFirst = vi.mocked(prisma.quoteRecord.findFirst);
const mockedUpdate = vi.mocked(prisma.quoteRecord.update);
const mockedGetL3 = vi.mocked(getL3);
const mockedAlertCreate = vi.mocked(prisma.alertLog.create);

beforeEach(() => {
  vi.clearAllMocks();
});

describe("sweepTimedOutQuote", () => {
  it("无 L3 → failed + QUOTE_TIMEOUT", async () => {
    mockedFindFirst.mockResolvedValue({
      quoteId: "QTE_001",
      cargoHash: "hash1",
      customerId: "CUST_001",
      createdAt: new Date(Date.now() - 421_000),
    } as never);
    mockedGetL3.mockResolvedValue(null);

    await sweepTimedOutQuote("QTE_001");

    expect(mockedUpdate).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({
          status: "failed",
          errorCode: "QUOTE_TIMEOUT",
        }),
      }),
    );
    expect(mockedAlertCreate).toHaveBeenCalled();
  });

  it("有 L3 → done stale", async () => {
    mockedFindFirst.mockResolvedValue({
      quoteId: "QTE_002",
      cargoHash: "hash2",
      customerId: "CUST_001",
      createdAt: new Date(Date.now() - 421_000),
    } as never);
    mockedGetL3.mockResolvedValue({ quotes: FOUR_TIERS });

    await sweepTimedOutQuote("QTE_002");

    expect(mockedUpdate).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({
          status: "done",
          sourceType: "stale",
          isRealtime: false,
        }),
      }),
    );
  });

  it("未超时 → 不处理", async () => {
    mockedFindFirst.mockResolvedValue({
      quoteId: "QTE_003",
      cargoHash: "hash3",
      customerId: "CUST_001",
      createdAt: new Date(Date.now() - 5_000),
    } as never);

    await sweepTimedOutQuote("QTE_003");

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

describe("sweepTimedOutQuotes", () => {
  it("批量扫描返回处理数量", async () => {
    vi.mocked(prisma.quoteRecord.findMany).mockResolvedValue([
      { quoteId: "QTE_A" },
      { quoteId: "QTE_B" },
    ] as never);
    mockedFindFirst.mockResolvedValue(null);

    const count = await sweepTimedOutQuotes();
    expect(count).toBe(2);
  });
});
