import type { Locator, Page } from "playwright";
import { RPA_STREET_INPUT_CLICK_MS, RPA_WIDGET_HYDRATION_TIMEOUT_MS } from "@/lib/constants/rpa";
import { firstVisibleLocator, pageLocator } from "@/lib/rpa/locator";
import { clickFirstVisibleFromSpecs } from "@/lib/rpa/selector-specs";
import { isRpaAddressRushMode } from "@/lib/rpa/env";
import { RpaError } from "@/modules/rpa/errors";
import {
  resolveRPAContext,
  type RPAContext,
} from "@/workers/rpa/kernel/context";
import {
  stabilizeQuoteLandingPage,
  waitForQuoterWidgetHydrated,
} from "@/workers/rpa/page-prep";

const WIDGET_WAIT_MS = RPA_WIDGET_HYDRATION_TIMEOUT_MS;
const STREET_NAME_RE = /搜索地址|Search\s+(?:by\s+)?address/i;
const ADDRESS_SIDE_POLL_MS = 200;

function resolveCtx(pageOrCtx: Page | RPAContext): RPAContext {
  return resolveRPAContext(pageOrCtx);
}

function sectionSpecs(
  ctx: RPAContext,
  side: "pickup" | "delivery",
): string[] {
  return ctx.selectors.specs(
    side === "pickup"
      ? "RPA_SELECTOR_PICKUP_SECTION"
      : "RPA_SELECTOR_DELIVERY_SECTION",
  );
}

function streetSpecs(
  ctx: RPAContext,
  side: "pickup" | "delivery",
): string[] {
  return ctx.selectors.specs(
    side === "pickup"
      ? "RPA_SELECTOR_PICKUP_STREET"
      : "RPA_SELECTOR_DELIVERY_STREET",
  );
}

function sideLabel(side: "pickup" | "delivery"): string {
  return side === "pickup" ? "提货" : "派送";
}

async function isEditableStreetInput(loc: Locator): Promise<boolean> {
  if (!(await loc.isVisible().catch(() => false))) {
    return false;
  }
  const editable = await loc.isEditable().catch(() => false);
  if (!editable) {
    return false;
  }
  const readonly = await loc.getAttribute("readonly").catch(() => null);
  const ariaReadonly = await loc.getAttribute("aria-readonly").catch(() => null);
  const disabled = await loc.isDisabled().catch(() => false);
  return !disabled && readonly === null && ariaReadonly !== "true";
}

async function findEmptyEditableStreetInput(
  boxes: Locator,
  count: number,
): Promise<Locator | null> {
  for (let i = count - 1; i >= 0; i -= 1) {
    const loc = boxes.nth(i);
    if (!(await isEditableStreetInput(loc))) {
      continue;
    }
    const val = ((await loc.inputValue().catch(() => "")) || "")
      .replace(/\s+/g, " ")
      .trim();
    if (val.length < 8) {
      return loc;
    }
  }
  return null;
}

async function streetTextboxesInWidget(ctx: RPAContext): Promise<Locator> {
  return ctx.selectors.widget(ctx.page).getByRole("textbox", {
    name: STREET_NAME_RE,
  });
}

async function firstEditableInputInWidget(ctx: RPAContext): Promise<Locator | null> {
  const inputs = ctx.selectors.widget(ctx.page).locator("input");
  const count = await inputs.count().catch(() => 0);
  for (let i = 0; i < count; i += 1) {
    const loc = inputs.nth(i);
    if (await isEditableStreetInput(loc)) {
      return loc;
    }
  }
  return null;
}

async function firstVisibleInputInWidget(ctx: RPAContext): Promise<Locator | null> {
  const inputs = ctx.selectors.widget(ctx.page).locator("input");
  const count = await inputs.count().catch(() => 0);
  for (let i = 0; i < count; i += 1) {
    const loc = inputs.nth(i);
    if (await loc.isVisible().catch(() => false)) {
      return loc;
    }
  }
  return null;
}

