/**
 * Flock Freight 可视全链回放 — 对齐 Priority1 visual-chain + 录制脚本
 * 录制：.rpa/recordings/flock-manual-20260713-152051.js
 */
import fs from "node:fs";
import path from "node:path";
import type { BrowserContext, Page } from "playwright";
import {
  getFlockMinQuotes,
  getFlockQuoteUrl,
  getFlockQuoteWaitMs,
  getFlockStorageStatePath,
  isFlockFreshSessionPerQuote,
  shouldLoadFlockStorageState,
  shouldSaveFlockStorageState,
  type FlockTestAccount,
} from "@/lib/flock/env";
import { hasEffectiveFlockLogin, mustForceFlockAccountLogin } from "@/lib/flock/login-context";
import { resolveFlockQuoteAccount } from "@/lib/flock/resolve-account";
import { launchRpaBrowser } from "@/lib/rpa/browser-launch";
import { createRpaBrowserContext } from "@/lib/rpa/browser-context";
import {
  formatExternalSiteNavigationError,
  gotoWithResilience,
  isTransientNavigationError,
} from "@/lib/rpa/page-goto";
import {
  isRpaHeaded,
  resolveRpaHeadless,
  resolveRpaSlowMoMs,
} from "@/lib/rpa/env";
import {
  clickFlockQuoteSubmit,
  fillFlockQuoteForm,
  waitForFlockQuoteForm,
} from "@/workers/rpa/flock/form-interactions";
import {
  ensureFlockLoggedIn,
  flockLoginRegistrationBlockedMessage,
} from "@/workers/rpa/flock/login";
import {
  flockQuoteLimitMessage,
  flockRegistrationErrorMessage,
} from "@/workers/rpa/flock/page-state";
import {
  completeFlockRegistrationIfNeeded,
  isFlockQuoteResultsPage,
  isFlockRegistrationPage,
} from "@/workers/rpa/flock/register-account";
import {
  detectFlockRatesPending,
  extractFlockQuoteCards,
  waitForFlockQuoteResults,
} from "@/workers/rpa/flock/quote-extract";
import { captureFlockStall, withFlockStallWatch } from "@/workers/rpa/flock/stall-watch";
import type {
  FlockQuoteInput,
  FlockRunQuoteResult,
} from "@/workers/rpa/flock/types";
import {
  isFlockPageClosedError,
  flockPageClosedMessage,
} from "@/workers/rpa/flock/page-alive";
import {
  reportFlockStage,
  type FlockStageReporter,
} from "@/lib/flock/rpa-progress";

function envHeadless(): boolean {
  return resolveRpaHeadless();
}

async function dwellIfHeaded(page: Page | null): Promise<void> {
  if (!isRpaHeaded() || !page) return;
  await page.waitForTimeout(5_000).catch(() => undefined);
}

function resolveStoragePath(): string {
  const raw = getFlockStorageStatePath();
  return path.isAbsolute(raw) ? raw : path.join(process.cwd(), raw);
}

async function persistStorageState(
  context: BrowserContext,
  statePath: string,
): Promise<void> {
  try {
    fs.mkdirSync(path.dirname(statePath), { recursive: true });
    await context.storageState({ path: statePath });
  } catch {
    /* ignore */
  }
}

async function afterSubmitWaitForQuotes(
  page: Page,
  account: FlockTestAccount,
  budgetMs: number,
): Promise<{ registered: boolean; errorMessage?: string }> {
  const deadline = Date.now() + budgetMs;
  const limitMsg = await flockQuoteLimitMessage(page);
  if (limitMsg) {
    return { registered: false };
  }

  let registered = false;
  if (await isFlockRegistrationPage(page)) {
    // 登录保障路径：禁止盲填随机注册
    if (hasEffectiveFlockLogin()) {
      return {
        registered: false,
        errorMessage: flockLoginRegistrationBlockedMessage(),
      };
    }
    registered = await completeFlockRegistrationIfNeeded(page, account);
    if (!registered) {
      return { registered: false };
    }
  }

  const remain = Math.max(500, deadline - Date.now());
  await waitForFlockQuoteResults(page, remain);

  if (!(await isFlockQuoteResultsPage(page))) {
    const leftover = Math.max(0, deadline - Date.now());
    if (leftover > 0) {
      await page
        .getByText(/Explore your custom quote|FlockDirect|Prices from/i)
        .first()
        .waitFor({ timeout: leftover })
        .catch(() => undefined);
    }
  }

  return { registered };
}

