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

import fs from 'node:fs/promises'

import { downloadRemoteQrImage, logBrowserSessionDebug, waitForFrame } from './session-login-shared.js'

const QQ_QR_TARGET_SIZE = 144

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

type FrameLike = {
  url: () => string
  locator: (selector: string) => LocatorLike
  waitForTimeout: (milliseconds: number) => Promise<void>
  evaluate: <T = any>(callback: any) => Promise<T>
}

type PageLike = {
  frames: () => FrameLike[]
  waitForTimeout: (milliseconds: number) => Promise<void>
  locator: (selector: string) => LocatorLike
  context: () => {
    request: {
      get: (url: string, options?: any) => Promise<{
        ok: () => boolean
        status: () => number
        body: () => Promise<Buffer>
      }>
    }
  }
}

type QqQrState = {
  visible: boolean
  qrVisible?: boolean
  scanned: boolean
  expired: boolean
  message: string
  frameUrl?: string
}

export async function ensureQqLoginReady(page: PageLike): Promise<void> {
  try {
    const frame = await waitForFrame<FrameLike>(
      page,
      (item) => item.url().includes('xui.ptlogin2.qq.com/cgi-bin/xlogin'),
      10_000,
    )
    const switcher = frame.locator('#switcher_qlogin')

    if (!(await switcher.count())) {
      return
    }

    await switcher.click({ timeout: 3_000 }).catch(() => null)
    await frame.waitForTimeout(250)
  } catch {
    // ignore qq inner tab switch failures; the screenshot retry path will try again
  }
}

export async function captureQqQrImage(page: PageLike, qrImagePath: string): Promise<void> {
  const frame = await waitForFrame<FrameLike>(
    page,
    (item) => item.url().includes('xui.ptlogin2.qq.com/cgi-bin/xlogin'),
    10_000,
  )
  const qrUrl = await resolveQqQrImageUrl(frame)
  const effectiveQrUrl = upgradeQqQrImageUrl(qrUrl)

  logBrowserSessionDebug('qq.capture.qrUrl', {
    qrUrl,
    effectiveQrUrl,
    frameUrl: frame.url(),
  })

  if (effectiveQrUrl) {
    const body = await downloadRemoteQrImage(page, effectiveQrUrl, qrImagePath, frame.url(), 'qq')
    logBrowserSessionDebug('qq.capture.downloadResult', {
      qrUrl: effectiveQrUrl,
      downloaded: Buffer.isBuffer(body) && body.length > 0,
      qrImagePath,
    })

    if (Buffer.isBuffer(body) && body.length > 0) {
      await fs.writeFile(qrImagePath, body)
      return
    }
  }

  logBrowserSessionDebug('qq.capture.fallbackScreenshot', {
    qrImagePath,
  })

  const qrLocator = await findQqQrLocator(page, frame)

  try {
    await qrLocator.screenshot({ path: qrImagePath })
    return
  } catch {
    const iframeLocator = page.locator('#milo-qcwx-frame-qc').first()
    await iframeLocator.waitFor({ state: 'visible', timeout: 15_000 })
    await iframeLocator.screenshot({ path: qrImagePath })
  }
}

export async function findQqQrLocator(page: PageLike, frame: FrameLike | null = null): Promise<LocatorLike> {
  const effectiveFrame =
    frame ||
    await waitForFrame<FrameLike>(page, (item) => item.url().includes('xui.ptlogin2.qq.com/cgi-bin/xlogin'))
  const locator = effectiveFrame.locator('#qrlogin_img')
  await locator.waitFor({ state: 'visible', timeout: 15_000 })
  logBrowserSessionDebug('qq.findQrLocator.visibleReady', {
    frameUrl: effectiveFrame.url(),
  })
  return locator
}

export async function extractQqQrState(page: Pick<PageLike, 'frames'>): Promise<QqQrState> {
  const frame = page.frames().find((item) => item.url().includes('xui.ptlogin2.qq.com/cgi-bin/xlogin'))

  if (!frame) {
    return {
      visible: false,
      scanned: false,
      expired: false,
      message: '',
    }
  }

  try {
    const { qrVisible, bodyText } = await frame
      .evaluate(() => {
        const isVisible = (element: Element | null) => {
          if (!(element instanceof HTMLElement)) {
            return false
          }

          const style = window.getComputedStyle(element)
          const rect = element.getBoundingClientRect()
          return (
            style.display !== 'none' &&
            style.visibility !== 'hidden' &&
            style.opacity !== '0' &&
            rect.width > 0 &&
            rect.height > 0
          )
        }

        const qrImage = document.querySelector('#qrlogin_img')
        const bodyText = String(document.body?.textContent || '').replace(/\s+/g, ' ').trim()

        return {
          qrVisible:
            qrImage instanceof HTMLImageElement &&
            isVisible(qrImage) &&
            Number(qrImage.naturalWidth || 0) > 0 &&
            Number(qrImage.naturalHeight || 0) > 0,
          bodyText,
        }
      })
      .catch(() => ({
        qrVisible: false,
        bodyText: '',
      }))

    return {
      visible: true,
      qrVisible,
      scanned: !qrVisible && /扫描成功|请在手机上确认登录/.test(bodyText),
      expired: qrVisible ? false : /二维码失效|已失效/.test(bodyText),
      message: bodyText.slice(0, 200),
      frameUrl: frame.url(),
    }
  } catch {
    return {
      visible: true,
      qrVisible: false,
      scanned: false,
      expired: false,
      message: '',
      frameUrl: frame.url(),
    }
  }
}

async function resolveQqQrImageUrl(frame: FrameLike): Promise<string> {
  return frame
    .evaluate(() => {
      const qrImage = document.querySelector('#qrlogin_img')

      if (!(qrImage instanceof HTMLImageElement)) {
        return ''
      }

      return String(qrImage.currentSrc || qrImage.src || qrImage.getAttribute('src') || '').trim()
    })
    .then((src) => {
      if (!src) {
        return ''
      }

      try {
        return new URL(src, frame.url()).toString()
      } catch {
        return ''
      }
    })
    .catch(() => '')
}

function upgradeQqQrImageUrl(qrUrl: string): string {
  if (!qrUrl) {
    return ''
  }

  try {
    const url = new URL(qrUrl)

    if (!/xui\.ptlogin2\.qq\.com$/i.test(url.hostname) || !/\/ptqrshow$/i.test(url.pathname)) {
      return qrUrl
    }

    const currentSize = Number(url.searchParams.get('d') || 0)

    if (!Number.isFinite(currentSize) || currentSize < QQ_QR_TARGET_SIZE) {
      url.searchParams.set('d', String(QQ_QR_TARGET_SIZE))
    }

    return url.toString()
  } catch {
    return qrUrl
  }
}
