import type { Page } from "playwright";
import { pageLocator } from "@/lib/rpa/locator";
import { RpaError } from "@/modules/rpa/errors";
import { isRpaAddressRushMode, isRpaBlindAddressFlow } from "@/lib/rpa/env";
import { isCargoEditorOpen, isCargoFieldsVisible } from "@/workers/rpa/cargo-editor";
import {
  type ExpectedCargoWeight,
  probeWeightMatchesExpected,
} from "@/workers/rpa/cargo-weight";
import { isAddressesVisuallyCompleteForCargo } from "@/workers/rpa/address-commit";
import { dismissCookieConsent } from "@/workers/rpa/page-prep";

const POLL_MS = 200;
const COMMIT_WAIT_MS = 12_000;
const ADDRESS_SETTLE_MS = 8_000;

const CARGO_PLACEHOLDER_RE =
  /pallet|weight|length|width|height|托盘|重量|长度|宽度|高度/i;

export type CargoCommitProbe = {
  hasSummaryChip: boolean;
  visibleEditableCargoInputs: number;
  editorOpen: boolean;
  internalPalletCount: number | null;
  internalWeight: number | null;
  widgetTextPreview: string;
};

/** 货物 committed 校验期望（单托 weightLb + palletCount） */
export type ExpectedCargoCommit = {
  palletCount?: number;
  weight?: ExpectedCargoWeight;
};

const INVALID_WEIGHT_SUMMARY_RE = /-\s*(?:Lbs|kg|公斤)\b/i;

function parseWeightFromSummary(text: string): number | null {
  const patterns = [
    /x\s*(\d{2,5})\s*Lbs/i,
    /(\d{2,5})Lbs/i,
    /\b(\d{2,5})\s*(?:lbs?|kg|公斤)\b/i,
  ];
  for (const re of patterns) {
    const m = text.match(re);
    if (m) {
      const n = Number(m[1]);
      if (Number.isFinite(n) && n > 0) {
        return n;
      }
    }
  }
  return null;
}

export function hasValidWeightInSummary(text: string): boolean {
  if (INVALID_WEIGHT_SUMMARY_RE.test(text)) {
    return false;
  }
  return parseWeightFromSummary(text) !== null;
}

function cargoValuesMatchExpected(
  probe: CargoCommitProbe,
  expected?: ExpectedCargoCommit,
): boolean {
  if (!expected) {
    return true;
  }
  if (
    expected.palletCount !== undefined &&
    probe.internalPalletCount !== null &&
    probe.internalPalletCount !== expected.palletCount
  ) {
    return false;
  }
  if (expected.weight && !probeWeightMatchesExpected(probe, expected.weight)) {
    return false;
  }
  return true;
}

/** 纯函数：是否已达 committed（非仅 UI 可见） */
export function evaluateCargoCommitted(
  probe: CargoCommitProbe,
  expected?: ExpectedCargoCommit | number,
): boolean {
  const normalized: ExpectedCargoCommit | undefined =
    typeof expected === "number" ? { palletCount: expected } : expected;

  const summaryComplete =
    probe.hasSummaryChip &&
    !INVALID_WEIGHT_SUMMARY_RE.test(probe.widgetTextPreview) &&
    hasValidWeightInSummary(probe.widgetTextPreview) &&
    (/\d+W,\s*x\s*\d+L/i.test(probe.widgetTextPreview) ||
      (probe.internalPalletCount !== null && probe.internalPalletCount > 0));

  if (summaryComplete) {
    if (
      normalized?.palletCount !== undefined &&
      probe.internalPalletCount !== null &&
      probe.internalPalletCount !== normalized.palletCount
    ) {
      return false;
    }
    if (normalized?.weight && !probeWeightMatchesExpected(probe, normalized.weight)) {
      return false;
    }
    if (normalized?.palletCount !== undefined && probe.internalPalletCount !== null) {
      return probe.internalPalletCount === normalized.palletCount;
    }
    return cargoValuesMatchExpected(probe, normalized);
  }

  if (probe.visibleEditableCargoInputs > 0) {
    return false;
  }
  if (probe.editorOpen && probe.visibleEditableCargoInputs > 0) {
    return false;
  }
  if (!probe.hasSummaryChip) {
    return false;
  }
  if (INVALID_WEIGHT_SUMMARY_RE.test(probe.widgetTextPreview)) {
    return false;
  }
  const palletOk =
    probe.internalPalletCount !== null && probe.internalPalletCount > 0;
  const weightOk =
    probe.internalWeight !== null && probe.internalWeight > 0;
  const weightSummaryOk = hasValidWeightInSummary(probe.widgetTextPreview);
  const dimOk = /\d+W,\s*x\s*\d+L/i.test(probe.widgetTextPreview);
  if (!palletOk && !dimOk) {
    return false;
  }
  if (!weightOk && !weightSummaryOk) {
    return false;
  }
  if (normalized?.palletCount !== undefined && palletOk) {
    if (probe.internalPalletCount !== normalized.palletCount) {
      return false;
    }
  }
  if (normalized?.weight && !probeWeightMatchesExpected(probe, normalized.weight)) {
    return false;
  }
  return cargoValuesMatchExpected(probe, normalized);
}