async function runOnPage(
  page: Page,
  input: FlockQuoteInput,
  account: FlockTestAccount,
  onStage?: FlockStageReporter,
): Promise<FlockRunQuoteResult & { registered?: boolean; accountExists?: boolean }> {
  try {
    return await runOnPageInner(page, input, account, onStage);
  } catch (error) {
    if (isFlockPageClosedError(error)) {
      return {
        ok: false,
        quotes: [],
        errorMessage: flockPageClosedMessage("查价流程"),
      };
    }
    throw error;
  }
}

async function runOnPageInner(
  page: Page,
  input: FlockQuoteInput,
  account: FlockTestAccount,
  onStage?: FlockStageReporter,
): Promise<FlockRunQuoteResult & { registered?: boolean; accountExists?: boolean }> {
  const quoteWaitMs = getFlockQuoteWaitMs();
  const minQuotes = getFlockMinQuotes();

  const limitBefore = await flockQuoteLimitMessage(page);
  if (limitBefore) {
    return { ok: false, quotes: [], errorMessage: limitBefore };
  }

  await reportFlockStage(onStage, "fill_form");
  const filled = await withFlockStallWatch(page, "填写查价表单", () =>
    fillFlockQuoteForm(page, input),
  );
  if (!filled.ok) {
    return { ok: false, quotes: [], errorMessage: filled.error };
  }

  await reportFlockStage(onStage, "submit");
  if (!(await clickFlockQuoteSubmit(page))) {
    return {
      ok: false,
      quotes: [],
      errorMessage: "STRUCT_CHANGE：未找到提交按钮",
    };
  }

  await reportFlockStage(onStage, "wait_quote");
  const { registered, errorMessage: afterErr } = await withFlockStallWatch(
    page,
    "提交后等待报价",
    () => afterSubmitWaitForQuotes(page, account, quoteWaitMs),
  );
  if (afterErr) {
    return { ok: false, quotes: [], registered, errorMessage: afterErr };
  }

  if (await isFlockRegistrationPage(page)) {
    if (hasEffectiveFlockLogin()) {
      void captureFlockStall(page, "结果-登录态仍注册页", 3_000).catch(
        () => undefined,
      );
      return {
        ok: false,
        quotes: [],
        registered,
        errorMessage: flockLoginRegistrationBlockedMessage(),
      };
    }
    const regErr = await flockRegistrationErrorMessage(page);
    void captureFlockStall(page, "结果-注册页卡住", 3_000).catch(
      () => undefined,
    );
    return {
      ok: false,
      quotes: [],
      registered,
      errorMessage:
        regErr ??
        "STRUCT_CHANGE：进入注册页但无法自动填写（请配置 FLOCK_TEST_* 或重新 npm run record:flock）",
      accountExists: !!regErr,
    };
  }

  const pending = await detectFlockRatesPending(page);
  if (pending.pending) {
    await captureFlockStall(page, "结果-官网仍在寻价", 3_000).catch(
      () => undefined,
    );
    return {
      ok: false,
      quotes: [],
      reference: pending.reference,
      registered,
      errorMessage: pending.errorMessage ?? undefined,
    };
  }

  const { quotes, reference } = await extractFlockQuoteCards(page);
  if (quotes.length < minQuotes) {
    const pendingLate = await detectFlockRatesPending(page);
    if (pendingLate.pending) {
      await captureFlockStall(page, "结果-官网仍在寻价", 3_000).catch(
        () => undefined,
      );
      return {
        ok: false,
        quotes: [],
        reference: pendingLate.reference ?? reference,
        registered,
        errorMessage: pendingLate.errorMessage ?? undefined,
      };
    }
    await captureFlockStall(page, "结果-无报价或超时", quoteWaitMs).catch(
      () => undefined,
    );
    return {
      ok: false,
      quotes,
      reference,
      registered,
      errorMessage: `RPA_DATA_INVALID：${quoteWaitMs / 1000}s 内未拿到报价（截图见 .rpa/flock-stalls/）`,
    };
  }

  await reportFlockStage(onStage, "done");
  return { ok: true, quotes, reference, registered };
}

export type FlockVisualChainOptions = {
  input: FlockQuoteInput;
  onStage?: FlockStageReporter;
  /** 内部：禁止 storage 回退重试 */
  _skipStorageRetry?: boolean;
};

/** 在已有 page 上执行查价（批量单浏览器复用） */
export async function runFlockQuoteOnPage(
  page: Page,
  input: FlockQuoteInput,
  account: FlockTestAccount,
  onStage?: FlockStageReporter,
): Promise<FlockRunQuoteResult> {
  const result = await runOnPage(page, input, account, onStage);
  const { registered: _r, accountExists: _a, ...out } = result;
  return out;
}

