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

import fs from 'node:fs/promises'

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

type LocatorLike = {
  first: () => LocatorLike
  waitFor: (options?: { state?: string; timeout?: number }) => Promise<void>
  screenshot: (options: { path: string }) => Promise<void>
  isVisible: () => Promise<boolean>
  click: (options?: { timeout?: number }) => Promise<void>
  evaluate: <T = unknown>(callback: (element: Element) => T | Promise<T>) => Promise<T>
}

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>
  context: () => {
    request: {
      get: (url: string, options?: any) => Promise<{
        ok: () => boolean
        status: () => number
        body: () => Promise<Buffer>
      }>
    }
  }
}

type WxLoginViewState = {
  qrVisible: boolean
  quickLoginVisible: boolean
  switchToNormalVisible: boolean
  qrCandidates: Array<{
    index: number
    visible: boolean
    src: string
    naturalWidth: number
  }>
  bodyText: string
}

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

export async function ensureWxLoginReady(page: PageLike): Promise<void> {
  try {
    const frame = await waitForFrame<FrameLike>(
      page,
      (item) => item.url().includes('open.weixin.qq.com/connect/qrconnect'),
      10_000,
    )

    for (let attempt = 1; attempt <= 12; attempt += 1) {
      const viewState = await inspectWxLoginView(frame)
      logBrowserSessionDebug('wx.ensureQrMode.viewState', {
        attempt,
        frameUrl: frame.url(),
        qrVisible: viewState.qrVisible,
        quickLoginVisible: viewState.quickLoginVisible,
        switchToNormalVisible: viewState.switchToNormalVisible,
        qrCandidates: viewState.qrCandidates,
        bodyText: viewState.bodyText.slice(0, 160),
      })

      if (viewState.qrVisible) {
        return
      }

      if (
        viewState.quickLoginVisible ||
        viewState.switchToNormalVisible ||
        /使用其他头像、昵称或账号|微信快捷登录/.test(viewState.bodyText)
      ) {
        const switched = await switchWxQuickLoginToQr(frame)
        logBrowserSessionDebug('wx.ensureQrMode.switchToNormal', {
          attempt,
          switched,
        })

        if (switched) {
          await frame.waitForTimeout(700)
          continue
        }
      }

      await frame.waitForTimeout(400)
    }
  } catch {
    // ignore wx quick-login switch failures; the screenshot retry path will try again
  }
}

export async function captureWxQrImage(page: PageLike, qrImagePath: string): Promise<void> {
  const qrLocator = await findWxQrLocator(page)
  const frame = await waitForFrame<FrameLike>(
    page,
    (item) => item.url().includes('open.weixin.qq.com/connect/qrconnect'),
    10_000,
  )

  const qrUrl = await resolveWxQrImageUrl(frame)
  logBrowserSessionDebug('wx.capture.qrUrl', {
    qrUrl,
    frameUrl: frame.url(),
  })

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

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

  logBrowserSessionDebug('wx.capture.fallbackScreenshot', {
    qrImagePath,
  })
  await qrLocator.screenshot({ path: qrImagePath })
}

export async function findWxQrLocator(page: PageLike): Promise<LocatorLike> {
  await ensureWxLoginReady(page)

  const frame = await waitForFrame<FrameLike>(
    page,
    (item) => item.url().includes('open.weixin.qq.com/connect/qrconnect'),
  )
  const viewState = await inspectWxLoginView(frame)
  logBrowserSessionDebug('wx.findQrLocator.beforeWait', {
    frameUrl: frame.url(),
    qrVisible: viewState.qrVisible,
    qrCandidates: viewState.qrCandidates,
    bodyText: viewState.bodyText.slice(0, 160),
  })

  const locator = frame.locator('.js_qrcode_img:visible').first()
  await locator.waitFor({ state: 'visible', timeout: 15_000 })
  logBrowserSessionDebug('wx.findQrLocator.visibleReady', {
    frameUrl: frame.url(),
  })
  return locator
}