export async function probeCargoCommitState(
  page: Page,
  widgetSelector: string,
): Promise<CargoCommitProbe> {
  return page.evaluate(
    ({ sel, cargoPhReSource }) => {
      const widget = document.querySelector(sel);
      if (!widget) {
        return {
          hasSummaryChip: false,
          visibleEditableCargoInputs: 0,
          editorOpen: false,
          internalPalletCount: null,
          internalWeight: null,
          widgetTextPreview: "",
        };
      }

      const cargoPhRe = new RegExp(cargoPhReSource, "i");
      const innerText = (
        (widget as HTMLElement).innerText ??
        widget.textContent ??
        ""
      )
        .replace(/\s+/g, " ")
        .trim();

      const hasSummaryChip =
        /\d+\s*托盘s?/i.test(innerText) ||
        /\d+\s*pallets?/i.test(innerText) ||
        /\d+W,\s*x\s*\d+L/i.test(innerText) ||
        /\b\d{2,5}\s*(lbs?|kg|公斤)\b/i.test(innerText);

      let visibleEditableCargoInputs = 0;
      let internalPalletCount: number | null = null;
      let internalWeight: number | null = null;

      const inputs = widget.querySelectorAll<HTMLInputElement>("input");
      for (const inp of inputs) {
        const ph = inp.placeholder ?? "";
        if (!cargoPhRe.test(ph)) {
          continue;
        }
        const val = inp.value?.trim() ?? "";
        if (/托盘|pallet/i.test(ph) && val) {
          const n = Number(val);
          if (Number.isFinite(n) && n > 0) {
            internalPalletCount = n;
          }
        }
        if (/weight|重量|公斤|lbs|kg/i.test(ph) && val) {
          const n = Number(val);
          if (Number.isFinite(n) && n > 0) {
            internalWeight = n;
          }
        }
        const visible = inp.offsetParent !== null;
        const editable =
          visible &&
          !inp.disabled &&
          inp.readOnly === false &&
          inp.getAttribute("aria-readonly") !== "true";
        if (editable) {
          visibleEditableCargoInputs += 1;
        }
      }

      const editorOpen = Boolean(
        document.querySelector(
          '[role="dialog"][aria-modal="true"], [aria-modal="true"][data-state="open"]',
        ),
      );

      return {
        hasSummaryChip,
        visibleEditableCargoInputs,
        editorOpen,
        internalPalletCount:
          internalPalletCount ??
          (() => {
            const m = innerText.match(/(\d+)\s*(?:pallet|托盘)s?/i);
            return m ? Number(m[1]) : null;
          })(),
        internalWeight:
          internalWeight ??
          (() => {
            const patterns = [
              /x\s*(\d{2,5})\s*Lbs/i,
              /(\d{2,5})Lbs/i,
              /\b(\d{2,5})\s*(?:lbs?|kg|公斤)\b/i,
            ];
            for (const re of patterns) {
              const m = innerText.match(re);
              if (m) {
                const n = Number(m[1]);
                if (Number.isFinite(n) && n > 0) {
                  return n;
                }
              }
            }
            return null;
          })(),
        widgetTextPreview: innerText.slice(0, 220),
      };
    },
    { sel: widgetSelector, cargoPhReSource: CARGO_PLACEHOLDER_RE.source },
  );
}

