import type { Locator, Page } from "playwright";
import type { AddressLookupInput } from "@/modules/address/types";
import { pageLocator } from "@/lib/rpa/locator";
import { awaitHumanPacing } from "@/lib/rpa/human-pacing";
import { isRpaAddressRushMode } from "@/lib/rpa/env";
import { COLLECT_WIDGET_SUGGESTION_LABELS } from "@/workers/rpa/kernel/browser-scripts";
import { dismissCookieConsent } from "@/workers/rpa/page-prep";
import type { AxelSelectionBridge } from "@/workers/rpa/axel-selection-bridge";
import {
  findGoldStandardTargets,
  type GoldStandardClickTarget,
} from "@/workers/rpa/gold-standard-targets";

const NEGATIVE_CLICK_LABEL_RE =
  /^(use my location|pick up from|deliver to)$/i;

/** 易误点联想词：查询未含该词时不应匹配（如 Market Street vs Market Street Bridge） */
const SPURIOUS_STREET_TOKEN_RE =
  /\b(bridge|bypass|overpass|highway|expy|expressway|freeway)\b/i;

export function suggestionLabelConflicts(
  candidateLabel: string,
  query: AddressLookupInput,
  expectedLabel?: string,
): boolean {
  const cand = normalizeLabel(candidateLabel);
  const street = normalizeLabel(query.street);
  const expected = normalizeLabel(expectedLabel ?? "");
  const reference = expected || street;
  if (!cand || !reference) {
    return false;
  }

  const streetNum = query.street.match(/^\d+/)?.[0];
  if (streetNum && cand.includes(streetNum) && !reference.includes("bridge")) {
    if (/\bbridge\b/.test(cand) && !/\bbridge\b/.test(reference)) {
      return true;
    }
  }

  for (const token of ["bridge", "bypass", "overpass", "highway", "freeway"]) {
    const inCand = cand.includes(token);
    const inRef = reference.includes(token) || street.includes(token);
    if (inCand && !inRef) {
      return true;
    }
  }

  if (streetNum && !cand.includes(streetNum)) {
    const city = normalizeLabel(query.city);
    const state = normalizeLabel(query.state);
    if (
      city &&
      state &&
      cand.includes(city) &&
      cand.includes(state) &&
      !/\d/.test(cand.split(",")[0] ?? "")
    ) {
      return true;
    }
  }

  return false;
}

export type StyledClickStrategy = {
  name: string;
  locator: Locator;
};

function stripCountrySuffix(label: string): string {
  return label.replace(/,\s*USA\s*$/i, "").trim();
}

function normalizeLabel(text: string): string {
  return text.replace(/\s+/g, " ").trim().toLowerCase();
}

export function labelsMatch(a: string, b: string): boolean {
  const x = normalizeLabel(a);
  const y = normalizeLabel(b);
  if (!x || !y) return false;
  if (x === y) return true;
  if (x.includes(y) || y.includes(x)) return true;
  const num = x.match(/^\d+/)?.[0];
  if (num && x.includes(num) && y.includes(num)) {
    const stateA = x.match(/\b([a-z]{2})\b(?!.*\b[a-z]{2}\b)/i)?.[1];
    const stateB = y.match(/\b([a-z]{2})\b(?!.*\b[a-z]{2}\b)/i)?.[1];
    if (stateA && stateB && stateA.toLowerCase() === stateB.toLowerCase()) {
      if (SPURIOUS_STREET_TOKEN_RE.test(x) !== SPURIOUS_STREET_TOKEN_RE.test(y)) {
        return false;
      }
      return true;
    }
  }
  return false;
}

export function buildDescriptionProbes(
  description: string,
  query: AddressLookupInput,
): string[] {
  return [
    description,
    stripCountrySuffix(description),
    description.split(",")[0]?.trim() ?? "",
    `${query.street}, ${query.city}, ${query.state}`,
    `${query.street}, ${query.city}, ${query.state}, USA`,
    query.street,
  ].filter((value, index, arr) => value.length >= 6 && arr.indexOf(value) === index);
}

function pickLabelIndex(
  labels: string[],
  description: string,
  query: AddressLookupInput,
  preferredIndex: number,
): number {
  const eligible = labels
    .map((label, index) => ({ label, index }))
    .filter(
      ({ label }) => !suggestionLabelConflicts(label, query, description),
    );
  const pool = eligible.length ? eligible : labels.map((label, index) => ({ label, index }));

  if (preferredIndex >= 0 && preferredIndex < labels.length) {
    const preferred = labels[preferredIndex]!;
    if (!suggestionLabelConflicts(preferred, query, description)) {
      return preferredIndex;
    }
  }
  const probes = buildDescriptionProbes(description, query);
  for (const probe of probes) {
    const hit = pool.find(({ label }) => labelsMatch(label, probe));
    if (hit) {
      return hit.index;
    }
  }
  return pool[0]?.index ?? 0;
}

