import { type Locator, type Page } from "@playwright/test";
import type { RuntimeQaEnv } from "./env";

export async function loginWithCredentials(page: Page, runtimeEnv: RuntimeQaEnv): Promise<void> {
  if (!runtimeEnv.loginUrl || !runtimeEnv.username || !runtimeEnv.password) {
    throw new Error(
      `${runtimeEnv.application}/${runtimeEnv.environment}/${runtimeEnv.role} 로그인을 위한 ` +
        "loginUrl, username, password가 필요합니다."
    );
  }

  await page.goto(runtimeEnv.loginUrl);
  await waitForSoftNetworkIdle(page);

  const usernameInput = page.locator(runtimeEnv.usernameSelector).first();
  const usernameVisible = await usernameInput.isVisible({ timeout: 3_000 }).catch(() => false);
  if (!usernameVisible) {
    const block = await detectLoginBlock(page, runtimeEnv);
    if (block) {
      await assertLoginCompleted(page, runtimeEnv);
      return;
    }
    if (!(await isLoginPage(page, runtimeEnv))) {
      return;
    }
  }

  await usernameInput.fill(runtimeEnv.username);
  await page.locator(runtimeEnv.passwordSelector).first().fill(runtimeEnv.password);
  await clickFirstVisible(page.locator(runtimeEnv.submitSelector));
  await waitForSoftNetworkIdle(page);

  await assertLoginCompleted(page, runtimeEnv);
}

export async function isLoginPage(page: Page, runtimeEnv: RuntimeQaEnv): Promise<boolean> {
  const currentUrl = page.url();
  const loginPath = runtimeEnv.loginUrl ? new URL(runtimeEnv.loginUrl).pathname : "/auth/login";
  const currentPath = currentUrl.startsWith("http") ? new URL(currentUrl).pathname : "";
  const authPathVisible =
    currentPath === loginPath ||
    currentPath.startsWith(`${loginPath.replace(/\/+$/, "")}/`) ||
    /\/(sign-in|login|auth)(\/|$)/i.test(currentPath);

  if (!authPathVisible) {
    return false;
  }

  const usernameVisible = await page
    .locator(runtimeEnv.usernameSelector)
    .first()
    .isVisible({ timeout: 1_000 })
    .catch(() => false);
  const loginHeadingVisible = await page
    .getByRole("heading", { name: /로그인|Login/i })
    .first()
    .isVisible({ timeout: 1_000 })
    .catch(() => false);
  const twoFactorVisible = await page
    .locator(runtimeEnv.twoFactorSelector)
    .first()
    .isVisible({ timeout: 1_000 })
    .catch(() => false);
  const authTextVisible = await page
    .locator("body")
    .innerText({ timeout: 1_000 })
    .then((text) => /PIN\s*번호|인증번호|로그인|Login/i.test(text))
    .catch(() => false);

  return usernameVisible || loginHeadingVisible || twoFactorVisible || authTextVisible;
}

async function fillTwoFactorIfPresent(
  page: Page,
  runtimeEnv: RuntimeQaEnv,
  timeoutMs = 5_000
): Promise<boolean> {
  const twoFactorInput = page.locator(runtimeEnv.twoFactorSelector).first();
  const isTwoFactorVisible = await twoFactorInput.isVisible({ timeout: timeoutMs }).catch(() => false);

  if (!isTwoFactorVisible) {
    return false;
  }
  const isTwoFactorEnabled = await twoFactorInput.isEnabled({ timeout: 500 }).catch(() => false);
  if (!isTwoFactorEnabled) {
    return false;
  }

  if (!runtimeEnv.twoFactorCode) {
    throw new Error(`${runtimeEnv.application}/${runtimeEnv.environment}/${runtimeEnv.role} role의 2FA 코드가 필요합니다.`);
  }

  await twoFactorInput.fill(runtimeEnv.twoFactorCode);
  await clickFirstVisible(page.locator(runtimeEnv.twoFactorSubmitSelector));
  await waitForSoftNetworkIdle(page);
  return true;
}

async function waitForSoftNetworkIdle(page: Page): Promise<void> {
  const configured = Number(process.env.QA_AUTH_NETWORK_IDLE_TIMEOUT_MS);
  const timeout = Number.isFinite(configured) && configured > 0 ? configured : 3_000;
  await page.waitForLoadState("networkidle", { timeout }).catch(() => undefined);
}

