import fs from "node:fs";
import { expect, test, type Browser, type Locator, type Page, type TestInfo } from "@playwright/test";
import { isLoginPage, loginWithCredentials } from "../auth";
import { loadChecklist, loadIssueMetadata } from "../checklist";
import { getRuntimeQaEnv, type RuntimeQaEnv } from "../env";
import type { QaEnvironment, QaPageDefinition } from "../types";
import { buildPageUrl } from "../url";

interface ScenarioOptions {
  issueId: string;
  environment: QaEnvironment;
}

interface PermissionState {
  biscuitCardUse: boolean;
  chargePolicy: boolean;
}

interface PermissionSnapshot {
  pageName: string;
  url: string;
  state: PermissionState;
  bodyText: string;
}

const ADMIN_ROLE = "ADMIN";
const STORE_ALLOWED_ROLE = "DW_VE";
const STORE_NEGATIVE_ROLES = ["STORE_JJM_CO", "STORE_JJM_BO"];
const STORE_NEGATIVE_LABELS: Record<string, string> = {
  STORE_JJM_CO: "CO(secret-redacted)",
  STORE_JJM_BO: "BO(secret-redacted)"
};
const ROLE_LABEL = "가맹점";
const CARD_USE_LABEL = "비스킷 카드 사용";
const CHARGE_POLICY_LABEL = "스토어 비스킷 카드 충전 정책 수정";
const SAVE_REASON = "QA 3483 비스킷 카드 권한 검증";

