/**
 * 双地址对照录证（仅观测，不改 workers/rpa 业务逻辑）
 *
 * 对每组美国街道地址：开全新页 → 填 pickup → 快照 → 填 delivery → 快照
 * 记录 widget 输入、全文、下拉状态、axel/location 网络事件
 *
 * 用法：npx tsx scripts/record-dual-address-compare.ts
 * 可选：COMPARE_PAIR_IDS=01,02,06
 */
import "dotenv/config";
import { randomUUID } from "node:crypto";
import { mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { loadDevEnv } from "@/lib/dev-env";
import { ensureNativeInfraIfNeeded } from "@/lib/infra/ensure-native-infra";
import {
  hostFetchMothershipCandidates,
} from "@/lib/frontend/api-client";
import { applyMothershipCandidate } from "@/lib/frontend/mothership-address";
import type { QuoteRequestBody } from "@/lib/frontend/types";
import { isAnyRpaWorkerHealthy } from "@/lib/rpa/worker-health";
import { ensureRedisReady, resetRedisClient } from "@/lib/redis";
import type { Page } from "playwright";
import type { QuoteRequest } from "@/modules/providers/quote-provider";
import { openAddressSide, visibleStreetInput } from "@/workers/rpa/address-side";
import {
  clickSuggestionByLabel,
  ensureAddressSuggestions,
  findAddressSuggestionLocator,
  isAddressSuggestionDropdownOpen,
  resolveDomSuggestionLabel,
} from "@/workers/rpa/address-suggestions";
import {
  captureAddressCommitSnapshot,
  isAddressPresentInWidget,
  readWidgetCombinedText,
} from "@/workers/rpa/address-commit";
import { RPAContext } from "@/workers/rpa/kernel/context";
import { closeBrowser, openQuotePage } from "@/workers/rpa/session-manager";
import { quotePageAdapter } from "@/workers/rpa/quote-page-adapter";
import {
  stabilizeQuoteLandingPage,
  waitForQuoterWidgetHydrated,
} from "@/workers/rpa/page-prep";

loadDevEnv();

const BASE = process.env.PROVE_BASE_URL ?? "http://localhost:3000";
const TOKEN = "demo-host-token";
const CUSTOMER_ID = "CUST_001";
const OUT_DIR = join(process.cwd(), ".rpa");

const PAIRS: Record<
  string,
  {
    label: string;
    pickup: { street: string; city: string; state: string };
    delivery: { street: string; city: string; state: string };
  }
> = {
  "01": {
    label: "洛杉矶 CA → Farmers Branch TX",
    pickup: { street: "1234 Warehouse Street", city: "Los Angeles", state: "CA" },
    delivery: { street: "5678 Distribution Way", city: "Farmers Branch", state: "TX" },
  },
  "02": {
    label: "西雅图 WA → 迈阿密 FL",
    pickup: { street: "3131 Western Ave", city: "Seattle", state: "WA" },
    delivery: { street: "7000 NW 52nd St", city: "Miami", state: "FL" },
  },
  "06": {
    label: "丹佛 CO → 夏洛特 NC",
    pickup: { street: "4800 Race St", city: "Denver", state: "CO" },
    delivery: { street: "9300 South Blvd", city: "Charlotte", state: "NC" },
  },
  "99": {
    label: "Key West FL → Los Angeles CA（Duval / Hollywood）",
    pickup: { street: "Duval Street", city: "Key West", state: "FL" },
    delivery: { street: "Hollywood Boulevard", city: "Los Angeles", state: "CA" },
  },
};

type NetworkHit = { phase: string; url: string; status: number };

type Checkpoint = {
  phase: string;
  snapshot: Awaited<ReturnType<typeof captureAddressCommitSnapshot>>;
  pickupInWidget: boolean;
  deliveryInWidget: boolean;
  surface: string;
  fullWidget: string;
  networkSinceLast: NetworkHit[];
};

function draftAddress(d: { street: string; city: string; state: string }) {
  const street = d.street.trim();
  const city = d.city.trim();
  const state = d.state.trim();
  return {
    street,
    city,
    state,
    zip: "",
    place_id: `draft_${street}_${city}_${state}`.replace(/\s+/g, "_").slice(0, 64),
    formatted_address: `${street}, ${city}, ${state}`,
    selected_from_suggestions: true,
  };
}

function toAddressFields(
  base: ReturnType<typeof draftAddress>,
  candidate: ReturnType<typeof applyMothershipCandidate>,
): QuoteRequest["pickup"] {
  return {
    street: candidate.street,
    city: candidate.city,
    state: candidate.state,
    zip: candidate.zip ?? "",
    placeId: base.place_id,
    formattedAddress: candidate.formatted_address,
    selectedFromSuggestions: true,
    mothershipOptionId: candidate.mothership_option_id ?? "",
    mothershipDisplayLabel: candidate.mothership_display_label ?? "",
    selectedFromMothership: true,
  };
}

function attachNetworkLogger(page: Page, bucket: NetworkHit[], phaseRef: { current: string }) {
  page.on("response", (response) => {
    const url = response.url();
    if (!url.includes("services.mothership.com/axel/location")) {
      return;
    }
    bucket.push({
      phase: phaseRef.current,
      url,
      status: response.status(),
    });
  });
}

async function selectSide(
  ctx: RPAContext,
  side: "pickup" | "delivery",
  address: QuoteRequest["pickup"],
  widgetSelector: string,
): Promise<void> {
  const page = ctx.page;
  const label = address.mothershipDisplayLabel?.trim();
  if (!label) {
    throw new Error(`${side} 缺少 mothershipDisplayLabel`);
  }
  await openAddressSide(ctx, side);
  const street = await visibleStreetInput(ctx, side, {
    deliverySectionOpened: side === "delivery",
  });
  if (!street) {
    throw new Error(`${side} 街道输入框不可见`);
  }
  const queryHint = {
    street: address.street,
    city: address.city ?? "",
    state: address.state ?? "",
    zip: address.zip ?? "",
  };
  await ensureAddressSuggestions(page, street, queryHint, {
    mothershipLabel: label,
  });
  const resolved =
    (await resolveDomSuggestionLabel(page, label, queryHint)) ?? label;
  const loc = await findAddressSuggestionLocator(page, resolved, queryHint);
  if (loc) {
    await loc.click();
  } else {
    const clicked = await clickSuggestionByLabel(page, resolved, queryHint);
    if (!clicked) {
      throw new Error(`${side} 联想点选失败: ${label}`);
    }
  }
  await page.keyboard.press("Enter").catch(() => undefined);
  await page.waitForTimeout(400);
}

async function takeCheckpoint(
  ctx: RPAContext,
  widgetSelector: string,
  phase: string,
  pickup: QuoteRequest["pickup"],
  delivery: QuoteRequest["pickup"],
  networkDrain: NetworkHit[],
): Promise<Checkpoint> {
  const drained = networkDrain.splice(0, networkDrain.length);
  const snapshot = await captureAddressCommitSnapshot(
    ctx,
    phase.includes("delivery") ? "delivery" : "pickup",
    widgetSelector,
  );
  const surface = snapshot.surfaceText || snapshot.inputValues.join(" ");
  const fullWidget = await readWidgetCombinedText(ctx.page, widgetSelector);
  return {
    phase,
    snapshot,
    pickupInWidget: await isAddressPresentInWidget(
      ctx.page,
      widgetSelector,
      pickup,
    ),
    deliveryInWidget: await isAddressPresentInWidget(
      ctx.page,
      widgetSelector,
      delivery,
    ),
    surface,
    fullWidget: fullWidget.slice(0, 400),
    networkSinceLast: drained,
  };
}

async function recordPair(pairId: string): Promise<Record<string, unknown>> {
  const pair = PAIRS[pairId];
  if (!pair) {
    throw new Error(`未知组 ${pairId}`);
  }

  console.log(`\n=== 录证组 ${pairId}: ${pair.label} ===`);

  const userPickup = draftAddress(pair.pickup);
  const userDelivery = draftAddress(pair.delivery);

  const candRes = await hostFetchMothershipCandidates(
    BASE,
    TOKEN,
    CUSTOMER_ID,
    userPickup,
    userDelivery,
  );
  if (candRes.code !== 0 || !candRes.data) {
    throw new Error(`候选 API 失败: ${candRes.message}`);
  }

  const selectedPickup = candRes.data.pickup_candidates[0];
  const selectedDelivery = candRes.data.delivery_candidates[0];
  if (!selectedPickup || !selectedDelivery) {
    throw new Error("候选不足");
  }

  const pickup = toAddressFields(
    userPickup,
    applyMothershipCandidate(userPickup, selectedPickup),
  );
  const delivery = toAddressFields(
    userDelivery,
    applyMothershipCandidate(userDelivery, selectedDelivery),
  );

  const opened = await openQuotePage(quotePageAdapter, false, undefined, {
    freshCargoContext: true,
  });
  const ctx = RPAContext.fromSession(opened.page, opened.context);
  const widgetSelector = ctx.selectors.resolve("RPA_SELECTOR_QUOTE_WIDGET");

  const networkHits: NetworkHit[] = [];
  const phaseRef = { current: "init" };
  attachNetworkLogger(ctx.page, networkHits, phaseRef);

  await stabilizeQuoteLandingPage(ctx);
  await waitForQuoterWidgetHydrated(ctx.page, 60_000);

  const checkpoints: Checkpoint[] = [];

  phaseRef.current = "initial";
  checkpoints.push(
    await takeCheckpoint(ctx, widgetSelector, "initial", pickup, delivery, networkHits),
  );

  phaseRef.current = "pickup-select";
  await selectSide(ctx, "pickup", pickup, widgetSelector);
  checkpoints.push(
    await takeCheckpoint(
      ctx,
      widgetSelector,
      "after-pickup-select",
      pickup,
      delivery,
      networkHits,
    ),
  );

  phaseRef.current = "pre-delivery";
  await ctx.page.keyboard.press("Escape").catch(() => undefined);
  await openAddressSide(ctx, "delivery");
  checkpoints.push(
    await takeCheckpoint(
      ctx,
      widgetSelector,
      "after-open-delivery-side",
      pickup,
      delivery,
      networkHits,
    ),
  );

  phaseRef.current = "delivery-select";
  await selectSide(ctx, "delivery", delivery, widgetSelector);
  checkpoints.push(
    await takeCheckpoint(
      ctx,
      widgetSelector,
      "after-delivery-select",
      pickup,
      delivery,
      networkHits,
    ),
  );

  const finalSurface = checkpoints.at(-1)?.surface ?? "";
  const finalFull = await readWidgetCombinedText(ctx.page, widgetSelector);
  const dropdownOpen = await isAddressSuggestionDropdownOpen(ctx.page);

  const pickupLostAfterDelivery =
    checkpoints.find((c) => c.phase === "after-pickup-select")?.pickupInWidget ===
      true &&
    checkpoints.find((c) => c.phase === "after-delivery-select")?.pickupInWidget ===
      false;

  const placeApiHits = networkHits.filter((h) =>
    h.url.includes("/axel/location/place/"),
  );
  const pickupPlaceAfterSelect = placeApiHits.some(
    (h) => h.phase === "pickup-select" || h.phase === "after-pickup-select",
  );

  await ctx.page
    .screenshot({
      path: join(OUT_DIR, `compare-${pairId}-final.png`),
      fullPage: false,
    })
    .catch(() => undefined);

  await closeBrowser();

  return {
    pairId,
    label: pair.label,
    confirmedPickup: selectedPickup.display_label,
    confirmedDelivery: selectedDelivery.display_label,
    checkpoints,
    summary: {
      pickupPresentAfterPickup: checkpoints.find(
        (c) => c.phase === "after-pickup-select",
      )?.pickupInWidget,
      pickupPresentAfterOpenDelivery: checkpoints.find(
        (c) => c.phase === "after-open-delivery-side",
      )?.pickupInWidget,
      pickupPresentAfterDelivery: checkpoints.find(
        (c) => c.phase === "after-delivery-select",
      )?.pickupInWidget,
      deliveryPresentAfterDelivery: checkpoints.find(
        (c) => c.phase === "after-delivery-select",
      )?.deliveryInWidget,
      pickupLostAfterDelivery,
      dropdownOpenAtEnd: dropdownOpen,
      hasDefaultLosAngeles: /Los Angeles,\s*CA/i.test(finalFull),
      pickupPlaceApiAfterPickupSelect: pickupPlaceAfterSelect,
      placeApiHitCount: placeApiHits.length,
      allNetworkHits: networkHits,
    },
    finalSurface,
    finalFullPreview: finalFull.slice(0, 300),
  };
}

async function main(): Promise<void> {
  ensureNativeInfraIfNeeded();
  resetRedisClient();
  await ensureRedisReady(12, 1_000);

  const res = await fetch(BASE, { method: "GET", redirect: "manual" });
  if (res.status <= 0) {
    throw new Error("Next 未响应");
  }
  if (!(await isAnyRpaWorkerHealthy())) {
    throw new Error("RPA Worker 无心跳");
  }

  const pairIds = (process.env.COMPARE_PAIR_IDS ?? "01,02,06")
    .split(",")
    .map((s) => s.trim())
    .filter(Boolean);

  const runId = randomUUID().slice(0, 8);
  const results: Record<string, unknown>[] = [];

  for (const pairId of pairIds) {
    try {
      results.push(await recordPair(pairId));
    } catch (error) {
      results.push({
        pairId,
        error: error instanceof Error ? error.message : String(error),
      });
      await closeBrowser().catch(() => undefined);
    }
  }

  mkdirSync(OUT_DIR, { recursive: true });
  const outPath = join(OUT_DIR, `dual-address-compare-${runId}.json`);
  writeFileSync(
    outPath,
    JSON.stringify(
      {
        runId,
        finishedAt: new Date().toISOString(),
        pairIds,
        results,
        interpretation: buildInterpretation(results),
      },
      null,
      2,
    ),
    "utf8",
  );

  console.log(`\n录证报告：${outPath}`);
  console.log(JSON.stringify(buildInterpretation(results), null, 2));
}

function buildInterpretation(
  results: Record<string, unknown>[],
): Record<string, unknown> {
  const summaries = results
    .map((r) => ({
      pairId: r.pairId,
      error: r.error,
      summary: r.summary as Record<string, unknown> | undefined,
    }))
    .filter((r) => r.summary || r.error);

  const passDual = summaries.filter(
    (s) =>
      s.summary?.pickupPresentAfterDelivery === true &&
      s.summary?.deliveryPresentAfterDelivery === true,
  );
  const pickupLost = summaries.filter(
    (s) => s.summary?.pickupLostAfterDelivery === true,
  );
  const neverLockedPickup = summaries.filter(
    (s) =>
      s.summary?.pickupPresentAfterPickup === false ||
      s.summary?.hasDefaultLosAngeles === true,
  );

  let verdict: string;
  if (passDual.length === summaries.length && summaries.length > 0) {
    verdict = "ALL_PAIRS_DUAL_OK";
  } else if (pickupLost.length > 0 && passDual.length > 0) {
    verdict = "PAIR_SPECIFIC_PICKUP_ROLLBACK";
  } else if (pickupLost.length === summaries.length) {
    verdict = "ALL_PAIRS_PICKUP_ROLLBACK";
  } else if (neverLockedPickup.length === summaries.length) {
    verdict = "PICKUP_NEVER_LOCKED";
  } else {
    verdict = "MIXED_OR_INCONCLUSIVE";
  }

  return {
    verdict,
    passDualPairIds: passDual.map((s) => s.pairId),
    pickupLostPairIds: pickupLost.map((s) => s.pairId),
    neverLockedPickupPairIds: neverLockedPickup.map((s) => s.pairId),
    errors: summaries.filter((s) => s.error).map((s) => ({
      pairId: s.pairId,
      error: s.error,
    })),
  };
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});