function cssEscapeToken(token: string): string {
  return token.replace(/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, "\\$1");
}

function buildClassSelector(tagName: string, className: string): string | null {
  const tokens = className
    .split(/\s+/)
    .map((part) => part.trim())
    .filter((part) => part.length > 0 && part.startsWith("sc-"));
  if (!tokens.length) {
    return null;
  }
  const tag = (tagName || "div").toLowerCase();
  return `${tag}.${tokens.map(cssEscapeToken).join(".")}`;
}

/** 金样本 + 文本探针构建多层回退 Locator 链 */
export function buildGoldDrivenLocators(
  page: Page,
  description: string,
  query: AddressLookupInput,
  options?: {
    side?: "pickup" | "delivery";
    goldTargets?: GoldStandardClickTarget[];
    omitScFallback?: boolean;
  },
): StyledClickStrategy[] {
  const widget = pageLocator(page, "#react-quoter-landing-plugin");
  const strategies: StyledClickStrategy[] = [];
  const seen = new Set<string>();

  const push = (name: string, locator: Locator): void => {
    if (seen.has(name)) {
      return;
    }
    seen.add(name);
    strategies.push({ name, locator });
  };

  const goldTargets =
    options?.goldTargets ??
    (options?.side
      ? findGoldStandardTargets(description, query, options.side, 70)
      : []);

  for (const gold of goldTargets) {
    if (gold.dataOptionId) {
      push(
        `gold-data-option-id:${gold.dataOptionId.slice(0, 12)}`,
        widget.locator(`[data-option-id="${gold.dataOptionId}"]`),
      );
      push(
        `gold-data-place-id:${gold.dataOptionId.slice(0, 12)}`,
        widget.locator(`[data-place-id="${gold.dataOptionId}"]`),
      );
    }
    if (gold.textPreview.length >= 12) {
      push(
        `gold-exact-text:${gold.textPreview.slice(0, 24)}`,
        widget.getByText(gold.textPreview, { exact: true }),
      );
      const partial = stripCountrySuffix(gold.textPreview);
      if (partial !== gold.textPreview) {
        push(
          `gold-partial-text:${partial.slice(0, 24)}`,
          widget.getByText(partial, { exact: false }),
        );
      }
    }
    const classSel = buildClassSelector(gold.tagName, gold.className);
    if (classSel && gold.textPreview.length >= 12) {
      push(
        `gold-class-text:${gold.className.slice(0, 20)}`,
        widget.locator(classSel).filter({ hasText: gold.textPreview.slice(0, 40) }),
      );
    }
  }

  for (const probe of buildDescriptionProbes(description, query)) {
    push(
      `probe-text:${probe.slice(0, 24)}`,
      widget.locator("div[class*='sc-']").filter({ hasText: probe }),
    );
    push(`probe-getByText:${probe.slice(0, 24)}`, widget.getByText(probe));
  }

  if (!options?.omitScFallback) {
    push("sc-fallback", widget.locator("div[class*='sc-']"));
  }
  return strategies;
}

async function collectWidgetLabels(page: Page): Promise<string[]> {
  return (await page
    .evaluate(COLLECT_WIDGET_SUGGESTION_LABELS)
    .catch(() => [])) as string[];
}

/** 重新键入以唤起 styled-components 联想下拉 */
export async function reopenStyledSuggestionDropdown(
  page: Page,
  input: Locator,
  query: AddressLookupInput,
  description: string,
): Promise<void> {
  const fullText =
    description.trim() ||
    `${query.street}, ${query.city}, ${query.state}, USA`;
  await input.click({ force: true }).catch(() => undefined);
  await page.waitForTimeout(120);
  const currentValue = ((await input.inputValue().catch(() => "")) || "")
    .replace(/\s+/g, " ")
    .trim();
  const fullNorm = fullText.replace(/\s+/g, " ").trim();
  const alreadyTyped =
    currentValue.length >= 8 &&
    (currentValue === fullNorm ||
      currentValue.toLowerCase().includes(fullNorm.toLowerCase().slice(0, 16)));
  if (!alreadyTyped) {
    await input.fill("").catch(() => undefined);
    await page.waitForTimeout(80);
  }
  const typePromise = input
    .pressSequentially(fullText, { delay: 35 })
    .catch(() => undefined);
  const searchPromise = page
    .waitForResponse(
      (response) =>
        response.url().includes("axel/location/search") &&
        response.request().method() === "POST" &&
        response.status() === 200,
      { timeout: 12_000 },
    )
    .catch(() => undefined);
  await Promise.all([typePromise, searchPromise]);
  await page.waitForTimeout(500);
}