export function runBiscuitCardPermissionSuite(options: ScenarioOptions): void {
  const metadata = loadIssueMetadata(options.issueId);
  const checklist = loadChecklist(options.issueId, options.environment);
  const rolePermissionPage = requirePage(checklist.pages, "역할별 사용 가능 권한 관리");
  const userPresetPage = requirePage(checklist.pages, "사용자 프리셋 관리");
  const cardPage = requirePage(checklist.pages, "비스킷페이 카드 탭");
  const cardSettingPage = requirePage(checklist.pages, "비스킷페이 카드 충전 설정");
  const adminRuntime = getRuntimeQaEnv(options.environment, "admin", ADMIN_ROLE);
  const storeRuntime = getRuntimeQaEnv(options.environment, "store", STORE_ALLOWED_ROLE);
  const negativeStoreRuntimes = STORE_NEGATIVE_ROLES.map((role) => getRuntimeQaEnv(options.environment, "store", role));

  let originalPresetState: PermissionState | undefined;

  test.describe(`[${options.issueId}][${options.environment}] ${metadata.subject}`, () => {
    test.describe.configure({ mode: "serial" });

    test.afterAll(async ({ browser }) => {
      const restoreState = originalPresetState;
      if (!restoreState) {
        return;
      }

      await withAdminPage(browser, adminRuntime, async (page) => {
        await openPermissionPage(page, userPresetPage, adminRuntime, {
          roleLabel: ROLE_LABEL,
          tabLabel: "기능 권한"
        });
        await setPermissionState(page, restoreState);
        await saveUserPreset(page, `원복 - ${SAVE_REASON}`);
      });
    });

    test(checklist.checklist[0], async ({ browser }, testInfo) => {
      await withAdminPage(browser, adminRuntime, async (page) => {
        const snapshot = await verifyRoleAvailablePermissions(page, rolePermissionPage, adminRuntime);
        await clickRolePermissionSaveIfPossible(page);
        await attachEvidenceLog(testInfo, rolePermissionPage, ADMIN_ROLE, "role-permission-save", [
          "역할별 사용 가능 권한 관리에서 가맹점 역할의 기능 권한 탭을 확인했습니다.",
          `현재 URL: ${snapshot.url}`,
          `비스킷 카드 사용 체크: ${formatBoolean(snapshot.state.biscuitCardUse)}`,
          `스토어 비스킷 카드 충전 정책 수정 체크: ${formatBoolean(snapshot.state.chargePolicy)}`,
          `화면 일부: ${excerptAround(snapshot.bodyText, "비스킷 카드")}`
        ]);
        await attachPageScreenshot(page, testInfo, rolePermissionPage, ADMIN_ROLE, "role-permission-save");

        expect(snapshot.bodyText, `${CARD_USE_LABEL} 권한명이 보여야 합니다.`).toContain(CARD_USE_LABEL);
        expect(snapshot.bodyText, `${CHARGE_POLICY_LABEL} 권한명이 보여야 합니다.`).toContain(CHARGE_POLICY_LABEL);
        expect(snapshot.state.biscuitCardUse, `${CARD_USE_LABEL} 권한이 선택 가능/선택 상태여야 합니다.`).toBe(true);
        expect(snapshot.state.chargePolicy, `${CHARGE_POLICY_LABEL} 권한이 선택 가능/선택 상태여야 합니다.`).toBe(true);
      });
    });

    test(checklist.checklist[1], async ({ browser }, testInfo) => {
      await withAdminPage(browser, adminRuntime, async (page) => {
        const snapshot = await openUserPresetPermissions(page, userPresetPage, adminRuntime);
        originalPresetState = snapshot.state;

        await setPermissionState(page, {
          biscuitCardUse: true,
          chargePolicy: true
        });
        await saveUserPreset(page, SAVE_REASON);
        const afterSave = await openUserPresetPermissions(page, userPresetPage, adminRuntime);

        await attachEvidenceLog(testInfo, userPresetPage, ADMIN_ROLE, "user-preset-modify", [
          "사용자 프리셋 관리에서 가맹점 기본 프리셋의 기능 권한 탭을 확인하고 임시 수정했습니다.",
          `시작 전 비스킷 카드 사용: ${formatBoolean(snapshot.state.biscuitCardUse)}`,
          `시작 전 스토어 비스킷 카드 충전 정책 수정: ${formatBoolean(snapshot.state.chargePolicy)}`,
          `수정 후 비스킷 카드 사용: ${formatBoolean(afterSave.state.biscuitCardUse)}`,
          `수정 후 스토어 비스킷 카드 충전 정책 수정: ${formatBoolean(afterSave.state.chargePolicy)}`,
          `화면 일부: ${excerptAround(afterSave.bodyText, "비스킷 카드")}`
        ]);
        await attachPageScreenshot(page, testInfo, userPresetPage, ADMIN_ROLE, "user-preset-modify");

        expect(afterSave.bodyText, `${CARD_USE_LABEL} 권한명이 보여야 합니다.`).toContain(CARD_USE_LABEL);
        expect(afterSave.bodyText, `${CHARGE_POLICY_LABEL} 권한명이 보여야 합니다.`).toContain(CHARGE_POLICY_LABEL);
        expect(afterSave.state.biscuitCardUse, `${CARD_USE_LABEL} 권한이 저장 후 선택되어야 합니다.`).toBe(true);
        expect(afterSave.state.chargePolicy, `${CHARGE_POLICY_LABEL} 권한이 저장 후 선택되어야 합니다.`).toBe(true);
      });
    });

    test(checklist.checklist[2], async ({ browser }, testInfo) => {
      await withFreshStorePage(browser, storeRuntime, async (page) => {
        await openStoreHome(page, storeRuntime);
        const homeText = await bodyText(page);
        await attachEvidenceLog(testInfo, cardPage, "VE(secret-redacted)", "card-tab-visible", [
          "권한을 부여한 가맹점 계정(secret-redacted)으로 비스킷페이에 로그인했습니다.",
          `현재 URL: ${page.url()}`,
          `하단 탭 일부: ${excerptAround(homeText, "카드")}`
        ]);
        await attachPageScreenshot(page, testInfo, cardPage, "VE-secret-redacted", "card-tab-visible");

        expect(hasCardTabText(homeText), "권한 부여 계정은 카드 탭이 보여야 합니다.").toBe(true);
      });
    });

    test(checklist.checklist[3], async ({ browser }, testInfo) => {
      const evidence: string[] = [];

      for (const runtimeEnv of negativeStoreRuntimes) {
        await withFreshStorePage(browser, runtimeEnv, async (page) => {
          await openStoreHome(page, runtimeEnv);
          const text = await bodyText(page);
          const roleLabel = STORE_NEGATIVE_LABELS[runtimeEnv.role] ?? runtimeEnv.role;
          evidence.push(`${roleLabel}: URL=${page.url()}`);
          evidence.push(`${roleLabel}: 카드 탭 노출=${hasCardTabText(text) ? "yes" : "no"}`);
          await attachPageScreenshot(page, testInfo, cardPage, roleLabel, "card-tab-hidden");
          expect(hasCardTabText(text), `${roleLabel} 미설정 계정에는 카드 탭이 보이면 안 됩니다.`).toBe(false);
        });
      }

      await attachEvidenceLog(testInfo, cardPage, "CO/BO", "card-tab-hidden", [
        "미설정 계정으로 비스킷페이 로그인 후 카드 탭 미노출을 확인했습니다.",
        ...evidence
      ]);
    });

    test(checklist.checklist[4], async ({ browser }, testInfo) => {
      await withFreshStorePage(browser, storeRuntime, async (page) => {
        await openCardSettingsViaGear(page, storeRuntime, cardPage);
        const text = await bodyText(page);
        await attachEvidenceLog(testInfo, cardSettingPage, "VE(secret-redacted)", "charge-policy-visible", [
          "스토어 비스킷 카드 충전 정책 수정 권한이 켜진 상태에서 카드 설정 화면을 확인했습니다.",
          `현재 URL: ${page.url()}`,
          `충전 방식 문구 노출: ${/충전\s*방식/.test(text) ? "yes" : "no"}`,
          `화면 일부: ${excerptAround(text, "충전 방식")}`
        ]);
        await attachPageScreenshot(page, testInfo, cardSettingPage, "VE-secret-redacted", "charge-policy-visible");

        expect(text, "충전 방식 선택 화면이 보여야 합니다.").toMatch(/충전\s*방식|자동\s*충전|수동\s*금액\s*충전/);
      });
    });

    test(checklist.checklist[5], async ({ browser }, testInfo) => {
      await withAdminPage(browser, adminRuntime, async (page) => {
        await openUserPresetPermissions(page, userPresetPage, adminRuntime);
        await setPermissionState(page, {
          biscuitCardUse: true,
          chargePolicy: false
        });
        await saveUserPreset(page, SAVE_REASON);
        const afterSave = await openUserPresetPermissions(page, userPresetPage, adminRuntime);
        await attachEvidenceLog(testInfo, userPresetPage, ADMIN_ROLE, "charge-policy-disabled", [
          `"${CARD_USE_LABEL}"만 선택한 상태로 사용자 프리셋을 저장했습니다.`,
          `비스킷 카드 사용: ${formatBoolean(afterSave.state.biscuitCardUse)}`,
          `스토어 비스킷 카드 충전 정책 수정: ${formatBoolean(afterSave.state.chargePolicy)}`
        ]);
      });

      await withFreshStorePage(browser, storeRuntime, async (page) => {
        await openCardSettingsViaGear(page, storeRuntime, cardPage);
        const text = await bodyText(page);
        await attachEvidenceLog(testInfo, cardSettingPage, "VE(secret-redacted)", "charge-policy-hidden", [
          `"${CARD_USE_LABEL}"만 선택된 상태에서 카드 설정 화면을 확인했습니다.`,
          `현재 URL: ${page.url()}`,
          `충전 방식 문구 노출: ${/충전\s*방식/.test(text) ? "yes" : "no"}`,
          `화면 일부: ${text.slice(0, 1500)}`
        ]);
        await attachPageScreenshot(page, testInfo, cardSettingPage, "VE-secret-redacted", "charge-policy-hidden");

        expect(text, "충전 정책 권한이 꺼지면 충전 방식 선택 화면이 보이면 안 됩니다.").not.toMatch(/충전\s*방식|자동\s*충전|수동\s*금액\s*충전/);
      });
    });
  });
}

