import fs from "node:fs/promises";
import path from "node:path";
import { chromium } from "playwright";
import {
  BROWSER_PROFILE_DIRECTORY,
  CATEGORY_CONFIG,
  CHROME_EXECUTABLE,
  DASHBOARD_URL,
  DIMENSION_CONFIG,
  RUNS_DIRECTORY,
  getPastTwoCompleteMonths,
} from "./config.mjs";

const wait = (milliseconds) =>
  new Promise((resolve) => setTimeout(resolve, milliseconds));

function escapeRegExp(value) {
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

async function findDashboardFrame(page, onProgress) {
  const startedAt = Date.now();
  let loginMessageSent = false;

  while (Date.now() - startedAt < 10 * 60 * 1000) {
    const frame = page
      .frames()
      .find(
        (candidate) =>
          candidate.url().includes("ldpqbi.zhuanspirit.com") ||
          candidate.url().includes("n-specific_conversion"),
      );

    if (
      frame &&
      ((await frame.locator(".root-container").count()) > 0 ||
        (await frame.getByText("指标趋势", { exact: true }).count()) > 0)
    ) {
      return frame;
    }

    if (!loginMessageSent) {
      onProgress({
        stage: "waiting_login",
        step: 2,
        percent: 18,
        message: "请在弹出的专用 Chrome 窗口登录乾数据平台，登录后会自动继续。",
      });
      loginMessageSent = true;
    }
    await wait(1_500);
  }

  throw new Error(
    "等待乾数据平台登录超时。请重新开始任务，并在弹出的 Chrome 窗口完成登录。",
  );
}

async function setDateInput(input, value) {
  await input.scrollIntoViewIfNeeded();
  await input.click();
  try {
    await input.fill(value);
  } catch {
    await input.evaluate((element) => element.removeAttribute("readonly"));
    await input.fill(value);
  }
  await input.press("Enter");
  await wait(250);
}

async function setDateRange(frame, start, end) {
  const wrapper = frame
    .locator(".query-field-wrapper")
    .filter({ hasText: "统计日期" })
    .first();
  const inputs = wrapper.locator("input");

  if ((await inputs.count()) < 2) {
    throw new Error("没有找到统计日期的起止输入框。");
  }

  await setDateInput(inputs.nth(0), start);
  await setDateInput(inputs.nth(1), end);
  await frame.locator("body").press("Escape").catch(() => {});
}

async function chooseAntOption(frame, fieldLabel, optionText) {
  const wrapper = frame
    .locator(".query-field-wrapper")
    .filter({ hasText: fieldLabel })
    .first();
  if ((await wrapper.count()) === 0) {
    throw new Error(`没有找到“${fieldLabel}”筛选项。`);
  }

  const selector = wrapper
    .locator(
      ".ant-select-selector, .el-select__wrapper, .el-input__wrapper, input",
    )
    .first();
  const selectedText = (await selector.textContent().catch(() => "")) ?? "";
  if (selectedText.replace(/\s+/g, "").includes(optionText)) {
    return;
  }

  await selector.scrollIntoViewIfNeeded();
  await selector.click();
  const exactOption = new RegExp(`^\\s*${escapeRegExp(optionText)}\\s*$`);
  const candidates = [
    frame
      .locator(
        ".ant-select-dropdown:visible .ant-select-item-option-content",
      )
      .filter({ hasText: exactOption }),
    frame
      .locator(".el-select-dropdown:visible .el-select-dropdown__item")
      .filter({ hasText: exactOption }),
    frame.getByText(optionText, { exact: true }),
  ];

  for (const candidate of candidates) {
    if ((await candidate.count()) > 0) {
      await candidate.last().click();
      await frame.locator("body").press("Escape").catch(() => {});
      return;
    }
  }

  throw new Error(`“${fieldLabel}”中没有找到“${optionText}”选项。`);
}

async function clickQuery(frame) {
  const queryButton = frame
    .getByRole("button", { name: /查\s*询/ })
    .last();
  if ((await queryButton.count()) === 0) {
    throw new Error("没有找到查询按钮。");
  }
  await queryButton.click();
  await wait(3_000);
}

async function exportCard({ page, frame, title, destination }) {
  const cards = frame.locator(".component-card");
  let card = cards.filter({ hasText: title }).first();
  if ((await card.count()) === 0) {
    const fallbackIndex = title === "广告话术" ? 12 : 14;
    card = cards.nth(fallbackIndex);
  }
  if ((await card.count()) === 0) {
    throw new Error(`没有找到“${title}”数据区域。`);
  }

  await card.scrollIntoViewIfNeeded();
  const menuTrigger = card
    .locator(
      "i.ant-dropdown-trigger.common-more-outlined, .common-more-outlined, [class*='more-outlined']",
    )
    .last();
  if ((await menuTrigger.count()) === 0) {
    throw new Error(`没有找到“${title}”右上角的导出菜单。`);
  }
  await menuTrigger.click();

  const exportItem = frame
    .locator("span.control-menu-item, .ant-dropdown-menu-item")
    .filter({ hasText: /^\s*导出\s*$/ })
    .last();
  if ((await exportItem.count()) === 0) {
    throw new Error(`“${title}”菜单中没有找到导出。`);
  }
  await exportItem.click();

  const dialog = frame.getByRole("dialog", { name: /导出/ }).last();
  await dialog.waitFor({ state: "visible", timeout: 15_000 });
  const confirm = dialog
    .getByRole("button", { name: /确\s*定/ })
    .last();
  const downloadPromise = page.waitForEvent("download", { timeout: 60_000 });
  await confirm.click();
  const download = await downloadPromise;
  await download.saveAs(destination);
}

export async function exportDashboardFiles({
  taskId,
  onProgress = () => {},
}) {
  const period = getPastTwoCompleteMonths();
  const runDirectory = path.join(RUNS_DIRECTORY, taskId);
  await fs.mkdir(runDirectory, { recursive: true });
  await fs.mkdir(BROWSER_PROFILE_DIRECTORY, { recursive: true });

  onProgress({
    stage: "opening_browser",
    step: 2,
    percent: 10,
    message: "正在打开本机专用 Chrome 窗口…",
  });

  const context = await chromium.launchPersistentContext(
    BROWSER_PROFILE_DIRECTORY,
    {
      acceptDownloads: true,
      args: ["--start-maximized"],
      executablePath: CHROME_EXECUTABLE,
      headless: false,
      viewport: null,
    },
  );

  try {
    const pages = context.pages();
    const page = pages[0] ?? (await context.newPage());
    await page.goto(DASHBOARD_URL, {
      waitUntil: "domcontentloaded",
      timeout: 60_000,
    });
    const frame = await findDashboardFrame(page, onProgress);

    onProgress({
      stage: "configuring_filters",
      step: 2,
      percent: 25,
      message: `正在设置统计期 ${period.start} 至 ${period.end}…`,
    });
    await setDateRange(frame, period.start, period.end);
    await chooseAntOption(frame, "平台", "抖音");
    await chooseAntOption(frame, "定制", "定制");

    const files = {};
    const categories = Object.values(CATEGORY_CONFIG);
    const dimensions = Object.values(DIMENSION_CONFIG);
    let completedExports = 0;
    const totalExports = categories.length * dimensions.length;

    for (const category of categories) {
      onProgress({
        stage: "configuring_filters",
        step: 2,
        percent: 30 + completedExports * 10,
        message: `正在查询${category.name}数据…`,
      });
      await chooseAntOption(frame, "品类", category.name);
      await clickQuery(frame);

      for (const dimension of dimensions) {
        onProgress({
          stage: "exporting",
          step: 2,
          percent: 35 + completedExports * 12,
          message: `正在导出${category.name} ${dimension.name} 数据（${
            completedExports + 1
          }/${totalExports}）…`,
        });
        const fileKey = `${category.key}_${dimension.key}`;
        const destination = path.join(
          runDirectory,
          `${category.name}_${dimension.name}.xlsx`,
        );
        await exportCard({
          page,
          frame,
          title: dimension.dashboardTitle,
          destination,
        });
        files[fileKey] = destination;
        completedExports += 1;
      }
    }

    return { files, period, runDirectory };
  } finally {
    await context.close().catch(() => {});
  }
}
