import { mkdirSync } from "node:fs";
import { join } from "node:path";
import type { Frame, Page, Response } from "playwright";
import type { QuoteItem } from "@/modules/providers/quote-provider";
import { compareUiTierSortOrder } from "@/lib/constants/quote";
import { isAxelQuoteResponseUrl } from "@/workers/rpa/quote-capture/axel-contract";
import { decodeAxelQuoteBodyLenient } from "@/workers/rpa/quote-capture/payload-decode";
import { isInlineQuotePanelVisible } from "@/workers/rpa/quote-capture/post-submit-navigation";

type BrowseRoot = Page | Frame;

type TruckTabSpec = {
  serviceLevel: string;
  tabLabels: string[];
};

const TRUCK_TABS: TruckTabSpec[] = [
  {
    serviceLevel: "standard",
    tabLabels: ["Shared truck", "共享卡车"],
  },
  {
    serviceLevel: "dedicated",
    tabLabels: ["Dedicated truck", "专属卡车"],
  },
];

const QUOTE_SCREENSHOT_DIR = join(process.cwd(), ".rpa", "probe-widget-la-pomona");

function tierKey(item: QuoteItem): string {
  return `${item.serviceLevel}/${item.rateOption}`;
}

function sortItems(items: QuoteItem[]): QuoteItem[] {
  return [...items].sort((a, b) =>
    compareUiTierSortOrder(
      a.serviceLevel,
      a.rateOption,
      b.serviceLevel,
      b.rateOption,
    ),
  );
}

function toQuoteItem(serviceLevel: string, price: number): QuoteItem {
  return {
    serviceLevel,
    rateOption: "bestValue",
    carrier: "Mothership",
    transitDays: "待确认",
    transitDescription: "Widget Tab 读价",
    rawFreight: price,
    surcharges: 0,
    rawTotal: price,
  };
}

function isTransitEmpty(value: string | undefined): boolean {
  return !value?.trim() || value.trim() === "—";
}

/** Tab 展示价覆盖金额，保留 axel 时效/承运商等元数据 */
export function mergeWidgetTabQuotePrice(
  existing: QuoteItem | undefined,
  tabItem: QuoteItem,
): QuoteItem {
  if (!existing) {
    return {
      ...tabItem,
      transitDays: isTransitEmpty(tabItem.transitDays)
        ? "待确认"
        : tabItem.transitDays,
      transitDescription: isTransitEmpty(tabItem.transitDescription)
        ? "Widget Tab 读价"
        : tabItem.transitDescription,
    };
  }
  return {
    ...existing,
    rawFreight: tabItem.rawFreight,
    rawTotal: tabItem.rawTotal,
  };
}

function tabNamePattern(label: string): RegExp {
  const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  return new RegExp(`^\\s*${escaped}\\s*$`, "i");
}

/** 探针/日志：档位 + 官网 Tab 文案 */
export function formatWidgetQuoteTierLabel(item: QuoteItem): string {
  const base = `${item.serviceLevel}/${item.rateOption}`;
  if (item.serviceLevel === "dedicated") {
    return `${base} (Dedicated truck)`;
  }
  if (item.serviceLevel === "standard" && item.rateOption === "bestValue") {
    return `${base} (Shared truck · Best value)`;
  }
  return base;
}

function browseRoots(page: Page): BrowseRoot[] {
  const frames = page.frames();
  const ordered: BrowseRoot[] = [];
  for (const frame of frames) {
    const url = frame.url();
    if (/dashboard\.mothership|quoter|sign-up|mothership\.com/i.test(url)) {
      ordered.push(frame);
    }
  }
  if (!ordered.includes(page)) {
    ordered.push(page);
  }
  for (const frame of frames) {
    if (!ordered.includes(frame)) {
      ordered.push(frame);
    }
  }
  return ordered;
}

function mergeAxelBodies(
  merged: Map<string, QuoteItem>,
  bodies: unknown[],
): void {
  for (const body of bodies) {
    const decoded = decodeAxelQuoteBodyLenient(body);
    if (!decoded) {
      continue;
    }
    for (const item of decoded) {
      merged.set(tierKey(item), item);
    }
  }
}

async function captureWidgetQuoteScreenshot(
  page: Page,
  serviceLevel: string,
  tag: string,
): Promise<string | null> {
  try {
    mkdirSync(QUOTE_SCREENSHOT_DIR, { recursive: true });
    const file = join(
      QUOTE_SCREENSHOT_DIR,
      `widget-quote-${serviceLevel}-${tag}-${Date.now()}.png`,
    );
    await page.screenshot({ path: file, fullPage: false });
    console.log(`[rpa] widget-quote: 截图 ${file}`);
    return file;
  } catch {
    return null;
  }
}