async function assertLoginCompleted(page: Page, runtimeEnv: RuntimeQaEnv): Promise<void> {
  const interactive = isInteractiveAuthEnabled();
  const timeoutMs = getAuthTimeoutMs(interactive);
  const deadline = Date.now() + timeoutMs;
  let lastInteractiveBlock = "";

  while (Date.now() < deadline) {
    const block = await detectLoginBlock(page, runtimeEnv);
    if (block) {
      if (!block.interactive || !interactive) {
        throw new Error(block.message);
      }
      lastInteractiveBlock = block.message;
      await page.waitForTimeout(1_000);
      continue;
    }

    if (await fillTwoFactorIfPresent(page, runtimeEnv, 750)) {
      await page.waitForTimeout(500);
      continue;
    }

    if (!(await isLoginPage(page, runtimeEnv))) {
      return;
    }

    await page.waitForTimeout(500);
  }

  const context = `${runtimeEnv.application}/${runtimeEnv.environment}/${runtimeEnv.role}`;
  const detail = lastInteractiveBlock ? ` 마지막 감지 상태: ${lastInteractiveBlock}` : "";
  throw new Error(`BLOCKED: ${context} 로그인이 ${timeoutMs}ms 안에 완료되지 않아 QA를 진행할 수 없습니다.${detail}`);
}

async function clickFirstVisible(locator: Locator): Promise<void> {
  const count = await locator.count();

  for (let index = 0; index < count; index += 1) {
    const candidate = locator.nth(index);
    if (await candidate.isVisible().catch(() => false)) {
      await candidate.click();
      return;
    }
  }

  await locator.first().click();
}

interface LoginBlock {
  message: string;
  interactive: boolean;
}

async function detectLoginBlock(
  page: Page,
  runtimeEnv: RuntimeQaEnv
): Promise<LoginBlock | undefined> {
  const bodyText = await page.locator("body").innerText({ timeout: 1_000 }).catch(() => "");
  const context = `${runtimeEnv.application}/${runtimeEnv.environment}/${runtimeEnv.role}`;

  if (/사용자를 찾을 수 없습니다/.test(bodyText)) {
    return {
      message: `BLOCKED: ${context} 계정이 대상 환경에서 거부되었습니다. 계정 또는 환경 매핑 확인이 필요합니다.`,
      interactive: false
    };
  }

  if (/가맹점\s*사용자는\s*전산\s*로그인이\s*불가능/.test(bodyText)) {
    return {
      message: `BLOCKED: ${context} 계정은 admin 전산 로그인이 제한되어 대상 화면 QA를 진행할 수 없습니다.`,
      interactive: false
    };
  }

  if (/등록되지 않은 기기|기기 등록|SMS 인증|인증번호발송/.test(bodyText)) {
    return {
      message: `BLOCKED: ${context} 로그인은 미등록 기기 등록 또는 SMS 인증이 필요해 자동 QA가 차단되었습니다.`,
      interactive: true
    };
  }

  if (/비밀번호.*(일치하지|틀렸|확인)|PIN.*(일치하지|틀렸|확인)/.test(bodyText)) {
    return {
      message: `BLOCKED: ${context} 로그인 인증 정보가 거부되었습니다.`,
      interactive: false
    };
  }

  if (/로그인\s*또는\s*2차\s*인증\s*연속\s*실패로\s*계정이\s*잠겼|계정이\s*잠겼/.test(bodyText)) {
    return {
      message: `BLOCKED: ${context} 계정이 잠겨 QA를 진행할 수 없습니다. 계정 잠금 해제가 필요합니다.`,
      interactive: false
    };
  }

  if (/로그인할\s*수\s*없는\s*사용자/.test(bodyText)) {
    return {
      message: `BLOCKED: ${context} 계정이 대상 환경에서 로그인할 수 없는 사용자로 거부되었습니다.`,
      interactive: false
    };
  }

  return undefined;
}

function isInteractiveAuthEnabled(): boolean {
  return process.env.QA_AUTH_HEADFUL === "1" || process.env.QA_AUTH_INTERACTIVE === "1";
}

function getAuthTimeoutMs(interactive: boolean): number {
  const configured = Number(process.env.QA_AUTH_TIMEOUT_MS);
  if (Number.isFinite(configured) && configured > 0) {
    return configured;
  }
  return interactive ? 300_000 : 45_000;
}