async function runFlockVisualChainOnce(
  options: FlockVisualChainOptions,
  loadStorage: boolean,
): Promise<FlockRunQuoteResult & { registered?: boolean; accountExists?: boolean }> {
  const url = getFlockQuoteUrl();
  const statePath = resolveStoragePath();
  const headless = envHeadless();
  const slowMo = resolveRpaSlowMoMs();
  const fresh = isFlockFreshSessionPerQuote();
  const loginMode = hasEffectiveFlockLogin();
  const account = resolveFlockQuoteAccount(options.input);
  console.log(
    `[flock-rpa] visual-chain headed=${!headless} slowMo=${slowMo ?? 0} fresh=${fresh} loginMode=${loginMode}`,
  );
  if (fresh) {
    console.log(
      `[flock-rpa] 无痕查价 email=${account.email} phone=${account.phone}`,
    );
  }
  if (loginMode) {
    console.log("[flock-rpa] DOM 保障路径：账号密码登录模式");
  }

  const browser = await launchRpaBrowser({
    headless,
    incognito: fresh,
    ...(slowMo !== undefined ? { slowMo } : {}),
  });
  let page: Page | null = null;
  try {
    const context = await createRpaBrowserContext(
      browser,
      loadStorage && fs.existsSync(statePath) ? { storageState: statePath } : {},
    );
    page = await context.newPage();

    try {
      await gotoWithResilience(page, url);
    } catch (error) {
      const raw = error instanceof Error ? error.message : String(error);
      return {
        ok: false,
        quotes: [],
        errorMessage: isTransientNavigationError({ message: raw })
          ? formatExternalSiteNavigationError("Flock Freight 官网", {
              message: raw,
            })
          : raw,
      };
    }

    const forceFresh = mustForceFlockAccountLogin();
    await reportFlockStage(options.onStage, "login");
    let login = await ensureFlockLoggedIn(page, context, {
      forceFreshLogin: forceFresh,
    });
    if (!login.ok && loginMode) {
      login = await ensureFlockLoggedIn(page, context, {
        reloginAttempt: true,
        forceFreshLogin: true,
      });
    }
    if (!login.ok) {
      return { ok: false, quotes: [], errorMessage: login.errorMessage };
    }

    await reportFlockStage(options.onStage, "open_quote");
    if (!(await waitForFlockQuoteForm(page))) {
      const limitMsg = await flockQuoteLimitMessage(page);
      await captureFlockStall(page, "入口-表单未出现", 20_000).catch(() => undefined);
      return {
        ok: false,
        quotes: [],
        errorMessage:
          limitMsg ??
          "QUOTE_ENTRY_UNAVAILABLE：查价表单未加载（截图见 .rpa/flock-stalls/）",
      };
    }

    const result = await runOnPage(page, options.input, account, options.onStage);

    if (
      (result.registered || login.didLogin) &&
      shouldSaveFlockStorageState()
    ) {
      await persistStorageState(context, statePath);
    }

    const accountExists =
      result.accountExists === true ||
      !!result.errorMessage?.includes("FLOCK_ACCOUNT_EXISTS");

    return { ...result, accountExists };
  } finally {
    await dwellIfHeaded(page);
    await browser.close();
  }
}

/**
 * 执行 Flock 官网查价全链（worker / probe 共用）
 * - 默认加载 .rpa/flock-storage.json（录制产出，可跳过注册）
 * - 仅注册成功时回写 storage，报价后绝不覆盖 storage
 * - 无 storage 且邮箱已注册时，自动用 storage 重试一次
 */
export async function runFlockVisualChain(
  options: FlockVisualChainOptions,
): Promise<FlockRunQuoteResult> {
  const statePath = resolveStoragePath();
  const storageExists = fs.existsSync(statePath);
  const loadStorage = shouldLoadFlockStorageState(storageExists);

  let result = await runFlockVisualChainOnce(options, loadStorage);

  if (
    !options._skipStorageRetry &&
    !isFlockFreshSessionPerQuote() &&
    !loadStorage &&
    storageExists &&
    result.accountExists
  ) {
    console.warn(
      "[flock-rpa] 邮箱已注册，回退加载 flock-storage.json 重试查价…",
    );
    result = await runFlockVisualChainOnce(
      { ...options, _skipStorageRetry: true },
      true,
    );
  }

  const { registered: _r, accountExists: _a, ...out } = result;
  return out;
}