async function verifyRoleAvailablePermissions(
  page: Page,
  pageDefinition: QaPageDefinition,
  runtimeEnv: RuntimeQaEnv
): Promise<PermissionSnapshot> {
  await openPermissionPage(page, pageDefinition, runtimeEnv, {
    roleLabel: ROLE_LABEL,
    tabLabel: "기능 권한"
  });
  const state = await readPermissionState(page);
  const text = await bodyText(page);
  return {
    pageName: pageDefinition.name,
    url: page.url(),
    state,
    bodyText: text
  };
}

async function openUserPresetPermissions(
  page: Page,
  pageDefinition: QaPageDefinition,
  runtimeEnv: RuntimeQaEnv
): Promise<PermissionSnapshot> {
  await openPermissionPage(page, pageDefinition, runtimeEnv, {
    roleLabel: ROLE_LABEL,
    tabLabel: "기능 권한"
  });
  const state = await readPermissionState(page);
  const text = await bodyText(page);
  return {
    pageName: pageDefinition.name,
    url: page.url(),
    state,
    bodyText: text
  };
}

async function openPermissionPage(
  page: Page,
  pageDefinition: QaPageDefinition,
  runtimeEnv: RuntimeQaEnv,
  options: { roleLabel: string; tabLabel: string }
): Promise<void> {
  if (!runtimeEnv.storageState && (!runtimeEnv.username || !runtimeEnv.password)) {
    blocked(`${runtimeEnv.application}/${runtimeEnv.environment}/${runtimeEnv.role} 로그인 정보가 필요합니다.`);
  }

  const targetUrl = buildPageUrl(runtimeEnv.baseUrl!, pageDefinition, runtimeEnv.tenantId!);
  await page.goto(targetUrl);
  await waitForNavigationToSettle(page);

  if (await isLoginPage(page, runtimeEnv)) {
    await loginWithCredentials(page, runtimeEnv);
    await page.goto(targetUrl);
    await waitForNavigationToSettle(page);
  }

  await selectRoleButton(page, options.roleLabel);
  await clickTab(page, options.tabLabel);
  await expect(page.locator("body"), `${pageDefinition.name} 화면에 ${CARD_USE_LABEL} 권한명이 보여야 합니다.`).toContainText(CARD_USE_LABEL, {
    timeout: 10_000
  });
}