async function findEditableStreetFromSpecs(
  ctx: RPAContext,
  specs: string[],
  preferLast: boolean,
): Promise<Locator | null> {
  const page = ctx.page;
  for (const spec of specs) {
    const root = pageLocator(page, spec);
    const count = await root.count().catch(() => 0);
    if (count === 0) {
      continue;
    }
    const indices = preferLast
      ? Array.from({ length: count }, (_, i) => count - 1 - i)
      : Array.from({ length: count }, (_, i) => i);
    for (const i of indices) {
      const loc = root.nth(i);
      if (await isEditableStreetInput(loc)) {
        return loc;
      }
    }
  }
  return null;
}

export async function visibleStreetInput(
  pageOrCtx: Page | RPAContext,
  side: "pickup" | "delivery",
  options?: { deliverySectionOpened?: boolean; enumerationMode?: boolean },
): Promise<Locator | null> {
  const ctx = resolveCtx(pageOrCtx);
  const preferLast = side === "delivery";
  const boxes = await streetTextboxesInWidget(ctx);
  const count = await boxes.count().catch(() => 0);

  if (side === "delivery" && options?.enumerationMode) {
    const indices = preferLast
      ? Array.from({ length: count }, (_, i) => count - 1 - i)
      : Array.from({ length: count }, (_, i) => i);
    for (const i of indices) {
      const loc = boxes.nth(i);
      if (await isEditableStreetInput(loc)) {
        return loc;
      }
    }
    return findEditableStreetFromSpecs(ctx, streetSpecs(ctx, side), preferLast);
  }

  if (side === "delivery") {
    if (!options?.deliverySectionOpened && count < 2) {
      return null;
    }
    if (count < 2) {
      const empty = await findEmptyEditableStreetInput(boxes, count);
      if (empty) {
        return empty;
      }
      return null;
    }
  }

  const indices = preferLast
    ? Array.from({ length: count }, (_, i) => count - 1 - i)
    : Array.from({ length: count }, (_, i) => i);
  for (const i of indices) {
    const loc = boxes.nth(i);
    if (await isEditableStreetInput(loc)) {
      return loc;
    }
  }
  return findEditableStreetFromSpecs(ctx, streetSpecs(ctx, side), preferLast);
}

async function tryReadyDeliveryStreetInput(
  ctx: RPAContext,
): Promise<Locator | null> {
  const input = await visibleStreetInput(ctx, "delivery", {
    deliverySectionOpened: true,
  });
  if (!input || !(await isEditableStreetInput(input))) {
    return null;
  }
  const val = ((await input.inputValue().catch(() => "")) || "")
    .replace(/\s+/g, " ")
    .trim();
  if (val.length >= 8) {
    return null;
  }
  return input;
}

/** 提货锁定后等待独立派送输入框（禁止复用仍含提货文案的单框） */
export async function waitForDeliveryStreetInput(
  pageOrCtx: Page | RPAContext,
  timeoutMs = 2_500,
): Promise<Locator> {
  const ctx = resolveCtx(pageOrCtx);

  const immediate = await tryReadyDeliveryStreetInput(ctx);
  if (immediate) {
    return immediate;
  }

  const deadline = Date.now() + timeoutMs;

  while (Date.now() < deadline) {
    const ready = await tryReadyDeliveryStreetInput(ctx);
    if (ready) {
      return ready;
    }

    await clickFirstVisibleFromSpecs(ctx.page, sectionSpecs(ctx, "delivery"));
    await ctx.page.waitForTimeout(250);

    const afterClick = await tryReadyDeliveryStreetInput(ctx);
    if (afterClick) {
      return afterClick;
    }
    await ctx.page.waitForTimeout(ADDRESS_SIDE_POLL_MS);
  }

  throw new RpaError(
    "STRUCT_CHANGE",
    "派送地址输入框未就绪（提货可能未锁定，仍占用同一搜索框）",
    { retryable: true },
  );
}

async function waitForQuoteWidget(ctx: RPAContext): Promise<void> {
  const widget = ctx.selectors.widget(ctx.page);
  await widget
    .waitFor({ state: "visible", timeout: WIDGET_WAIT_MS })
    .catch(() =>
      widget.waitFor({ state: "attached", timeout: WIDGET_WAIT_MS }),
    );
}