export async function extractWxQrState(page: Pick<PageLike, 'frames'>): Promise<WxQrState> {
  const frame = page.frames().find((item) => item.url().includes('open.weixin.qq.com/connect/qrconnect'))

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

  try {
    const viewState = await inspectWxLoginView(frame)
    const qrVisible = viewState.qrVisible
    const bodyText = viewState.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 inspectWxLoginView(frame: FrameLike): Promise<WxLoginViewState> {
  return 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 quickLogin = document.querySelector('.js_quick_login')
      const switchToNormal = document.querySelector('.js_switchToNormal')
      const qrCandidates = Array.from(document.querySelectorAll('.js_qrcode_img')).map((item, index) => {
        if (!(item instanceof HTMLImageElement)) {
          return {
            index,
            visible: false,
            src: '',
            naturalWidth: 0,
          }
        }

        return {
          index,
          visible: isVisible(item),
          src: String(item.currentSrc || item.src || item.getAttribute('src') || '').trim(),
          naturalWidth: Number(item.naturalWidth || 0),
        }
      })
      const visibleQrCandidate = qrCandidates.find((item) => item.visible && item.naturalWidth > 0)

      return {
        qrVisible: Boolean(visibleQrCandidate),
        quickLoginVisible: isVisible(quickLogin),
        switchToNormalVisible: isVisible(switchToNormal),
        qrCandidates,
        bodyText: String(document.body?.textContent || '').replace(/\s+/g, ' ').trim(),
      }
    })
    .catch(() => ({
      qrVisible: false,
      quickLoginVisible: false,
      switchToNormalVisible: false,
      qrCandidates: [],
      bodyText: '',
    }))
}

async function switchWxQuickLoginToQr(frame: FrameLike): Promise<boolean> {
  const switcherCandidates = [
    frame.locator('.js_switchToNormal:visible').first(),
    frame.locator('.js_switchToNormal').first(),
    frame.locator('button:has-text("使用其他头像、昵称或账号")').first(),
    frame.locator('text=使用其他头像、昵称或账号').first(),
  ]

  for (const candidate of switcherCandidates) {
    const visible = await candidate.isVisible().catch(() => false)

    if (!visible) {
      continue
    }

    logBrowserSessionDebug('wx.switchToNormal.candidateVisible', {
      candidateIndex: switcherCandidates.indexOf(candidate),
    })

    const clicked =
      (await candidate
        .click({ timeout: 2_000 })
        .then(() => true)
        .catch(() => false)) ||
      (await candidate
        .evaluate((element) => {
          if (!(element instanceof HTMLElement)) {
            return false
          }

          element.click()
          return true
        })
        .catch(() => false))

    if (clicked) {
      logBrowserSessionDebug('wx.switchToNormal.clicked', {
        candidateIndex: switcherCandidates.indexOf(candidate),
      })
      return true
    }
  }

  return frame
    .evaluate(() => {
      const candidates = Array.from(document.querySelectorAll('button, a, div')).filter((element) => {
        if (!(element instanceof HTMLElement)) {
          return false
        }

        const text = String(element.textContent || '').replace(/\s+/g, ' ').trim()
        const style = window.getComputedStyle(element)
        const rect = element.getBoundingClientRect()

        return (
          /使用其他头像、昵称或账号/.test(text) &&
          style.display !== 'none' &&
          style.visibility !== 'hidden' &&
          style.opacity !== '0' &&
          rect.width > 0 &&
          rect.height > 0
        )
      })

      const target = candidates[0]

      if (!(target instanceof HTMLElement)) {
        return false
      }

      target.click()
      return true
    })
    .catch(() => false)
}

async function resolveWxQrImageUrl(frame: FrameLike): Promise<string> {
  return 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 qrImages = Array.from(document.querySelectorAll('.js_qrcode_img'))
      const qrImage =
        qrImages.find((item) => item instanceof HTMLImageElement && isVisible(item)) ||
        qrImages.find((item) => item instanceof HTMLImageElement) ||
        null

      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(() => '')
}