async function selectRoleButton(page: Page, label: string): Promise<void> {
  const button = page.getByRole("button", { name: label }).last();
  await expect(button, `${label} 사용자 롤 버튼이 보여야 합니다.`).toBeVisible({ timeout: 10_000 });
  await button.click({ force: true });
  await waitForNavigationToSettle(page);
}

async function clickTab(page: Page, label: string): Promise<void> {
  const tab = page.locator("button").filter({ hasText: label }).last();
  await expect(tab, `${label} 탭 버튼이 보여야 합니다.`).toBeVisible({ timeout: 10_000 });
  await tab.click({ force: true });
  await page.waitForTimeout(800);
}

async function clickRolePermissionSaveIfPossible(page: Page): Promise<void> {
  const saveButton = page.locator("button").filter({ hasText: "저장" }).last();
  if (!(await saveButton.isVisible({ timeout: 2_000 }).catch(() => false))) {
    return;
  }
  await saveButton.click({ force: true }).catch(() => undefined);
  await waitForNavigationToSettle(page);
}

async function setPermissionState(page: Page, desired: PermissionState): Promise<void> {
  await setCheckboxState(page, CARD_USE_LABEL, desired.biscuitCardUse);
  await setCheckboxState(page, CHARGE_POLICY_LABEL, desired.chargePolicy);
}

