/// <reference lib="dom" />

import fs from 'node:fs/promises'
import path from 'node:path'

import { isRetryableQrCaptureError, logBrowserSessionDebug } from './session-login-shared.js'
import { captureQqQrImage, ensureQqLoginReady, extractQqQrState } from './session-qq.js'
import { captureWxQrImage, ensureWxLoginReady, extractWxQrState } from './session-wx.js'

type LoginType = 'qq' | 'wx' | string

type LocatorLike = {
  count: () => Promise<number>
  first: () => LocatorLike
  click: (options?: { timeout?: number }) => Promise<void>
  waitFor: (options?: { state?: string; timeout?: number }) => Promise<void>
  screenshot: (options: { path: string }) => Promise<void>
}

type BrowserPageLike = {
  frames: () => any[]
  locator: (selector: string) => LocatorLike
  waitForTimeout: (milliseconds: number) => Promise<void>
  evaluate: <T = any>(callback: any) => Promise<T>
  reload: (options?: { waitUntil?: string }) => Promise<unknown>
  goto: (url: string, options?: { waitUntil?: string }) => Promise<unknown>
  context: () => any
}

type SessionForQrCapture = {
  page: BrowserPageLike
  loginType: LoginType
  sessionDir: string
  qrImagePath?: string
  qrImageBase64?: string
  qrUpdatedAt?: string
  updatedAt?: string
}

type CaptureSessionQrOptions = {
  ensureLoginReady?: boolean
  ensureLoginTab?: typeof ensureLoginTab
  captureQrImageWithRetry?: typeof captureQrImageWithRetry
  readFile?: (path: string, encoding: BufferEncoding) => Promise<string>
  now?: () => string
}

type CaptureQrImageWithRetryOptions = {
  captureQqQrImage?: (page: BrowserPageLike, qrImagePath: string) => Promise<unknown>
  captureWxQrImage?: (page: BrowserPageLike, qrImagePath: string) => Promise<unknown>
  ensureLoginTab?: typeof ensureLoginTab
  isRetryableQrCaptureError?: (error: unknown) => boolean
  logBrowserSessionDebug?: (scope: string, detail?: unknown) => void
}

type EnsureLoginTabOptions = {
  ensureQqLoginReady?: (page: BrowserPageLike) => Promise<unknown>
  ensureWxLoginReady?: (page: BrowserPageLike) => Promise<unknown>
}

type ExtractQrStateOptions = {
  extractQqQrState?: (page: BrowserPageLike) => Promise<unknown>
  extractWxQrState?: (page: BrowserPageLike) => Promise<unknown>
}

export async function captureSessionQr(
  session: SessionForQrCapture,
  {
    ensureLoginReady = true,
    ensureLoginTab: ensureLoginTabImpl = ensureLoginTab,
    captureQrImageWithRetry: captureQrImageWithRetryImpl = captureQrImageWithRetry,
    readFile = fs.readFile,
    now = () => new Date().toISOString(),
  }: CaptureSessionQrOptions = {},
): Promise<void> {
  if (ensureLoginReady) {
    await ensureLoginTabImpl(session.page, session.loginType)
  }

  const qrImagePath = path.join(session.sessionDir, `${session.loginType}-qr.png`)
  await captureQrImageWithRetryImpl(session.page, session.loginType, qrImagePath, {
    ensureLoginTab: ensureLoginTabImpl,
  })

  session.qrImagePath = qrImagePath
  session.qrImageBase64 = await readFile(qrImagePath, 'base64')
  session.qrUpdatedAt = now()
  session.updatedAt = session.qrUpdatedAt
}

