/**
 * Flock 登录态 Quick 表单矩阵探针：只填表不提交，截图并对比选项是否在官网选中
 *
 *   RPA_HEADLESS=false npm run probe:flock-form-matrix
 *   npm run probe:flock-form-matrix -- --max=5
 *   npm run probe:flock-form-matrix -- --only-failed
 */
import fs from "node:fs";
import path from "node:path";
import type { Browser, Page } from "playwright";
import {
  FLOCK_LOCATION_ACCESSORIALS,
  FLOCK_LOCATION_TYPES,
} from "@/components/flock/flock-logged-in-quote-form";
import { defaultFlockPickupDateIso } from "@/lib/flock/pickup-date";
import { launchRpaBrowser } from "@/lib/rpa/browser-launch";
import { createRpaBrowserContext } from "@/lib/rpa/browser-context";
import { ensureFlockLoggedIn } from "@/workers/rpa/flock/login";
import {
  fillFlockLoggedInQuoteForm,
  openFlockLoggedInQuoteEntry,
} from "@/workers/rpa/flock/logged-in-form";
import {
  verifyLoggedInFormOnPage,
  type LoggedInFormVerifyExpect,
  type LoggedInFormVerifyIssue,
} from "@/workers/rpa/flock/logged-in-form-verify";
import type { LoggedInFormInput } from "@/workers/rpa/flock/logged-in-form";

const OUT_DIR = path.join(process.cwd(), ".rpa", "flock-form-matrix");
const BASE_PICKUP_ZIP = "30326";
const BASE_DELIVERY_ZIP = "15222";

type MatrixCase = {
  id: string;
  label: string;
  pickupTypeId: string;
  deliveryTypeId: string;
};

type CaseResult = {
  id: string;
  label: string;
  fillOk: boolean;
  fillError?: string;
  verifyOk: boolean;
  issues: LoggedInFormVerifyIssue[];
  screenshot: string;
  durationMs: number;
};

function isoToDisplay(iso: string): string {
  const [y, m, d] = iso.split("-");
  return y && m && d ? `${m}/${d}/${y}` : iso;
}

function accessorialBools(
  typeId: string,
  side: "pickup" | "delivery",
): Pick<
  LoggedInFormInput,
  | "pickupLiftgate"
  | "pickupInside"
  | "pickupPalletJack"
  | "deliveryLiftgate"
  | "deliveryInside"
  | "deliveryPalletJack"
> {
  const flags = FLOCK_LOCATION_ACCESSORIALS[typeId as keyof typeof FLOCK_LOCATION_ACCESSORIALS];
  if (!flags) return {};
  const on = {
    liftgate: flags.liftgate,
    inside: flags.inside,
    palletJack: flags.palletJack,
  };
  if (side === "pickup") {
    return {
      pickupLiftgate: on.liftgate,
      pickupInside: on.inside,
      pickupPalletJack: on.palletJack,
    };
  }
  return {
    deliveryLiftgate: on.liftgate,
    deliveryInside: on.inside,
    deliveryPalletJack: on.palletJack,
  };
}

function buildMatrixCases(): MatrixCase[] {
  const ids = FLOCK_LOCATION_TYPES.map((t) => t.id);
  const baseline = "business_with_dock";
  const cases: MatrixCase[] = [];

  for (const pickupId of ids) {
    cases.push({
      id: `pickup_${pickupId}__delivery_${baseline}`,
      label: `提货=${pickupId} / 派送=${baseline}`,
      pickupTypeId: pickupId,
      deliveryTypeId: baseline,
    });
  }

  for (const deliveryId of ids) {
    if (deliveryId === baseline) continue;
    cases.push({
      id: `pickup_${baseline}__delivery_${deliveryId}`,
      label: `提货=${baseline} / 派送=${deliveryId}`,
      pickupTypeId: baseline,
      deliveryTypeId: deliveryId,
    });
  }

  const crossPairs: Array<[string, string]> = [
    ["residential", "limited_farm"],
    ["residential", "limited_construction"],
    ["business_without_dock", "residential"],
    ["limited_farm", "residential"],
    ["trade_show", "limited_school"],
    ["limited_worship", "limited_school"],
    ["limited_airport", "limited_port"],
    ["limited_military", "limited_other"],
  ];
  for (const [p, d] of crossPairs) {
    cases.push({
      id: `cross_${p}__${d}`,
      label: `交叉 ${p} → ${d}`,
      pickupTypeId: p,
      deliveryTypeId: d,
    });
  }

  return cases;
}