export async function clickStreetInput(
  input: Locator,
  side: "pickup" | "delivery",
): Promise<void> {
  const label = sideLabel(side);
  await input.scrollIntoViewIfNeeded().catch(() => undefined);
  if (!(await isEditableStreetInput(input))) {
    throw new RpaError(
      "STRUCT_CHANGE",
      `${label}地址输入框不可编辑（可能命中已锁定的提货框）`,
      { retryable: false },
    );
  }

  const focused = await input
    .focus({ timeout: RPA_STREET_INPUT_CLICK_MS })
    .then(() => true)
    .catch(() => false);

  if (focused) {
    return;
  }

  try {
    await input.click({ timeout: 3_000, force: true });
  } catch {
    throw new RpaError(
      "STRUCT_CHANGE",
      `${label}地址输入框无法聚焦（请检查 RPA_HEADLESS=false / VPN / storageState）`,
      { retryable: false },
    );
  }
}

export async function openAddressSide(
  pageOrCtx: Page | RPAContext,
  side: "pickup" | "delivery",
  options?: { enumerationMode?: boolean },
): Promise<Locator> {
  const ctx = resolveCtx(pageOrCtx);
  const page = ctx.page;
  ctx.state.markAddressSide(side);

  if (side === "pickup") {
    const visible = await firstVisibleInputInWidget(ctx);
    if (visible) {
      await visible.scrollIntoViewIfNeeded().catch(() => undefined);
      await visible.click({ timeout: 2_000, force: true }).catch(() => undefined);
      if (await isEditableStreetInput(visible)) {
        console.log("[rpa] openAddressSide:pickup 可见输入框激活成功");
        return visible;
      }
    }
    const direct = await firstEditableInputInWidget(ctx);
    if (direct) {
      console.log("[rpa] openAddressSide:pickup 直达输入框成功");
      await clickStreetInput(direct, side);
      return direct;
    }
    const existing = await visibleStreetInput(ctx, "pickup");
    if (existing && (await isEditableStreetInput(existing))) {
      console.log("[rpa] openAddressSide:pickup 快速通道成功");
      await clickStreetInput(existing, side);
      return existing;
    }
    console.log("[rpa] openAddressSide:pickup 快速通道失败，尝试点击分区");
  }

  if (side === "delivery" && !options?.enumerationMode) {
    if (isRpaAddressRushMode()) {
      const direct =
        (await visibleStreetInput(ctx, "delivery", {
          deliverySectionOpened: true,
        })) ??
        (await firstEditableInputInWidget(ctx)) ??
        (await firstVisibleInputInWidget(ctx));
      if (direct && (await isEditableStreetInput(direct))) {
        console.log("[rpa] openAddressSide:delivery visual-rush 直达输入框");
        await clickStreetInput(direct, side);
        return direct;
      }
    }

    // 快速通道1：直接检查是否有空的delivery输入框
    const ready = await tryReadyDeliveryStreetInput(ctx);
    if (ready) {
      console.log("[rpa] openAddressSide:delivery 快速通道1成功（空输入框）");
      await clickStreetInput(ready, side);
      return ready;
    }
    
    // 快速通道2：点击派送分区标签（含新版 Where to?）
    const deliverTo = ctx.selectors
      .widget(page)
      .getByText(/^(Deliver to|Delivery to|Where to\?)$/i)
      .first();
    if (await deliverTo.isVisible().catch(() => false)) {
      await deliverTo.click({ timeout: 2_000 }).catch(() => undefined);
      await page.waitForTimeout(80);
      const afterDeliverTo = await tryReadyDeliveryStreetInput(ctx);
      if (afterDeliverTo) {
        console.log("[rpa] openAddressSide:delivery 快速通道2成功（Deliver to点击）");
        await clickStreetInput(afterDeliverTo, side);
        return afterDeliverTo;
      }
    }
    
    console.log("[rpa] openAddressSide:delivery 快速通道失败，尝试分区 selector");
    await clickFirstVisibleFromSpecs(page, sectionSpecs(ctx, "delivery")).catch(
      () => undefined,
    );
    await page.waitForTimeout(200);
    const afterSection = await tryReadyDeliveryStreetInput(ctx);
    if (afterSection) {
      console.log("[rpa] openAddressSide:delivery 分区点击后输入框就绪");
      await clickStreetInput(afterSection, side);
      return afterSection;
    }

    const fallback = await visibleStreetInput(ctx, "delivery", {
      deliverySectionOpened: true,
    });
    if (fallback && (await isEditableStreetInput(fallback))) {
      await clickStreetInput(fallback, side);
      return fallback;
    }
    throw new RpaError(
      "STRUCT_CHANGE",
      "派送地址输入框未就绪（请确认提货已锁定）",
      { retryable: true },
    );
  }

  const sections = sectionSpecs(ctx, side);
  if (sections.length === 0) {
    throw new RpaError(
      "STRUCT_CHANGE",
      `${sideLabel(side)}分区 selector 未配置`,
      { retryable: false },
    );
  }

  await clickFirstVisibleFromSpecs(page, sections);

  const input = await visibleStreetInput(ctx, side, {
    deliverySectionOpened: side === "delivery",
    enumerationMode: options?.enumerationMode,
  });
  if (!input || !(await isEditableStreetInput(input))) {
    throw new RpaError(
      "STRUCT_CHANGE",
      `${sideLabel(side)}地址输入框不可见（请检查中英 selector）`,
      { retryable: false },
    );
  }
  await clickStreetInput(input, side);
  return input;
}