export async function captureQrImageWithRetry(
  page: BrowserPageLike,
  loginType: LoginType,
  qrImagePath: string,
  {
    captureQqQrImage: captureQqQrImageImpl = captureQqQrImage,
    captureWxQrImage: captureWxQrImageImpl = captureWxQrImage,
    ensureLoginTab: ensureLoginTabImpl = ensureLoginTab,
    isRetryableQrCaptureError: isRetryableQrCaptureErrorImpl = isRetryableQrCaptureError,
    logBrowserSessionDebug: logBrowserSessionDebugImpl = logBrowserSessionDebug,
  }: CaptureQrImageWithRetryOptions = {},
): Promise<void> {
  const maxAttempts = loginType === 'qq' ? 4 : 3
  let lastError: unknown = null

  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
    try {
      logBrowserSessionDebugImpl('captureQrImageWithRetry.start', {
        loginType,
        attempt,
        maxAttempts,
        qrImagePath,
      })

      if (loginType === 'qq') {
        await captureQqQrImageImpl(page, qrImagePath)
      } else {
        await captureWxQrImageImpl(page, qrImagePath)
      }

      logBrowserSessionDebugImpl('captureQrImageWithRetry.success', {
        loginType,
        attempt,
        qrImagePath,
      })

      return
    } catch (error) {
      lastError = error
      logBrowserSessionDebugImpl('captureQrImageWithRetry.error', {
        loginType,
        attempt,
        message: error instanceof Error ? error.message : String(error),
      })

      if (!isRetryableQrCaptureErrorImpl(error) || attempt === maxAttempts) {
        throw error
      }

      await page.waitForTimeout(500)
      await ensureLoginTabImpl(page, loginType)
    }
  }

  if (lastError) {
    throw lastError
  }

  throw new Error('二维码截图失败')
}

export async function ensureLoginTab(
  page: BrowserPageLike,
  loginType: LoginType,
  {
    ensureQqLoginReady: ensureQqLoginReadyImpl = ensureQqLoginReady,
    ensureWxLoginReady: ensureWxLoginReadyImpl = ensureWxLoginReady,
  }: EnsureLoginTabOptions = {},
): Promise<void> {
  const tabSelector = loginType === 'wx' ? '.wx-tab' : '.qc-tab'
  const loginButton = page.locator('#unlogin')
  let interacted = false

  if (await loginButton.count()) {
    try {
      await loginButton.first().click({ timeout: 2_000 })
      interacted = true
    } catch {
      // ignore when the login layer is already open
    }
  }

  const tab = page.locator(tabSelector).first()

  if (await tab.count()) {
    try {
      await tab.click({ timeout: 2_000 })
      interacted = true
    } catch {
      // ignore tab click failures
    }
  }

  if (interacted) {
    await page.waitForTimeout(250)
  }

  if (loginType === 'qq') {
    await ensureQqLoginReadyImpl(page)
    return
  }

  if (loginType === 'wx') {
    await ensureWxLoginReadyImpl(page)
  }
}

export async function extractHostState(page: BrowserPageLike) {
  return page.evaluate(() => {
    const isVisible = (element: Element | null) => {
      if (!element) {
        return false
      }

      const style = window.getComputedStyle(element)

      return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'
    }

    const unlogin = document.querySelector('#unlogin')
    const logined = document.querySelector('#logined')

    return {
      unloginVisible: isVisible(unlogin),
      loginedVisible: isVisible(logined),
      loginText: String(logined?.textContent || '').trim(),
    }
  })
}

export async function reloadActivityPageForPresentation(page: BrowserPageLike, activityUrl: string): Promise<void> {
  try {
    await page.reload({ waitUntil: 'domcontentloaded' })
  } catch {
    await page.goto(activityUrl, { waitUntil: 'domcontentloaded' })
  }

  await page.waitForTimeout(2_500)
}

export async function extractQrState(
  page: BrowserPageLike,
  loginType: LoginType,
  {
    extractQqQrState: extractQqQrStateImpl = extractQqQrState,
    extractWxQrState: extractWxQrStateImpl = extractWxQrState,
  }: ExtractQrStateOptions = {},
): Promise<unknown> {
  if (loginType === 'qq') {
    return extractQqQrStateImpl(page)
  }

  return extractWxQrStateImpl(page)
}