function buildInput(caseDef: MatrixCase): LoggedInFormInput {
  const pickupDate = isoToDisplay(defaultFlockPickupDateIso());
  return {
    pickupDate,
    pickupZip: BASE_PICKUP_ZIP,
    deliveryZip: BASE_DELIVERY_ZIP,
    palletCount: 2,
    totalWeightLb: 500,
    lengthIn: 48,
    widthIn: 40,
    heightIn: 48,
    pickupLocationType: caseDef.pickupTypeId,
    deliveryLocationType: caseDef.deliveryTypeId,
    packagingType: "pallets_48x40",
    freightClass: "density",
    description: "matrix probe",
    stackable: true,
    turnable: false,
    additionalServices: ["unloading"],
    vehicleTypes: ["box_truck", "dry_van", "refrigerated"],
    ...accessorialBools(caseDef.pickupTypeId, "pickup"),
    ...accessorialBools(caseDef.deliveryTypeId, "delivery"),
  };
}

function buildVerifyExpect(caseDef: MatrixCase): LoggedInFormVerifyExpect {
  const input = buildInput(caseDef);
  return {
    pickupTypeId: caseDef.pickupTypeId,
    deliveryTypeId: caseDef.deliveryTypeId,
    pickupZip: BASE_PICKUP_ZIP,
    deliveryZip: BASE_DELIVERY_ZIP,
    pickupLiftgate: input.pickupLiftgate,
    pickupInside: input.pickupInside,
    pickupPalletJack: input.pickupPalletJack,
    deliveryLiftgate: input.deliveryLiftgate,
    deliveryInside: input.deliveryInside,
    deliveryPalletJack: input.deliveryPalletJack,
  };
}

function parseArgs(): {
  max: number | null;
  headed: boolean;
  onlyFailed: boolean;
  onlyIds: string[] | null;
  pauseMs: number;
  slowMo: number;
} {
  const argv = process.argv.slice(2);
  let max: number | null = null;
  let headed = process.env.RPA_HEADLESS === "false";
  let onlyFailed = false;
  let onlyIds: string[] | null = null;
  let pauseMs = 0;
  let slowMo = 0;
  for (let i = 0; i < argv.length; i++) {
    const a = argv[i] ?? "";
    if (a === "--headed") headed = true;
    if (a === "--only-failed" || a === "--failed") {
      onlyFailed = true;
    }
    if (a.startsWith("--only=")) {
      onlyIds = a
        .slice("--only=".length)
        .split(",")
        .map((s) => s.trim())
        .filter(Boolean);
    }
    if (a === "--only" && argv[i + 1]) {
      onlyIds = String(argv[++i])
        .split(",")
        .map((s) => s.trim())
        .filter(Boolean);
    }
    if (a.startsWith("--max=")) {
      const n = Number(a.split("=")[1]);
      if (Number.isFinite(n) && n > 0) max = Math.floor(n);
    }
    if (a === "--max" && argv[i + 1]) {
      const n = Number(argv[i + 1]);
      if (Number.isFinite(n) && n > 0) max = Math.floor(n);
    }
    if (a.startsWith("--pause-ms=")) {
      const n = Number(a.split("=")[1]);
      if (Number.isFinite(n) && n >= 0) pauseMs = Math.floor(n);
    }
    if (a.startsWith("--slowmo=")) {
      const n = Number(a.split("=")[1]);
      if (Number.isFinite(n) && n >= 0) slowMo = Math.floor(n);
    }
  }
  if (headed && pauseMs === 0) pauseMs = 2500;
  if (headed && slowMo === 0) slowMo = 60;
  return { max, headed, onlyFailed, onlyIds, pauseMs, slowMo };
}

function loadFailedCaseIds(): Set<string> {
  const reportPath = path.join(OUT_DIR, "report.json");
  if (!fs.existsSync(reportPath)) {
    throw new Error(`无历史报告 ${reportPath}，无法 --only-failed`);
  }
  const raw = JSON.parse(fs.readFileSync(reportPath, "utf8")) as {
    results?: CaseResult[];
  };
  const ids = new Set(
    (raw.results ?? [])
      .filter((r) => !r.fillOk || !r.verifyOk)
      .map((r) => r.id),
  );
  if (ids.size === 0) {
    throw new Error("历史报告中无失败用例");
  }
  return ids;
}

