import fs from "node:fs";
import path from "node:path";
import {
  chromium,
  type Browser,
  type BrowserContextOptions
} from "@playwright/test";
import { isLoginPage, loginWithCredentials } from "./auth";
import {
  getRuntimeQaEnv,
  resolveStorageStatePath
} from "./env";
import type { QaApplication, QaEnvironment } from "./types";

export interface CreateStorageStateOptions {
  application: QaApplication;
  environment: QaEnvironment;
  role: string;
}

export async function createStorageStateForRole(
  options: CreateStorageStateOptions
): Promise<string> {
  const runtimeEnv = getRuntimeQaEnv(
    options.environment,
    options.application,
    options.role
  );
  const storageStatePath = resolveStorageStatePath(
    options.environment,
    options.application,
    options.role
  );

  const browser = await launchAuthBrowser();
  try {
    try {
      await loginAndPersistStorageState(browser, runtimeEnv, storageStatePath);
    } catch (error) {
      if (shouldRetryWithFreshState(error) && fs.existsSync(storageStatePath)) {
        await loginAndPersistStorageState(browser, runtimeEnv, storageStatePath, {
          useExistingState: false
        });
        return storageStatePath;
      }
      if (!shouldRetryWithExistingState(error) || !fs.existsSync(storageStatePath)) {
        throw error;
      }
      await reuseExistingDeviceState(browser, runtimeEnv, storageStatePath);
    }
    return storageStatePath;
  } finally {
    await browser.close();
  }
}

async function launchAuthBrowser(): Promise<Browser> {
  return chromium.launch({
    headless: process.env.QA_AUTH_HEADFUL === "1" ? false : undefined,
    slowMo: getAuthSlowMoMs()
  });
}

async function loginAndPersistStorageState(
  browser: Browser,
  runtimeEnv: ReturnType<typeof getRuntimeQaEnv>,
  storageStatePath: string,
  options: { useExistingState?: boolean } = {}
): Promise<void> {
  const context = await browser.newContext(
    options.useExistingState === false
      ? {}
      : getLoginContextOptions(storageStatePath)
  );
  const page = await context.newPage();

  try {
    await loginWithCredentials(page, runtimeEnv);
    fs.mkdirSync(path.dirname(storageStatePath), { recursive: true });
    await context.storageState({ path: storageStatePath });
  } finally {
    await context.close();
  }
}

function getLoginContextOptions(storageStatePath: string): BrowserContextOptions {
  if (!fs.existsSync(storageStatePath)) {
    return {};
  }

  const state = JSON.parse(fs.readFileSync(storageStatePath, "utf8")) as StorageState;

  return {
    storageState: {
      ...state,
      cookies: (state.cookies ?? []).filter(
        (cookie) => !isAuthStateKey(cookie.name)
      ),
      origins: (state.origins ?? []).map((origin) => ({
        ...origin,
        localStorage: (origin.localStorage ?? []).filter(
          (item) => !isAuthStateKey(item.name)
        )
      }))
    }
  };
}

type StorageState = Exclude<BrowserContextOptions["storageState"], string | undefined>;

function isAuthStateKey(name: string | undefined): boolean {
  return /access|refresh|auth|session|token/i.test(name ?? "");
}

async function reuseExistingDeviceState(
  browser: Browser,
  runtimeEnv: ReturnType<typeof getRuntimeQaEnv>,
  storageStatePath: string
): Promise<void> {
  const context = await browser.newContext({ storageState: storageStatePath });
  const page = await context.newPage();

  try {
    await page.goto(runtimeEnv.loginUrl ?? runtimeEnv.baseUrl!, {
      waitUntil: "domcontentloaded",
      timeout: getAuthNavigationTimeoutMs()
    });
    await page.waitForLoadState("networkidle").catch(() => undefined);
    if (await isLoginPage(page, runtimeEnv)) {
      await loginWithCredentials(page, runtimeEnv);
    }
    fs.mkdirSync(path.dirname(storageStatePath), { recursive: true });
    await context.storageState({ path: storageStatePath });
  } finally {
    await context.close();
  }
}

function shouldRetryWithExistingState(error: unknown): boolean {
  const message = error instanceof Error ? error.message : String(error);
  return /미등록 기기|기기 등록|SMS 인증|인증번호발송/.test(message);
}

function shouldRetryWithFreshState(error: unknown): boolean {
  const message = error instanceof Error ? error.message : String(error);
  return /로그인이 \d+ms 안에 완료되지 않아/.test(message);
}

function getAuthSlowMoMs(): number | undefined {
  const configured = Number(process.env.QA_AUTH_SLOW_MO_MS);
  return Number.isFinite(configured) && configured > 0 ? configured : undefined;
}

function getAuthNavigationTimeoutMs(): number {
  const configured = Number(process.env.QA_AUTH_NAVIGATION_TIMEOUT_MS);
  if (Number.isFinite(configured) && configured > 0) {
    return configured;
  }
  return 60_000;
}