async function setCheckboxState(page: Page, label: string, checked: boolean): Promise<void> {
  const labelLocator = permissionLabel(page, label);
  await expect(labelLocator, `${label} 체크박스 라벨이 보여야 합니다.`).toBeVisible({ timeout: 10_000 });
  const current = await readCheckboxState(labelLocator);
  if (current !== checked) {
    await labelLocator.click({ force: true });
    await page.waitForTimeout(300);
  }
  await expect.poll(async () => readCheckboxState(labelLocator), {
    timeout: 3_000,
    message: `${label} 체크 상태가 ${checked}로 변경되어야 합니다.`
  }).toBe(checked);
}

async function readPermissionState(page: Page): Promise<PermissionState> {
  return {
    biscuitCardUse: await readCheckboxState(permissionLabel(page, CARD_USE_LABEL)),
    chargePolicy: await readCheckboxState(permissionLabel(page, CHARGE_POLICY_LABEL))
  };
}

function permissionLabel(page: Page, label: string): Locator {
  return page.locator("label").filter({ hasText: label }).last();
}

async function readCheckboxState(labelLocator: Locator): Promise<boolean> {
  return labelLocator.evaluate((labelElement) => {
    const input = labelElement.parentElement?.querySelector("input[type='checkbox']");
    return input?.getAttribute("aria-checked") === "true" || (input as HTMLInputElement | null)?.checked === true;
  });
}

async function saveUserPreset(page: Page, reason: string): Promise<void> {
  const updateButton = page.locator("button").filter({ hasText: "수정" }).last();
  await expect(updateButton, "사용자 프리셋 수정 버튼이 보여야 합니다.").toBeVisible({ timeout: 10_000 });
  await updateButton.click({ force: true });
  await page.waitForTimeout(500);

  const reasonInput = page.locator("input[placeholder*='사유'], textarea[placeholder*='사유']").last();
  await expect(reasonInput, "수정 사유 입력창이 보여야 합니다.").toBeVisible({ timeout: 5_000 });
  await reasonInput.fill(reason);

  const saveRequest = page.waitForResponse((response) => {
    return response.url().includes("/api/v1/admin/role-permission-presets") &&
      ["PUT", "PATCH", "POST"].includes(response.request().method());
  }, { timeout: 12_000 }).catch(() => undefined);
  await page.getByRole("button", { name: "확인" }).last().click({ force: true });
  await saveRequest;
  await waitForNavigationToSettle(page);
}

async function openStoreHome(page: Page, runtimeEnv: RuntimeQaEnv): Promise<void> {
  await loginWithCredentials(page, runtimeEnv);
  await waitForNavigationToSettle(page);
  await expect(page.locator("body"), "스토어 홈 화면 본문이 표시되어야 합니다.").toBeVisible();
}

async function openCardSettingsViaGear(
  page: Page,
  runtimeEnv: RuntimeQaEnv,
  cardPage: QaPageDefinition
): Promise<void> {
  await loginWithCredentials(page, runtimeEnv);
  await page.goto(new URL(cardPage.path, ensureTrailingSlash(runtimeEnv.baseUrl!)).toString());
  await waitForNavigationToSettle(page);
  await expect(page.locator("body"), "카드 화면 본문이 표시되어야 합니다.").toContainText("카드", { timeout: 15_000 });

  const settingsButton = page.getByLabel("카드 설정 페이지로 이동").first();
  if (await settingsButton.isVisible({ timeout: 3_000 }).catch(() => false)) {
    await settingsButton.click();
  } else {
    await page.goto(new URL("/biscuit-card/settings", ensureTrailingSlash(runtimeEnv.baseUrl!)).toString());
  }

  await waitForNavigationToSettle(page);
  await expect(page.locator("body"), "카드 설정 화면 본문이 표시되어야 합니다.").toContainText("카드정보", {
    timeout: 15_000
  });
}

