import html2canvas from 'html2canvas'
import { storeToRefs } from 'pinia'
import globalApi from '@/api/global'
import { userStore } from '@/stores'
import { $XXL, getNativeApi } from '@/utils'
import { useTips } from './useTips'
import { uploadFileToCos, uploadNativeFilePathToCos } from '@/utils/cosUpload'

let monitorTimer: ReturnType<typeof setInterval> | null = null
let monitorSessionId = ''
let isUploadingMonitorSnapshot = false
let monitorState: 0 | 1 = 0

const MONITOR_SNAPSHOT_INTERVAL = 10000
const MONITOR_MSG_TYPE = 'monitorStart'
const MONITOR_STOP_MSG_TYPE = 'monitorStop'
const MONITOR_LOG_PREFIX = '[class-monitor]'
const MONITOR_SNAPSHOT_TARGET_SIZE = 100 * 1024
const MONITOR_SNAPSHOT_MIN_WIDTH = 480
const MONITOR_SNAPSHOT_MIN_HEIGHT = 270

const monitorLog = (...args: unknown[]) => console.log(MONITOR_LOG_PREFIX, ...args)
const monitorWarn = (...args: unknown[]) => console.warn(MONITOR_LOG_PREFIX, ...args)
const monitorError = (...args: unknown[]) => console.error(MONITOR_LOG_PREFIX, ...args)

const getServerOrigin = () => {
  try {
    return new URL(import.meta.env.VITE_SERVER_URL).origin
  } catch {
    return ''
  }
}

const isLocalFilePath = (path: string) => {
  return /^(file:\/\/|\/storage\/|\/sdcard\/|content:\/\/)/i.test(path)
}

