import {
  Browser,
  BrowserContext,
  BrowserContextOptions,
} from "@playwright/test";
import fs from "fs";
import path from "path";
import { TIMEOUT } from "../constant/timeout";

const AUTH_DIR = path.resolve("playwright/.auth");
const LOCK_DIR = path.resolve("playwright/.auth/.locks");
const MAX_AUTH_AGE_MINUTES = 50; // Làm mới trước khi hết hạn 50 phút (buffer 10 phút)

interface Account {
  id: string;
  email: string;
  password: string;
  des: string;
  role: string;
}

/**
 * Tạo BrowserContext đã xác thực, tự động làm mới token nếu đã cũ.
 * Thay thế cho: browser.newContext({ storageState: `playwright/.auth/${account.id}.json` })
 */
export async function createAuthContext(
  browser: Browser,
  account: Account,
  options?: BrowserContextOptions, // Thêm tham số tùy chọn này
): Promise<BrowserContext> {
  const authFile = path.join(AUTH_DIR, `${account.id}.json`);

  // Kiểm tra nếu auth file đã cũ hoặc không tồn tại -> làm mới
  if (!isAuthFresh(authFile)) {
    await refreshAuth(browser, account, authFile);
  }

  return browser.newContext({
    storageState: authFile,
    ...options, // Gộp các cấu hình như video, screenshot vào đây
  });
}

/**
 * Kiểm tra auth file còn mới (< MAX_AUTH_AGE_MINUTES phút) hay không
 */
function isAuthFresh(authFile: string): boolean {
  if (!fs.existsSync(authFile)) return false;

  const stats = fs.statSync(authFile);
  const diffMinutes =
    (Date.now() - new Date(stats.mtime).getTime()) / (1000 * 60);
  return diffMinutes < MAX_AUTH_AGE_MINUTES;
}

/**
 * Làm mới auth bằng cách login lại, có cơ chế lock để tránh nhiều worker cùng login 1 account
 */
async function refreshAuth(
  browser: Browser,
  account: Account,
  authFile: string,
): Promise<void> {
  const lockPath = path.join(LOCK_DIR, `${account.id}.lock`);

  // Đảm bảo thư mục lock tồn tại
  fs.mkdirSync(LOCK_DIR, { recursive: true });

  // Thử lấy lock (dùng mkdir vì nó atomic trên hầu hết OS)
  const gotLock = tryAcquireLock(lockPath);

  if (gotLock) {
    try {
      // Kiểm tra lần nữa sau khi có lock (worker khác có thể đã refresh xong)
      if (isAuthFresh(authFile)) {
        console.log(
          `[Auth] ${account.email} đã được worker khác làm mới. Bỏ qua.`,
        );
        return;
      }

      console.log(`[Auth] Đang làm mới token cho ${account.email}...`);
      await performLogin(browser, account, authFile);
      console.log(`[Auth] Đã làm mới token cho ${account.email} thành công!`);
    } finally {
      releaseLock(lockPath);
    }
  } else {
    // Worker khác đang login account này -> chờ
    console.log(
      `[Auth] Worker khác đang làm mới ${account.email}, đang chờ...`,
    );
    await waitForLockRelease(lockPath, authFile);
    console.log(`[Auth] ${account.email} đã sẵn sàng.`);
  }
}

/**
 * Thực hiện login Microsoft và lưu auth state
 */
async function performLogin(
  browser: Browser,
  account: Account,
  authFile: string,
): Promise<void> {
  const BASE_URL = process.env.BASE_URL!;
  const context = await browser.newContext();
  const page = await context.newPage();

  try {
    // 1. Mở trang web (redirect sang Microsoft)
    await page.goto(BASE_URL + "/home", {
      timeout: TIMEOUT.LOGIN_NAVIGATE,
    });

    // 2. Điền Email và bấm Next
    await page
      .locator('input[type="email"]')
      .fill(account.email, { timeout: TIMEOUT.LOGIN_NAVIGATE });
    await page
      .locator('input[type="submit"][value="Next"]')
      .click({ timeout: TIMEOUT.LOGIN_NAVIGATE });

    // 3. Điền Password và bấm Sign In
    await page
      .locator('input[type="password"]')
      .fill(account.password, { timeout: TIMEOUT.LOGIN_NAVIGATE });
    await page
      .locator('input[type="submit"][value="Sign in"]')
      .click({ timeout: TIMEOUT.LOGIN_NAVIGATE });

    // 4. Xử lý "Stay signed in?"
    try {
      const staySignedInBtn = page.locator('#idSIButton9, input[value="Yes"]');
      await staySignedInBtn.waitFor({ state: "visible", timeout: 5000 });
      await staySignedInBtn.click();
    } catch {
      // Không gặp màn hình Stay signed in
    }

    // 5. Chờ chuyển hướng về trang chủ
    await page.waitForURL(`${BASE_URL}/**`, {
      timeout: TIMEOUT.LOGIN_NAVIGATE,
    });

    // 6. Lưu auth state
    await context.storageState({ path: authFile });
  } finally {
    await context.close();
  }
}

// ── Lock helpers (dùng mkdir atomic) ──

function tryAcquireLock(lockPath: string): boolean {
  try {
    fs.mkdirSync(lockPath);
    return true;
  } catch {
    // Lock đã tồn tại -> kiểm tra xem có phải lock cũ bị kẹt không (> 2 phút)
    try {
      const stats = fs.statSync(lockPath);
      const ageMs = Date.now() - new Date(stats.mtime).getTime();
      if (ageMs > 2 * 60 * 1000) {
        // Lock cũ bị kẹt, xóa và thử lại
        fs.rmdirSync(lockPath);
        try {
          fs.mkdirSync(lockPath);
          return true;
        } catch {
          return false;
        }
      }
    } catch {
      // Không đọc được lock -> bỏ qua
    }
    return false;
  }
}

function releaseLock(lockPath: string): void {
  try {
    fs.rmdirSync(lockPath);
  } catch {
    // Ignore
  }
}

async function waitForLockRelease(
  lockPath: string,
  authFile: string,
): Promise<void> {
  const maxWait = 90_000; // Chờ tối đa 90 giây
  const interval = 1_000; // Kiểm tra mỗi 1 giây
  let waited = 0;

  while (waited < maxWait) {
    await sleep(interval);
    waited += interval;

    // Lock đã được giải phóng và auth file đã mới
    if (!fs.existsSync(lockPath) && isAuthFresh(authFile)) {
      return;
    }
  }

  // Hết thời gian chờ -> thử tự refresh
  console.log(`[Auth] Hết thời gian chờ lock, sẽ thử tự đăng nhập lại...`);
  releaseLock(lockPath); // Force release lock cũ
}

function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