async function openFreshQuickForm(page: Page): Promise<boolean> {
  const home =
    process.env.FLOCK_LOGGED_IN_HOME_URL?.trim() ||
    "https://app.flockfreight.com/home";
  await page.goto(home, { waitUntil: "domcontentloaded" });
  await page.waitForTimeout(800);
  return openFlockLoggedInQuoteEntry(page);
}

async function runCase(
  page: Page,
  caseDef: MatrixCase,
  index: number,
  total: number,
  options?: { pauseMs?: number },
): Promise<CaseResult> {
  const started = Date.now();
  const screenshot = path.join(OUT_DIR, `${caseDef.id}.png`);

  console.log(`\n[matrix] ${index}/${total} ${caseDef.id}`);
  console.log(`[matrix] ${caseDef.label}`);

  const opened = await openFreshQuickForm(page);
  if (!opened) {
    await page.screenshot({ path: screenshot, fullPage: true }).catch(() => undefined);
    return {
      id: caseDef.id,
      label: caseDef.label,
      fillOk: false,
      fillError: "无法打开 Quick 查价表单",
      verifyOk: false,
      issues: [
        {
          field: "表单入口",
          expected: "Let's build a quote / Quick",
          actual: page.url(),
          analysis: "登录态未进入 Quick 表单，检查 storage 或 FLOCK_LOGIN_*",
        },
      ],
      screenshot,
      durationMs: Date.now() - started,
    };
  }

  const input = buildInput(caseDef);
  console.log(
    `[matrix] 期望附属 Pickup Lg/In/Pj=` +
      `${input.pickupLiftgate}/${input.pickupInside}/${input.pickupPalletJack}` +
      ` Delivery=${input.deliveryLiftgate}/${input.deliveryInside}/${input.deliveryPalletJack}`,
  );
  const filled = await fillFlockLoggedInQuoteForm(page, input);

  await page
    .getByText(/Pickup.*Delivery|Shipment Pickup/i)
    .first()
    .scrollIntoViewIfNeeded()
    .catch(() => undefined);
  await page.waitForTimeout(400);
  await page.screenshot({ path: screenshot, fullPage: true });

  if (!filled.ok) {
    if (options?.pauseMs) {
      console.log(`[matrix] 填表失败，暂停 ${options.pauseMs}ms 便于观察…`);
      await page.waitForTimeout(options.pauseMs);
    }
    return {
      id: caseDef.id,
      label: caseDef.label,
      fillOk: false,
      fillError: filled.error,
      verifyOk: false,
      issues: [
        {
          field: "RPA 填表",
          expected: "fill ok",
          actual: filled.error ?? "unknown",
          analysis:
            "填表阶段即失败，未进入校验；对照 logged-in-form.ts 对应字段 selector",
        },
      ],
      screenshot,
      durationMs: Date.now() - started,
    };
  }

  const verified = await verifyLoggedInFormOnPage(
    page,
    buildVerifyExpect(caseDef),
  );

  if (!verified.ok) {
    for (const issue of verified.issues) {
      console.warn(
        `  ✗ ${issue.field}: 期望=${issue.expected} 实际=${issue.actual}`,
      );
      console.warn(`    → ${issue.analysis}`);
    }
  } else {
    console.log("  ✓ 官网选项与系统一致");
  }

  if (options?.pauseMs) {
    console.log(`[matrix] 暂停 ${options.pauseMs}ms 观察页面…`);
    await page.waitForTimeout(options.pauseMs);
  }

  return {
    id: caseDef.id,
    label: caseDef.label,
    fillOk: true,
    verifyOk: verified.ok,
    issues: verified.issues,
    screenshot,
    durationMs: Date.now() - started,
  };
}