async function landingPopoverHasCargo(page: Page): Promise<boolean> {
  return page.evaluate(() => {
    const pallet = document.querySelector<HTMLInputElement>(
      'input[placeholder*="How many pallets" i]',
    );
    const weight = document.querySelector<HTMLInputElement>(
      'input[placeholder*="Weight" i]',
    );
    return Boolean(pallet?.value?.trim() && weight?.value?.trim());
  });
}

export async function isCargoCommittedState(
  page: Page,
  widgetSelector: string,
  expected?: ExpectedCargoCommit | number,
): Promise<boolean> {
  const normalized: ExpectedCargoCommit | undefined =
    typeof expected === "number" ? { palletCount: expected } : expected;
  if (normalized && (await landingPopoverMatchesExpected(page, normalized))) {
    return true;
  }
  if (!normalized && (await landingPopoverHasCargo(page))) {
    return true;
  }
  const editorOpen = await isCargoEditorOpen(page);
  const probe = await probeCargoCommitState(page, widgetSelector);
  return evaluateCargoCommitted(
    { ...probe, editorOpen: probe.editorOpen || editorOpen },
    expected,
  );
}

/** 地址搜索框是否仍可编辑 */
export async function isAddressSearchEditable(
  page: Page,
  widgetSelector: string,
): Promise<boolean> {
  return page.evaluate((sel) => {
    const widget = document.querySelector(sel);
    if (!widget) {
      return true;
    }
    const inputs = widget.querySelectorAll<HTMLInputElement>("input");
    for (const inp of inputs) {
      const ph = inp.placeholder ?? "";
      if (!/search|搜索/i.test(ph)) {
        continue;
      }
      const visible = inp.offsetParent !== null;
      const editable =
        !inp.disabled &&
        inp.readOnly === false &&
        inp.getAttribute("aria-readonly") !== "true";
      if (visible && editable) {
        return true;
      }
    }
    return false;
  }, widgetSelector);
}

/** 双地址必须已写入 widget（进入货物步前） */
export async function assertBothAddressesLocked(
  page: Page,
  widgetSelector: string,
): Promise<void> {
  if (await isAddressesVisuallyCompleteForCargo(page, widgetSelector)) {
    console.log("[rpa] assertBothAddressesLocked: 视觉已确认双地址，跳过网络闸门");
    return;
  }
  const deadline = Date.now() + ADDRESS_SETTLE_MS;
  while (Date.now() < deadline) {
    if (await isAddressesLockedForSubmit(page, widgetSelector)) {
      return;
    }
    await page.waitForTimeout(POLL_MS);
  }
  const text = await page
    .locator(widgetSelector)
    .innerText()
    .catch(() => "");
  throw new RpaError(
    "ADDRESS_NOT_CONFIRMED",
    `双地址未锁定，无法进入货物步（preview=${text.slice(0, 120)}）`,
    { retryable: true },
  );
}

/** 纯函数：双地址是否已锁定（供单测） */
export function evaluateAddressesLockedForSubmit(
  widgetText: string,
  addressSearchEditable: boolean,
): boolean {
  if (addressSearchEditable) {
    return false;
  }
  const normalized = widgetText.replace(/\s+/g, " ");
  const zipCount = (normalized.match(/\b\d{5}(?:-\d{4})?\b/g) ?? []).length;
  const stateCount = (normalized.match(/,\s*[A-Z]{2}\b/g) ?? []).length;
  const streetMarkers = normalized.match(/\d{3,5}\s+\S+/g) ?? [];
  const cityPairs =
    (normalized.match(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*,\s*[A-Z]{2}\b/g) ??
      []).length;
  return (
    stateCount >= 2 ||
    zipCount >= 2 ||
    streetMarkers.length >= 2 ||
    cityPairs >= 2
  );
}