async function withAdminPage(
  browser: Browser,
  runtimeEnv: RuntimeQaEnv,
  callback: (page: Page) => Promise<void>
): Promise<void> {
  const context = await browser.newContext(
    runtimeEnv.storageState
      ? { storageState: runtimeEnv.storageState, acceptDownloads: true }
      : { acceptDownloads: true }
  );
  const page = await context.newPage();

  try {
    await callback(page);
  } finally {
    await context.close();
  }
}

async function withFreshStorePage(
  browser: Browser,
  runtimeEnv: RuntimeQaEnv,
  callback: (page: Page) => Promise<void>
): Promise<void> {
  const context = await browser.newContext({
    acceptDownloads: true,
    viewport: { width: 390, height: 844 }
  });
  const page = await context.newPage();

  try {
    await callback(page);
  } finally {
    await context.close();
  }
}

async function waitForNavigationToSettle(page: Page): Promise<void> {
  await page.waitForLoadState("domcontentloaded").catch(() => undefined);
  await page.waitForLoadState("networkidle", { timeout: 6_000 }).catch(() => undefined);
  await page.waitForTimeout(500);
}

async function bodyText(page: Page): Promise<string> {
  return normalizeText(await page.locator("body").innerText({ timeout: 5_000 }).catch(() => ""));
}

async function attachEvidenceLog(
  testInfo: TestInfo,
  pageDefinition: QaPageDefinition,
  role: string,
  slug: string,
  lines: string[]
): Promise<void> {
  const evidencePath = testInfo.outputPath(`${safeFilename(pageDefinition.name)}-${safeFilename(role)}-${slug}.md`);
  fs.writeFileSync(evidencePath, `${lines.join("\n")}\n`, "utf8");
  await testInfo.attach(`${safeFilename(pageDefinition.name)}-${safeFilename(role)}-${slug}.md`, {
    path: evidencePath,
    contentType: "text/markdown"
  });
}

async function attachPageScreenshot(
  page: Page,
  testInfo: TestInfo,
  pageDefinition: QaPageDefinition,
  role: string,
  slug: string
): Promise<void> {
  const screenshotPath = testInfo.outputPath(`${safeFilename(pageDefinition.name)}-${safeFilename(role)}-${slug}.png`);
  await page.screenshot({ path: screenshotPath, fullPage: true });
  await testInfo.attach(`${safeFilename(pageDefinition.name)}-${safeFilename(role)}-${slug}.png`, {
    path: screenshotPath,
    contentType: "image/png"
  });
}

function requirePage(pages: QaPageDefinition[], name: string): QaPageDefinition {
  const pageDefinition = pages.find((page) => page.name === name);
  if (!pageDefinition) {
    throw new Error(`checklist.json pages에 '${name}' 정의가 필요합니다.`);
  }
  return pageDefinition;
}

function hasCardTabText(text: string): boolean {
  return /홈\s*지갑(?:\s*결제)?\s*내역\s*카드/.test(text) || /\b카드\b/.test(text);
}

function excerptAround(text: string, keyword: string): string {
  const index = text.indexOf(keyword);
  if (index < 0) {
    return text.slice(0, 1200);
  }
  return text.slice(Math.max(0, index - 400), index + 1200);
}

function formatBoolean(value: boolean): string {
  return value ? "선택됨" : "선택 안 됨";
}

function normalizeText(value: string): string {
  return value.replace(/\s+/g, " ").trim();
}

function ensureTrailingSlash(rawUrl: string): string {
  return rawUrl.endsWith("/") ? rawUrl : `${rawUrl}/`;
}

function safeFilename(value: string): string {
  return value
    .replace(/[\\/:*?"<>|]+/g, "-")
    .replace(/\s+/g, "-")
    .slice(0, 80);
}

function blocked(message: string): never {
  throw new Error(`BLOCKED: ${message}`);
}