function writeReport(
  results: CaseResult[],
  options?: { mergePrevious?: boolean },
): void {
  let merged = results;
  if (options?.mergePrevious) {
    const reportPath = path.join(OUT_DIR, "report.json");
    if (fs.existsSync(reportPath)) {
      const prev = JSON.parse(fs.readFileSync(reportPath, "utf8")) as {
        results?: CaseResult[];
      };
      const byId = new Map((prev.results ?? []).map((r) => [r.id, r]));
      for (const r of results) byId.set(r.id, r);
      merged = [...byId.values()];
    }
  }

  const passed = merged.filter((r) => r.fillOk && r.verifyOk);
  const failed = merged.filter((r) => !r.fillOk || !r.verifyOk);

  const jsonPath = path.join(OUT_DIR, "report.json");
  fs.writeFileSync(
    jsonPath,
    JSON.stringify(
      {
        at: new Date().toISOString(),
        total: merged.length,
        passed: passed.length,
        failed: failed.length,
        results: merged,
      },
      null,
      2,
    ),
    "utf8",
  );

  const lines: string[] = [
    "# Flock 登录态表单矩阵探针",
    "",
    `- 时间：${new Date().toLocaleString("zh-CN")}`,
    `- 合计：${merged.length}，通过：${passed.length}，失败：${failed.length}`,
    `- 截图目录：\`.rpa/flock-form-matrix/\``,
    "",
  ];

  if (failed.length > 0) {
    lines.push("## 失败用例", "");
    for (const r of failed) {
      lines.push(`### ${r.id}`, "");
      lines.push(`- 说明：${r.label}`);
      if (r.fillError) lines.push(`- 填表错误：${r.fillError}`);
      lines.push(`- 截图：\`${path.basename(r.screenshot)}\``);
      for (const issue of r.issues) {
        lines.push(
          `- **${issue.field}**：期望 \`${issue.expected}\` / 实际 \`${issue.actual}\``,
        );
        lines.push(`  - 分析：${issue.analysis}`);
      }
      lines.push("");
    }
  }

  if (passed.length > 0) {
    lines.push("## 通过用例", "");
    for (const r of passed) {
      lines.push(`- ${r.id} (${r.durationMs}ms)`);
    }
  }

  fs.writeFileSync(path.join(OUT_DIR, "report.md"), lines.join("\n"), "utf8");
  console.log(`\n[matrix] 报告：${jsonPath}`);
}

async function main(): Promise<void> {
  const { max, headed, onlyFailed, onlyIds, pauseMs, slowMo } = parseArgs();
  fs.mkdirSync(OUT_DIR, { recursive: true });

  let cases = buildMatrixCases();
  if (onlyFailed) {
    const failedIds = loadFailedCaseIds();
    cases = cases.filter((c) => failedIds.has(c.id));
    console.log(`[matrix] --only-failed 过滤后 cases=${cases.length}`);
  }
  if (onlyIds && onlyIds.length > 0) {
    cases = cases.filter((c) =>
      onlyIds.some((q) => c.id.includes(q) || c.label.includes(q)),
    );
    console.log(
      `[matrix] --only 过滤后 cases=${cases.length} keys=${onlyIds.join(",")}`,
    );
  }
  if (max != null) cases = cases.slice(0, max);

  console.log(
    `[matrix] cases=${cases.length} headed=${headed} pauseMs=${pauseMs} slowMo=${slowMo} out=${OUT_DIR}`,
  );

  const storagePath =
    process.env.FLOCK_LOGGED_IN_STORAGE_STATE_PATH?.trim() ||
    path.join(process.cwd(), ".rpa", "flock-logged-in-storage.json");
  const storageOpts = fs.existsSync(storagePath)
    ? { storageState: storagePath }
    : undefined;
  if (storageOpts) {
    console.log(`[matrix] 使用 storage: ${storagePath}`);
  }

  const browser: Browser = await launchRpaBrowser({
    headless: !headed,
    incognito: !storageOpts,
    ...(slowMo > 0 ? { slowMo } : {}),
  });

  const results: CaseResult[] = [];

  try {
    const context = await createRpaBrowserContext(browser, storageOpts);
    const page = await context.newPage();

    const login = await ensureFlockLoggedIn(page, context);
    if (!login.ok) {
      console.error(`[matrix] 登录失败: ${login.errorMessage}`);
      process.exit(1);
    }

    for (let i = 0; i < cases.length; i++) {
      results.push(
        await runCase(page, cases[i]!, i + 1, cases.length, {
          pauseMs: headed ? pauseMs : 0,
        }),
      );
    }
  } finally {
    await browser.close();
  }

  writeReport(results, { mergePrevious: onlyFailed || Boolean(onlyIds?.length) });

  const failCount = results.filter((r) => !r.fillOk || !r.verifyOk).length;
  console.log(
    `[matrix] 完成 通过=${results.length - failCount} 失败=${failCount}`,
  );
  process.exit(failCount > 0 ? 1 : 0);
}

main().catch((error) => {
  console.error("[matrix] fatal:", error);
  process.exit(1);
});