/** submit 前地址是否已锁定（必须双地址证据，禁止单州码弱放行） */
export async function isAddressesLockedForSubmit(
  page: Page,
  widgetSelector: string,
): Promise<boolean> {
  const text = await page
    .locator(widgetSelector)
    .innerText()
    .catch(() => "");
  const addressSearchEditable = await isAddressSearchEditable(
    page,
    widgetSelector,
  );
  return evaluateAddressesLockedForSubmit(text, addressSearchEditable);
}

/** 收起内联货物步以便 submit（不点「货运详情」避免重置向导） */
export async function collapseInlineCargoForSubmit(
  page: Page,
  widgetSelector: string,
): Promise<void> {
  const widget = pageLocator(page, widgetSelector);

  for (const pattern of [/\d+W,\s*x\s*\d+L/i, /\d+\s*托盘s?/i, /\d+\s*pallets?/i]) {
    const summary = widget.getByText(pattern).first();
    if (await summary.isVisible().catch(() => false)) {
      await summary
        .click({ timeout: 3_000, force: true })
        .catch(() => undefined);
      await page.waitForTimeout(350);
      break;
    }
  }

  if (await isCargoFieldsVisible(page, widgetSelector)) {
    await page.keyboard.press("Escape").catch(() => undefined);
    await widget
      .click({ position: { x: 300, y: 60 }, timeout: 3_000, force: true })
      .catch(() => undefined);
    await page.waitForTimeout(300);
  }
}

const CARGO_OVERLAY_SELECTORS = ['[role="dialog"]', '[aria-modal="true"]'] as const;

/** 仅 dialog/modal 货物弹层；widget 内联 Freight details 不算弹层 */
export async function isCargoOverlayPopoverOpen(page: Page): Promise<boolean> {
  for (const selector of CARGO_OVERLAY_SELECTORS) {
    const overlays = page.locator(selector);
    const count = await overlays.count().catch(() => 0);
    for (let i = 0; i < count; i++) {
      const overlay = overlays.nth(i);
      if (!(await overlay.isVisible().catch(() => false))) {
        continue;
      }
      const cargoInput = overlay
        .locator(
          'input[placeholder*="pallet" i], input[placeholder*="weight" i], input[placeholder*="托盘" i], input[placeholder*="重量" i]',
        )
        .first();
      if (await cargoInput.isVisible().catch(() => false)) {
        return true;
      }
    }
  }
  return false;
}

/** 仅当 overlay 货物弹层仍打开时 Escape 收起（内联字段勿 Escape，会重置地址） */
export async function dismissCargoPopoverOnly(
  page: Page,
  widgetSelector: string,
): Promise<void> {
  void widgetSelector;
  if (!(await isCargoOverlayPopoverOpen(page))) {
    return;
  }
  console.log("[rpa] cargo: 收起货物弹层（Escape，不点击向导步骤）");
  await page.keyboard.press("Escape").catch(() => undefined);
  await page.waitForTimeout(200);
}

/** 尝试提交内联货物步（不点击地址 chip / 步骤标签，避免重置向导） */
export async function attemptCargoCommit(
  page: Page,
  widgetSelector: string,
  blindFlow = false,
): Promise<void> {
  if (blindFlow || isRpaBlindAddressFlow() || isRpaAddressRushMode()) {
    await dismissCargoPopoverOnly(page, widgetSelector);
    return;
  }

  const widget = pageLocator(page, widgetSelector);

  await page.keyboard.press("Tab").catch(() => undefined);
  await page.keyboard.press("Enter").catch(() => undefined);
  await page.waitForTimeout(250);

  const summaryPatterns = [
    /\d+\s*托盘s?/i,
    /\d+\s*pallets?/i,
    /\d+W,\s*x\s*\d+L/i,
  ];
  for (const pattern of summaryPatterns) {
    const summary = widget.getByText(pattern).first();
    if (await summary.isVisible().catch(() => false)) {
      await summary
        .click({ timeout: 3_000, force: true })
        .catch(() => undefined);
      await page.waitForTimeout(400);
      return;
    }
  }

  await page.keyboard.press("Escape").catch(() => undefined);
  await widget
    .click({ position: { x: 280, y: 220 }, timeout: 3_000, force: true })
    .catch(() => undefined);
  await page.waitForTimeout(300);
}