const normalizeUploadPath = (res: any) => {
  const rawPath = String(
    res?.data?.url ||
      res?.url ||
      res?.data?.filePath ||
      res?.filePath ||
      res?.data?.path ||
      res?.path ||
      '',
  )
    .replace(/[`]/g, '')
    .replace(/http:\/\//gi, 'https://')
    .trim()

  if (!rawPath) return ''
  if (isLocalFilePath(rawPath)) {
    monitorWarn('upload result is local path, ignore it', rawPath)
    return ''
  }
  if (/^https?:\/\//i.test(rawPath)) return rawPath

  const origin = getServerOrigin()
  if (origin && rawPath.startsWith('/')) return `${origin}${rawPath}`
  return rawPath
}

const parseNativeScreenshotPath = (raw: unknown): string => {
  if (typeof raw === 'string' && raw.trim()) return raw.trim()
  if (!raw || typeof raw !== 'object') return ''
  const r = raw as Record<string, unknown>
  const directPath = r.filePath ?? r.path ?? r.localPath ?? r.url
  if (typeof directPath === 'string' && directPath.trim()) return directPath.trim()
  const d = r.data
  if (typeof d === 'string' && d.trim()) return d.trim()
  if (d && typeof d === 'object') {
    const o = d as Record<string, unknown>
    const p = o.filePath ?? o.path ?? o.localPath
    if (typeof p === 'string' && p.trim()) return p.trim()
  }
  return ''
}

const uploadNativeScreenshot = async () => {
  try {
    const raw = $XXL.getScreenshot() || {}
    monitorLog('native getScreenshot result', raw)
    const filePath = parseNativeScreenshotPath(raw)
    if (!filePath) {
      monitorWarn('native screenshot path empty')
      return ''
    }
    monitorLog('native screenshot path', filePath)

    const res = await uploadNativeFilePathToCos(filePath, config => $XXL.uploadFile(config), {
      fileType: 1,
    })
    const url = normalizeUploadPath(res)
    monitorLog('native upload result', { raw: res, url })
    return url
  } catch (err) {
    monitorWarn('native screenshot upload failed', err)
    return ''
  }
}

/** App WebView 无原生截图时仅能用 DOM 截图；尽量贴合「当前视口」并降低高 DPR 白屏概率 */
const getMonitorCaptureElement = (): HTMLElement => {
  const app = document.getElementById('app')
  if (app && (app.offsetWidth > 0 || app.offsetHeight > 0)) return app
  const scrollRoot = document.scrollingElement
  if (scrollRoot instanceof HTMLElement) return scrollRoot
  return document.documentElement
}

const prepareMonitorDomForCapture = async () => {
  try {
    await document.fonts?.ready
  } catch {
    //
  }
  await new Promise<void>(resolve => {
    requestAnimationFrame(() => {
      requestAnimationFrame(() => resolve())
    })
  })
}

const canvasToBlob = (canvas: HTMLCanvasElement, type: string, quality?: number) =>
  new Promise<Blob | null>(resolve => {
    canvas.toBlob(
      blob => {
        resolve(blob)
      },
      type,
      quality,
    )
  })

const resizeCanvas = (source: HTMLCanvasElement, ratio: number) => {
  const canvas = document.createElement('canvas')
  canvas.width = Math.max(1, Math.round(source.width * ratio))
  canvas.height = Math.max(1, Math.round(source.height * ratio))
  const ctx = canvas.getContext('2d')
  if (!ctx) return source
  ctx.drawImage(source, 0, 0, canvas.width, canvas.height)
  return canvas
}

const canvasToMonitorFile = async (canvas: HTMLCanvasElement) => {
  const qualities = [0.72, 0.6, 0.5, 0.42, 0.35, 0.3, 0.25]
  let current = canvas
  let bestBlob: Blob | null = null
  let bestInfo = {
    width: canvas.width,
    height: canvas.height,
    quality: qualities[0],
  }

  for (let resizeCount = 0; resizeCount < 8; resizeCount++) {
    for (const quality of qualities) {
      const blob = await canvasToBlob(current, 'image/jpeg', quality)
      if (!blob) return null
      if (!bestBlob || blob.size < bestBlob.size) {
        bestBlob = blob
        bestInfo = {
          width: current.width,
          height: current.height,
          quality,
        }
      }
      if (blob.size <= MONITOR_SNAPSHOT_TARGET_SIZE) {
        monitorLog('web screenshot compressed', {
          width: current.width,
          height: current.height,
          quality,
          size: blob.size,
        })
        return new File([blob], `monitor-${Date.now()}.jpg`, { type: 'image/jpeg' })
      }
    }

    if (
      current.width <= MONITOR_SNAPSHOT_MIN_WIDTH ||
      current.height <= MONITOR_SNAPSHOT_MIN_HEIGHT
    ) {
      break
    }
    current = resizeCanvas(current, 0.85)
  }

  if (!bestBlob) return null
  monitorWarn('web screenshot compressed but still over target', {
    ...bestInfo,
    size: bestBlob.size,
    target: MONITOR_SNAPSHOT_TARGET_SIZE,
  })
  return new File([bestBlob], `monitor-${Date.now()}.jpg`, { type: 'image/jpeg' })
}

const createWebScreenshotFile = async (): Promise<File | null> => {
  await prepareMonitorDomForCapture()

  const vw = window.innerWidth
  const vh = window.innerHeight
  const scale = Math.min(window.devicePixelRatio || 1, 1)
  monitorLog('web screenshot start', {
    href: window.location.href,
    viewport: `${vw}x${vh}`,
    dpr: window.devicePixelRatio,
    scale,
    scrollX: window.scrollX,
    scrollY: window.scrollY,
  })
  const base = {
    useCORS: true,
    allowTaint: false,
    backgroundColor: '#ffffff' as const,
    scale,
    imageTimeout: 15000,
    logging: import.meta.env.DEV,
  }
  const win = {
    scrollX: -window.scrollX,
    scrollY: -window.scrollY,
    windowWidth: Math.max(document.documentElement.clientWidth, vw),
    windowHeight: Math.max(document.documentElement.clientHeight, vh),
  }

  const attempts: Array<() => Promise<HTMLCanvasElement>> = [
    // 视口截图常用写法（对 WebView 比单纯裁 x/y 更稳）
    () =>
      html2canvas(document.body, {
        ...base,
        foreignObjectRendering: false,
        ...win,
      }),
    () =>
      html2canvas(document.body, {
        ...base,
        foreignObjectRendering: true,
        ...win,
      }),
    () => {
      const el = getMonitorCaptureElement()
      const sx = window.scrollX
      const sy = window.scrollY
      return html2canvas(el, {
        ...base,
        foreignObjectRendering: false,
        x: sx,
        y: sy,
        width: vw,
        height: vh,
        windowWidth: vw,
        windowHeight: vh,
      })
    },
  ]

  for (const [index, run] of attempts.entries()) {
    try {
      monitorLog('web screenshot attempt start', index + 1)
      const canvas = await run()
      monitorLog('web screenshot canvas', {
        attempt: index + 1,
        width: canvas.width,
        height: canvas.height,
      })
      if (!canvas.width || !canvas.height) {
        monitorWarn('web screenshot canvas empty', index + 1)
        continue
      }
      const file = await canvasToMonitorFile(canvas)
      if (file) {
        monitorLog('web screenshot file ready', {
          attempt: index + 1,
          name: file.name,
          size: file.size,
          type: file.type,
        })
        return file
      }
      monitorWarn('web screenshot toBlob returned empty', index + 1)
    } catch (err) {
      monitorWarn('web screenshot attempt failed', index + 1, err)
    }
  }
  monitorWarn('web screenshot all attempts failed')
  return null
}

const uploadWebScreenshot = async () => {
  monitorLog('web upload start')
  const file = await createWebScreenshotFile()
  if (!file) {
    monitorWarn('web upload skipped because screenshot file is empty')
    return ''
  }

  const res = await uploadFileToCos(file, { fileType: 1 })
  const url = normalizeUploadPath(res)
  monitorLog('web upload result', { raw: res, url })
  return url
}

const uploadMonitorScreenshot = async () => {
  // $xxl（学习机）与 $xxl_universal（通用 App）都可能提供 getScreenshot，不能只用 sys_type 区分
  const nativeApi = getNativeApi()
  monitorLog('choose screenshot mode', {
    hasNativeApi: !!nativeApi,
    hasGetScreenshot: typeof nativeApi?.getScreenshot === 'function',
    hasUploadFile: typeof nativeApi?.uploadFile === 'function',
  })
  if (nativeApi && typeof nativeApi.getScreenshot === 'function') {
    const url = await uploadNativeScreenshot()
    if (url) return url
    monitorWarn('native screenshot empty, fallback to web screenshot')
  }
  return await uploadWebScreenshot()
}

const reportMonitorSnapshot = async (sessionId: string) => {
  const { userInfo } = storeToRefs(userStore())
  if (isUploadingMonitorSnapshot || monitorSessionId !== sessionId) {
    monitorLog('snapshot skipped', {
      isUploadingMonitorSnapshot,
      monitorSessionId,
      sessionId,
    })
    return
  }

  isUploadingMonitorSnapshot = true
  monitorLog('snapshot report start', { sessionId, userId: userInfo.value.userId })
  try {
    const url = await uploadMonitorScreenshot()
    if (!url || monitorSessionId !== sessionId) {
      monitorWarn('snapshot report skipped after screenshot', {
        url,
        monitorSessionId,
        sessionId,
      })
      return
    }
    monitorLog('snapshot final image url', url)

    await globalApi.reportSnapshot({
      sessionId,
      url,
      userId: userInfo.value.userId,
    })
    monitorLog('snapshot report success', { sessionId, url })
  } catch (err) {
    monitorError('snapshot report failed', err)
  } finally {
    isUploadingMonitorSnapshot = false
    monitorLog('snapshot report end', { sessionId })
  }
}

/** 与老师讲解 WS 等场景兼容：外层 type 可能是 push/classMessage，监控类型在 msg.type */
export const resolveMonitorWsType = (data: anyObj): string | undefined => {
  const nested = data?.msg?.type
  if (nested === MONITOR_STOP_MSG_TYPE || nested === MONITOR_MSG_TYPE) return nested
  const top = data?.type
  if (top === MONITOR_STOP_MSG_TYPE || top === MONITOR_MSG_TYPE) return top
  return undefined
}

export const stopClassMonitor = (showTip = false) => {
  const isMonitoring = !!monitorTimer || !!monitorSessionId || monitorState === 1
  monitorLog('stop monitor', { showTip, isMonitoring, monitorSessionId, monitorState })
  if (monitorTimer) {
    clearInterval(monitorTimer)
    monitorTimer = null
  }
  if (showTip && isMonitoring) {
    useTips({
      tips: '已结束课堂监控',
      showCancel: false,
    })
  }
  monitorSessionId = ''
  monitorState = 0
}

const startClassMonitor = (msg: anyObj) => {
  const sessionId = msg?.sessionId
  monitorLog('start monitor message', msg)
  if (!sessionId) {
    monitorWarn('start monitor ignored because sessionId is empty')
    return
  }
  if (monitorTimer && monitorSessionId === sessionId) {
    monitorLog('start monitor ignored because session is already running', sessionId)
    return
  }

  stopClassMonitor()
  monitorSessionId = sessionId
  monitorState = 1
  useTips({
    tips: '已进入课堂监控状态',
    showCancel: false,
  })
  reportMonitorSnapshot(sessionId)
  monitorTimer = setInterval(() => reportMonitorSnapshot(sessionId), MONITOR_SNAPSHOT_INTERVAL)
}

const getMonitorSessionId = (data: anyObj) => {
  const sessionId =
    data?.sessionId ||
    data?.monitorSessionId ||
    data?.classMonitorSessionId ||
    data?.monitorInfo?.sessionId ||
    data?.classMonitor?.sessionId

  return typeof sessionId === 'string' || typeof sessionId === 'number'
    ? String(sessionId).trim()
    : ''
}

export const handleClassMonitorMessage = (data: anyObj) => {
  const monitorType = resolveMonitorWsType(data)
  monitorLog('handle monitor ws message', { monitorType, data })
  if (monitorType === MONITOR_STOP_MSG_TYPE) {
    stopClassMonitor(true)
    return true
  }
  if (monitorType !== MONITOR_MSG_TYPE) return false

  const msg: anyObj = {
    ...(data && typeof data === 'object' ? data : {}),
    ...(data?.msg && typeof data.msg === 'object' && !Array.isArray(data.msg) ? data.msg : {}),
  }
  const monitoring = msg?.monitoring
  if (monitoring === true || monitoring === 'true') {
    startClassMonitor(msg)
    return true
  }
  if (monitoring === false || monitoring === 'false') {
    stopClassMonitor(true)
    return true
  }
  return false
}

export const syncClassMonitorState = (state: unknown) => {
  const stateInfo = state && typeof state === 'object' ? (state as anyObj) : {}
  const nextState = Number(stateInfo.monitorState ?? state)
  if (nextState !== 0 && nextState !== 1) return

  if (nextState === 0) {
    stopClassMonitor(true)
    return
  }

  const sessionId = getMonitorSessionId(stateInfo)
  if (!sessionId) {
    monitorWarn('sync monitor state ignored because sessionId is empty', stateInfo)
    monitorState = 1
    return
  }

  startClassMonitor({
    ...stateInfo,
    sessionId,
  })
}