export async function waitForQuoteWidgetReady(
  pageOrCtx: Page | RPAContext,
  timeoutMs?: number,
): Promise<void> {
  const ctx = resolveCtx(pageOrCtx);
  await stabilizeQuoteLandingPage(ctx);

  const widgetWaitMs = timeoutMs ?? WIDGET_WAIT_MS;

  // 显式等待 quoter 资源加载 + widget hydration（en-US 首页渲染较慢，避免固定 sleep 提前进入）
  // 提货分区出现即返回；deadline 在 hydration 完成后计时，保证分区点击循环获得完整预算
  await waitForQuoterWidgetHydrated(ctx.page, widgetWaitMs).catch(() => undefined);

  const deadline = Date.now() + widgetWaitMs;
  while (Date.now() < deadline) {
    const pickupInput = await visibleStreetInput(ctx, "pickup");
    if (pickupInput && (await isEditableStreetInput(pickupInput))) {
      return;
    }

    await clickFirstVisibleFromSpecs(ctx.page, sectionSpecs(ctx, "pickup"));
    await ctx.page.waitForTimeout(400);
  }

  throw new RpaError(
    "STRUCT_CHANGE",
    "报价 widget 未就绪（提货地址输入不可见，可能为英文 UI 需更新 selector）",
    { retryable: false },
  );
}

/** @deprecated 使用 waitForQuoteWidgetReady */
export async function ensureQuoteFormVisible(page: Page): Promise<void> {
  await waitForQuoteWidgetReady(page);
}

export async function getVisibleStreetInput(
  pageOrCtx: Page | RPAContext,
  side: "pickup" | "delivery",
  options?: { skipOpenSide?: boolean },
): Promise<Locator> {
  const ctx = resolveCtx(pageOrCtx);
  if (!options?.skipOpenSide) {
    return openAddressSide(ctx, side);
  }
  const input = await visibleStreetInput(ctx, side, {
    deliverySectionOpened: side === "delivery",
  });
  if (!input) {
    throw new RpaError(
      "STRUCT_CHANGE",
      `${sideLabel(side)}地址输入框不可见`,
      { retryable: false },
    );
  }
  if (!(await isEditableStreetInput(input))) {
    throw new RpaError(
      "STRUCT_CHANGE",
      `${sideLabel(side)}地址输入框不可编辑`,
      { retryable: false },
    );
  }
  return input;
}