/** Playwright 点 Tab（button / tab / pill div） */
async function clickTruckTabPlaywright(
  root: BrowseRoot,
  label: string,
): Promise<boolean> {
  const pattern = tabNamePattern(label);
  const locators = [
    root.getByRole("button", { name: pattern }),
    root.getByRole("tab", { name: pattern }),
    root
      .locator('button, [role="button"], [role="tab"], div, span')
      .filter({ hasText: pattern }),
    root.getByText(pattern),
  ];

  for (const loc of locators) {
    const el = loc.first();
    try {
      if (!(await el.isVisible({ timeout: 1_000 }))) {
        continue;
      }
      await el.scrollIntoViewIfNeeded().catch(() => undefined);
      await el.click({ timeout: 8_000, force: true });
      return true;
    } catch {
      /* 尝试下一选择器 */
    }
  }
  return false;
}

/** evaluate 深搜 shadow DOM 点 Tab（Playwright 失败时回退） */
async function clickTruckTabEvaluate(
  root: BrowseRoot,
  label: string,
): Promise<boolean> {
  return root
    .evaluate((tabLabel) => {
      const norm = (s: string) => s.replace(/\s+/g, " ").trim();
      const target = norm(tabLabel);
      const out: Element[] = [];
      const visit = (node: Element) => {
        out.push(node);
        if (node.shadowRoot) {
          for (const c of node.shadowRoot.querySelectorAll("*")) {
            visit(c);
          }
        }
      };
      for (const el of document.querySelectorAll("*")) {
        visit(el);
      }
      const candidates = out.filter((el) => norm(el.textContent || "") === target);
      candidates.sort(
        (a, b) => (a.textContent || "").length - (b.textContent || "").length,
      );
      for (const el of candidates) {
        const rect = el.getBoundingClientRect();
        if (rect.width < 4 || rect.height < 4) {
          continue;
        }
        (el as HTMLElement).click();
        return true;
      }
      return false;
    }, label)
    .catch(() => false);
}

async function clickTruckTabByMouse(page: Page, label: string): Promise<boolean> {
  for (const root of browseRoots(page)) {
    const coords = await root
      .evaluate((tabLabel) => {
        const norm = (s: string) => s.replace(/\s+/g, " ").trim();
        const target = norm(tabLabel);
        const out: Element[] = [];
        const visit = (node: Element) => {
          out.push(node);
          if (node.shadowRoot) {
            for (const c of node.shadowRoot.querySelectorAll("*")) {
              visit(c);
            }
          }
        };
        for (const el of document.querySelectorAll("*")) {
          visit(el);
        }
        const candidates = out.filter((el) => norm(el.textContent || "") === target);
        candidates.sort(
          (a, b) => (a.textContent || "").length - (b.textContent || "").length,
        );
        for (const el of candidates) {
          const rect = el.getBoundingClientRect();
          if (rect.width < 4 || rect.height < 4) {
            continue;
          }
          return {
            x: rect.left + rect.width / 2,
            y: rect.top + rect.height / 2,
          };
        }
        return null;
      }, label)
      .catch(() => null);
    if (!coords) {
      continue;
    }
    await page.mouse.click(coords.x, coords.y);
    return true;
  }
  return false;
}

async function clickTruckTab(page: Page, label: string): Promise<boolean> {
  for (const root of browseRoots(page)) {
    if (await clickTruckTabPlaywright(root, label)) {
      return true;
    }
    if (await clickTruckTabEvaluate(root, label)) {
      return true;
    }
  }
  if (await clickTruckTabByMouse(page, label)) {
    return true;
  }
  return false;
}

/** 报价卡片上最醒目 $ 金额（按可见区域面积） */
async function readDominantQuotePrice(root: BrowseRoot): Promise<number | null> {
  const text = await root.locator("body").innerText().catch(() => "");
  if (!/Choose your quote|选择您的报价/i.test(text)) {
    return null;
  }

  const priceLoc = root.locator("text=/\\$\\s*[\\d,]+\\.\\d{2}/");
  const count = await priceLoc.count().catch(() => 0);
  let bestPrice: number | null = null;
  let bestArea = 0;

  for (let i = 0; i < count; i += 1) {
    const el = priceLoc.nth(i);
    if (!(await el.isVisible().catch(() => false))) {
      continue;
    }
    const raw = await el.innerText().catch(() => "");
    const m = raw.match(/\$\s*([\d,]+\.\d{2})/);
    if (!m) {
      continue;
    }
    const price = Number(m[1]!.replace(/,/g, ""));
    if (!Number.isFinite(price) || price < 15) {
      continue;
    }
    const box = await el.boundingBox();
    if (!box) {
      continue;
    }
    const area = box.width * box.height;
    if (area > bestArea) {
      bestArea = area;
      bestPrice = price;
    }
  }

  if (bestPrice != null) {
    return bestPrice;
  }

  const panelMatch = text.match(
    /Choose your quote[\s\S]{0,800}?\$\s*([\d,]+\.\d{2})/i,
  );
  if (panelMatch) {
    return Number(panelMatch[1]!.replace(/,/g, ""));
  }
  return null;
}