async function landingPopoverMatchesExpected(
  page: Page,
  expected: ExpectedCargoCommit,
): Promise<boolean> {
  const state = await page.evaluate(() => {
    const pallet = document.querySelector<HTMLInputElement>(
      'input[placeholder*="How many pallets" i]',
    );
    const weight = document.querySelector<HTMLInputElement>(
      'input[placeholder*="Weight" i]',
    );
    return {
      pallet: pallet?.value?.trim() ?? "",
      weight: weight?.value?.trim() ?? "",
    };
  });
  if (!state.pallet || !state.weight) {
    return false;
  }
  if (
    expected.palletCount !== undefined &&
    Number(state.pallet) !== expected.palletCount
  ) {
    return false;
  }
  if (expected.weight) {
    const actual = Number(state.weight);
    const target = Number(expected.weight.displayValue);
    if (Number.isFinite(actual) && Number.isFinite(target) && actual !== target) {
      return false;
    }
  }
  return true;
}

export async function waitForCargoCommittedState(
  page: Page,
  widgetSelector: string,
  expected?: ExpectedCargoCommit | number,
): Promise<CargoCommitProbe> {
  const normalized: ExpectedCargoCommit | undefined =
    typeof expected === "number" ? { palletCount: expected } : expected;

  if (normalized && (await landingPopoverMatchesExpected(page, normalized))) {
    console.log("[rpa] cargo-commit: ok（landing Freight details 弹层已填）");
    return probeCargoCommitState(page, widgetSelector);
  }

  const deadline = Date.now() + COMMIT_WAIT_MS;
  let last: CargoCommitProbe | null = null;

  while (Date.now() < deadline) {
    const probe = await probeCargoCommitState(page, widgetSelector);
    const editorOpen = await isCargoEditorOpen(page);
    const merged = { ...probe, editorOpen: probe.editorOpen || editorOpen };
    last = merged;

    if (normalized && (await landingPopoverMatchesExpected(page, normalized))) {
      console.log("[rpa] cargo-commit: ok（landing Freight details 弹层已填）");
      return merged;
    }

    if (evaluateCargoCommitted(merged, normalized)) {
      console.log(
        `[rpa] cargo-commit: ok pallet=${merged.internalPalletCount ?? "n/a"} weight=${merged.internalWeight ?? "n/a"} editable=${merged.visibleEditableCargoInputs}`,
      );
      return merged;
    }

    await page.waitForTimeout(POLL_MS);
  }

  const failed = last ?? (await probeCargoCommitState(page, widgetSelector));
  console.log(`[rpa] cargo-commit: FAIL ${JSON.stringify(failed)}`);
  const weightHint = normalized?.weight
    ? ` expectedWeight=${normalized.weight.weightLbPerPallet}lb/pallet`
    : "";
  throw new RpaError(
    normalized?.weight &&
      failed.hasSummaryChip &&
      !probeWeightMatchesExpected(failed, normalized.weight)
      ? "CARGO_WEIGHT_MISMATCH"
      : "CARGO_NOT_COMMITTED",
    normalized?.weight &&
      failed.hasSummaryChip &&
      !probeWeightMatchesExpected(failed, normalized.weight)
      ? `货物重量与请求不一致（widget=${failed.internalWeight ?? failed.widgetTextPreview.slice(0, 80)}${weightHint}）`
      : `货物未进入 committed 态（summary=${failed.hasSummaryChip} editable=${failed.visibleEditableCargoInputs} pallet=${failed.internalPalletCount}）`,
    { retryable: true },
  );
}