async function clickRowWithPointer(page: Page, row: Locator): Promise<void> {
  await row.scrollIntoViewIfNeeded().catch(() => undefined);
  const box = await row.boundingBox().catch(() => null);
  if (box) {
    const x = box.x + box.width / 2;
    const y = box.y + box.height / 2;
    const steps = process.env.RPA_FAST_CLICK === "true" ? 1 : 6;
    await page.mouse.move(x, y, { steps });
    if (steps > 1) {
      await page.waitForTimeout(60);
    }
    await page.mouse.down();
    if (steps > 1) {
      await page.waitForTimeout(40);
    }
    await page.mouse.up();
    return;
  }
  await row.click({ force: true, timeout: 3_000 }).catch(() => undefined);
}

async function isNegativeClickTarget(row: Locator): Promise<boolean> {
  const text = ((await row.innerText().catch(() => "")) || "")
    .replace(/\s+/g, " ")
    .trim();
  return text.length < 12 || NEGATIVE_CLICK_LABEL_RE.test(text);
}

/** 等待 styled-components 联想行出现 */
export async function waitForStyledSuggestionRows(
  page: Page,
  _probes: string[],
  _query: AddressLookupInput,
  timeoutMs: number,
): Promise<boolean> {
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    const labels = await collectWidgetLabels(page);
    if (labels.length > 0) {
      return true;
    }
    await page.waitForTimeout(150);
  }
  return false;
}

async function clickStyledRowByLabel(
  page: Page,
  targetLabel: string,
  query?: AddressLookupInput,
): Promise<{ clicked: boolean; matchedText?: string; reason?: string }> {
  const widget = pageLocator(page, "#react-quoter-landing-plugin");
  const queryHint: AddressLookupInput = query ?? {
    street: targetLabel.split(",")[0]?.trim() ?? targetLabel,
    city: targetLabel.split(",")[1]?.trim() ?? "",
    state: targetLabel.split(",")[2]?.trim()?.slice(0, 2) ?? "",
    zip: "",
  };
  const probes = buildDescriptionProbes(targetLabel, queryHint);
  for (const probe of probes) {
    const byText = widget.getByText(probe, { exact: false }).first();
    if (!(await byText.isVisible().catch(() => false))) {
      continue;
    }
    const box = await byText.boundingBox().catch(() => null);
    if (box && box.width >= 80 && box.height >= 18) {
      await clickRowWithPointer(page, byText);
      return { clicked: true, matchedText: probe };
    }
  }

  const rows = widget.locator("div[class*='sc-']");
  const count = await rows.count().catch(() => 0);
  let best: { row: Locator; text: string; score: number } | null = null;

  for (let i = 0; i < count; i += 1) {
    const row = rows.nth(i);
    if (await row.locator("input, textarea, button").count().catch(() => 0)) {
      continue;
    }
    const text = ((await row.innerText().catch(() => "")) || "")
      .replace(/\s+/g, " ")
      .trim();
    if (text.length < 12) {
      continue;
    }
    if (!labelsMatch(text, targetLabel)) {
      continue;
    }
    if (suggestionLabelConflicts(text, queryHint, targetLabel)) {
      continue;
    }
    const box = await row.boundingBox().catch(() => null);
    if (!box || box.width < 120 || box.height < 20) {
      continue;
    }
    const score = text.length;
    if (!best || score > best.score) {
      best = { row, text, score };
    }
  }

  if (!best) {
    return { clicked: false, reason: "no-dom-row" };
  }

  await clickRowWithPointer(page, best.row);
  return { clicked: true, matchedText: best.text };
}