async function readQuotePriceFromAnyRoot(page: Page): Promise<number | null> {
  for (const root of browseRoots(page)) {
    const price = await readDominantQuotePrice(root);
    if (price != null) {
      return price;
    }
  }
  return null;
}

async function waitAxelAfterClick(page: Page): Promise<QuoteItem[] | null> {
  if (typeof page.waitForResponse !== "function") {
    return null;
  }
  const resp: Response | null = await page
    .waitForResponse(
      (r) =>
        isAxelQuoteResponseUrl(r.url()) &&
        r.status() >= 200 &&
        r.status() < 300,
      { timeout: 2_500 },
    )
    .catch(() => null);
  if (!resp) {
    return null;
  }
  const body = await resp.json().catch(() => null);
  if (!body) {
    return null;
  }
  return decodeAxelQuoteBodyLenient(body);
}

/**
 * 依次点击 Shared truck / Dedicated truck，切换后读当前卡片价（+ 可选 axel）。
 */
async function captureAllTabsByClick(page: Page): Promise<QuoteItem[]> {
  const captured: QuoteItem[] = [];

  for (const tab of TRUCK_TABS) {
    let clicked = false;
    let clickedLabel = "";

    for (const label of tab.tabLabels) {
      const axelPromise = waitAxelAfterClick(page);
      if (await clickTruckTab(page, label)) {
        clicked = true;
        clickedLabel = label;
        console.log(`[rpa] widget-quote: 已点击 Tab «${label}»`);
        void axelPromise.then((items) => {
          if (items?.length) {
            console.log(
              `[rpa] widget-quote: Tab «${label}» 触发 axel tiers=${items.length}`,
            );
          }
        });
        break;
      }
      void axelPromise.catch(() => null);
    }

    if (!clicked) {
      console.log(
        `[rpa] widget-quote: 未能点击 ${tab.serviceLevel}（${tab.tabLabels.join("/")}）`,
      );
      await captureWidgetQuoteScreenshot(page, tab.serviceLevel, "click-fail");
      continue;
    }

    await page.waitForTimeout(900);

    const price = await readQuotePriceFromAnyRoot(page);
    if (price == null) {
      console.log(
        `[rpa] widget-quote: Tab «${clickedLabel}» 未读到 $ 金额，尝试截图`,
      );
      await captureWidgetQuoteScreenshot(page, tab.serviceLevel, "price-fail");
      continue;
    }

    const item = toQuoteItem(tab.serviceLevel, price);
    captured.push(item);
    console.log(
      `[rpa] widget-quote: Tab 读价 ${formatWidgetQuoteTierLabel(item)}=$${price}`,
    );
    await captureWidgetQuoteScreenshot(
      page,
      tab.serviceLevel,
      `ok-${price.toFixed(2)}`,
    );
  }

  return captured;
}

/**
 * sign-up-quote：必须点击 Shared / Dedicated truck 切换后读价。
 * axel 首包常仅 standard；Tab 切换价为权威展示价。
 */
export async function supplementWidgetTruckTabQuotes(
  page: Page,
  items: QuoteItem[],
  axelBodies: unknown[] = [],
): Promise<QuoteItem[]> {
  if (!(await isInlineQuotePanelVisible(page))) {
    return items;
  }

  const merged = new Map<string, QuoteItem>();
  for (const item of items) {
    merged.set(tierKey(item), item);
  }
  mergeAxelBodies(merged, axelBodies);

  console.log(
    "[rpa] widget-quote: 依次点击 Shared truck → Dedicated truck 读取报价",
  );

  const fromTabs = await captureAllTabsByClick(page);
  for (const item of fromTabs) {
    const key = tierKey(item);
    merged.set(key, mergeWidgetTabQuotePrice(merged.get(key), item));
  }

  return sortItems([...merged.values()]);
}

/** @deprecated 测试兼容 */
export async function scrapeVisibleQuoteCardsForServiceLevel(
  page: Page,
  serviceLevel: string,
): Promise<QuoteItem[]> {
  const tab = TRUCK_TABS.find((t) => t.serviceLevel === serviceLevel);
  if (!tab) {
    return [];
  }
  for (const label of tab.tabLabels) {
    if (await clickTruckTab(page, label)) {
      await page.waitForTimeout(600);
      const price = await readQuotePriceFromAnyRoot(page);
      return price != null ? [toQuoteItem(serviceLevel, price)] : [];
    }
  }
  return [];
}