export async function assertCargoCommittedState(
  page: Page,
  widgetSelector: string,
  expected?: ExpectedCargoCommit | number,
): Promise<void> {
  if (await isCargoCommittedState(page, widgetSelector, expected)) {
    return;
  }
  const probe = await probeCargoCommitState(page, widgetSelector);
  throw new RpaError(
    "CARGO_NOT_COMMITTED",
    `submit 前货物未 committed（${JSON.stringify(probe)}）`,
    { retryable: true },
  );
}

/** 收起地址编辑态，确保进入货物步前无未锁定 street 输入 */
export async function settleAddressFieldsForCargo(
  page: Page,
  widgetSelector: string,
): Promise<void> {
  await dismissCookieConsent(page);
  const widget = pageLocator(page, widgetSelector);
  const deadline = Date.now() + ADDRESS_SETTLE_MS;

  while (Date.now() < deadline) {
    if (await isAddressesVisuallyCompleteForCargo(page, widgetSelector)) {
      console.log("[rpa] settleAddressFieldsForCargo: 视觉双地址已就绪，进入货物步");
      return;
    }
    if (await isAddressesLockedForSubmit(page, widgetSelector)) {
      return;
    }
    const hasEditableStreet = await page.evaluate((sel) => {
      const root = document.querySelector(sel);
      if (!root) {
        return true;
      }
      const inputs = root.querySelectorAll<HTMLInputElement>("input");
      for (const inp of inputs) {
        const ph = inp.placeholder ?? "";
        if (!/search|搜索/i.test(ph)) {
          continue;
        }
        const visible = inp.offsetParent !== null;
        const editable =
          !inp.disabled &&
          inp.readOnly === false &&
          inp.getAttribute("aria-readonly") !== "true";
        if (visible && editable) {
          return true;
        }
      }
      return false;
    }, widgetSelector);

    if (!hasEditableStreet) {
      return;
    }

    await page.keyboard.press("Escape").catch(() => undefined);
    await widget
      .click({ position: { x: 24, y: 24 }, timeout: 3_000, force: true })
      .catch(() => undefined);
    await page.waitForTimeout(350);
  }

  throw new RpaError(
    "STRUCT_CHANGE",
    "地址编辑态未收起，无法进入货物步",
    { retryable: true },
  );
}

/** @deprecated 使用 isCargoCommittedState */
export async function isCargoSummaryLocked(
  page: Page,
  widgetSelector: string,
  expectedPalletCount?: number,
): Promise<boolean> {
  return isCargoCommittedState(page, widgetSelector, expectedPalletCount);
}

/** 关闭 cargo popover / drawer */
export async function closeCargoEditor(
  page: Page,
  widgetSelector?: string,
): Promise<void> {
  if (widgetSelector) {
    const widget = pageLocator(page, widgetSelector);
    await page.keyboard.press("Escape").catch(() => undefined);
    await widget
      .click({ position: { x: 180, y: 100 }, timeout: 3_000, force: true })
      .catch(() => undefined);
    await page.waitForTimeout(400);
  }

  if (!(await isCargoEditorOpen(page))) {
    return;
  }

  await page.keyboard.press("Escape").catch(() => undefined);
  await page.waitForTimeout(400);

  if (!(await isCargoEditorOpen(page))) {
    return;
  }

  const dismissTargets = [
    page.locator("#react-quoter-landing-plugin").first(),
    page.getByRole("button", { name: /done|save|apply|确认|完成/i }).first(),
  ];

  for (const loc of dismissTargets) {
    if (!(await loc.isVisible().catch(() => false))) {
      continue;
    }
    await loc.click({ timeout: 3_000, force: true }).catch(() => undefined);
    await page.waitForTimeout(400);
    if (!(await isCargoEditorOpen(page))) {
      return;
    }
  }

  await page.mouse.click(12, 12).catch(() => undefined);
  await page.waitForTimeout(400);
}

/** @deprecated 使用 waitForCargoCommittedState */
export async function waitForCargoSummaryLocked(
  page: Page,
  widgetSelector: string,
  expectedPalletCount?: number,
): Promise<void> {
  await waitForCargoCommittedState(page, widgetSelector, expectedPalletCount);
}