async function tryLocatorStrategies(
  page: Page,
  strategies: StyledClickStrategy[],
  description: string,
  query: AddressLookupInput,
  options?: {
    bridge?: AxelSelectionBridge;
    placeId?: string;
    maxAttempts?: number;
    placeCommitTimeoutMs?: number;
  },
): Promise<{
  clicked: boolean;
  reason?: string;
  matchedText?: string;
  method?: string;
}> {
  const maxAttempts = options?.maxAttempts ?? strategies.length;
  const placeCommitMs = options?.placeCommitTimeoutMs ?? 2_000;
  let attempts = 0;

  for (const strategy of strategies) {
    if (attempts >= maxAttempts) {
      break;
    }
    const count = await strategy.locator.count().catch(() => 0);
    if (count === 0) {
      continue;
    }

    for (let i = 0; i < count; i += 1) {
      if (attempts >= maxAttempts) {
        break;
      }
      const row = strategy.locator.nth(i);
      if (!(await row.isVisible().catch(() => false))) {
        continue;
      }
      if (await isNegativeClickTarget(row)) {
        continue;
      }
      const text = ((await row.innerText().catch(() => "")) || "")
        .replace(/\s+/g, " ")
        .trim();
      if (
        text.length >= 12 &&
        suggestionLabelConflicts(text, query, description)
      ) {
        continue;
      }
      if (text.length >= 12 && !labelsMatch(text, description)) {
        const probes = buildDescriptionProbes(description, query);
        if (!probes.some((probe) => labelsMatch(text, probe))) {
          continue;
        }
      }

      attempts += 1;
      await clickRowWithPointer(page, row);

      if (options?.bridge && options?.placeId) {
        const committed = await options.bridge.waitForPlaceCommit(
          options.placeId,
          placeCommitMs,
        );
        if (committed) {
          return {
            clicked: true,
            matchedText: text,
            method: strategy.name,
          };
        }
        continue;
      }

      return {
        clicked: true,
        matchedText: text,
        method: strategy.name,
      };
    }
  }

  return { clicked: false, reason: "no-strategy-match" };
}

/**
 * 对齐人工操作：在 MotherShip styled-components 联想 DIV 上执行真实鼠标点击。
 * 为何：widget 不使用 Google pac/listbox；evaluate 派发事件不可靠，改用 locator + mouse。
 */
export async function clickMothershipStyledSuggestion(
  page: Page,
  description: string,
  query: AddressLookupInput,
  options?: {
    input?: Locator;
    candidateIndex?: number;
    skipReopen?: boolean;
    side?: "pickup" | "delivery";
    bridge?: AxelSelectionBridge;
    placeId?: string;
    maxStrategyAttempts?: number;
    preferLegacyFirst?: boolean;
    legacyOnly?: boolean;
    fastClick?: boolean;
    placeCommitTimeoutMs?: number;
  },
): Promise<{
  clicked: boolean;
  reason?: string;
  matchedText?: string;
  method?: string;
}> {
  await dismissCookieConsent(page);
  const rush = isRpaAddressRushMode();

  const placeCommitMs = options?.placeCommitTimeoutMs ?? 2_000;

  if (options?.legacyOnly) {
    const legacyOnly = await clickStyledRowByLabel(page, description, query);
    if (!legacyOnly.clicked) {
      return legacyOnly;
    }
    if (rush) {
      console.log(
        `[rpa] mothership-styled-click:ok method=legacy-only-rush "${(legacyOnly.matchedText ?? "").slice(0, 64)}"`,
      );
      return { ...legacyOnly, method: "legacy-only-rush" };
    }
    if (options?.bridge && options?.placeId) {
      const committed = await options.bridge.waitForPlaceCommit(
        options.placeId,
        placeCommitMs,
      );
      if (!committed) {
        return { clicked: false, reason: "legacy-click-no-place-request" };
      }
    }
    console.log(
      `[rpa] mothership-styled-click:ok method=legacy-only "${(legacyOnly.matchedText ?? "").slice(0, 64)}"`,
    );
    return { ...legacyOnly, method: "legacy-only" };
  }

  if (options?.preferLegacyFirst) {
    const legacyFast = await clickStyledRowByLabel(page, description, query);
    if (legacyFast.clicked) {
      if (rush) {
        return { ...legacyFast, method: "legacy-label-row-fast-rush" };
      }
      if (options?.bridge && options?.placeId) {
        const committed = await options.bridge.waitForPlaceCommit(
          options.placeId,
          placeCommitMs,
        );
        if (committed) {
          console.log(
            `[rpa] mothership-styled-click:ok method=legacy-label-row-fast "${(legacyFast.matchedText ?? "").slice(0, 64)}"`,
          );
          return { ...legacyFast, method: "legacy-label-row-fast" };
        }
      } else if (!options?.bridge) {
        return { ...legacyFast, method: "legacy-label-row-fast" };
      }
    }
  }

  let labels = await collectWidgetLabels(page);
  if (labels.length > 0) {
    console.log(
      `[rpa] mothership-styled-click:widget-labels ${JSON.stringify(labels.slice(0, 4))}`,
    );
  }

  const goldTargets = options?.side
    ? findGoldStandardTargets(description, query, options.side, 70)
    : [];
  if (goldTargets.length > 0) {
    console.log(
      `[rpa] mothership-styled-click:gold-targets ${goldTargets.length} best="${goldTargets[0]!.textPreview.slice(0, 48)}"`,
    );
  }

  const strategies = buildGoldDrivenLocators(page, description, query, {
    side: options?.side,
    goldTargets,
    omitScFallback: Boolean(options?.placeId),
  });

  const tryMultiStrategy = async (): Promise<{
    clicked: boolean;
    reason?: string;
    matchedText?: string;
    method?: string;
  }> => {
    const multi = await tryLocatorStrategies(
      page,
      strategies,
      description,
      query,
      {
        bridge: options?.bridge,
        placeId: options?.placeId,
        maxAttempts: options?.maxStrategyAttempts ?? 3,
        placeCommitTimeoutMs: placeCommitMs,
      },
    );
    if (multi.clicked) {
      return multi;
    }

    labels = await collectWidgetLabels(page);
    if (!labels.length) {
      return { clicked: false, reason: "no-labels" };
    }
    const idx = pickLabelIndex(
      labels,
      description,
      query,
      options?.candidateIndex ?? -1,
    );
    const targetLabel = labels[idx] ?? labels[0]!;
    const legacy = await clickStyledRowByLabel(page, targetLabel, query);
    if (!legacy.clicked) {
      return legacy;
    }
    if (rush) {
      return { ...legacy, method: "legacy-label-row-rush" };
    }
    if (options?.bridge && options?.placeId) {
      const committed = await options.bridge.waitForPlaceCommit(
        options.placeId,
        placeCommitMs,
      );
      if (!committed) {
        return { clicked: false, reason: "legacy-click-no-place-request" };
      }
    }
    return { ...legacy, method: "legacy-label-row" };
  };

  let result = await tryMultiStrategy();
  if (!result.clicked && options?.input && !options?.skipReopen) {
    await reopenStyledSuggestionDropdown(page, options.input, query, description);
    await waitForStyledSuggestionRows(page, [], query, 8_000);
    result = await tryMultiStrategy();
  }

  if (!result.clicked) {
    console.log(
      `[rpa] mothership-styled-click:miss reason=${result.reason ?? "unknown"}`,
    );
    return result;
  }

  if (!options?.fastClick) {
    await awaitHumanPacing(page);
    await page.waitForTimeout(250);
  }
  console.log(
    `[rpa] mothership-styled-click:ok method=${result.method ?? "unknown"} "${(result.matchedText ?? "").slice(0, 64)}"`,
  );
  return result;
}

/** search 已返回但 styled 联想 DOM 不可见时，用键盘索引点选 */
export async function tryStyledKeyboardCommit(
  page: Page,
  input: Locator,
  bridge: AxelSelectionBridge,
  placeId: string,
  candidateIndex: number,
): Promise<boolean> {
  await dismissCookieConsent(page);
  await input.scrollIntoViewIfNeeded().catch(() => undefined);
  await input.click({ force: true, timeout: 5_000 }).catch(() => undefined);
  await page.waitForTimeout(250);
  const steps = candidateIndex >= 0 ? candidateIndex + 1 : 1;
  for (let i = 0; i < steps; i += 1) {
    await page.keyboard.press("ArrowDown");
    await page.waitForTimeout(150);
  }
  await page.keyboard.press("Enter");
  const committed = await bridge.waitForPlaceCommit(placeId, 10_000);
  if (committed) {
    console.log(
      `[rpa] mothership-styled-keyboard:ok index=${candidateIndex} placeId=${placeId.slice(0, 16)}`,
    );
  }
  return committed;
}

/** @deprecated 保留单测字符串探针 */
export const FIND_MOTHERSHIP_SUGGESTION_TARGET = `() => ({ ok: false, reason: "use-locator-click" })`;
export const CLICK_MOTHERSHIP_STYLED_SUGGESTION = FIND_MOTHERSHIP_SUGGESTION_TARGET;
export const CLICK_WIDGET_SUGGESTION_BY_LABEL = FIND_MOTHERSHIP_SUGGESTION_TARGET;
export const LIST_STYLED_SUGGESTION_ROWS = `() => []`;
