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

import {
  BROWSER_SESSION_DATA_ROOT,
  REDEEM_SUCCESS_AUTO_CLOSE_MS,
} from './session-service-config.js'
import { SessionServiceError } from './session-service-runtime.js'
import {
  resolveInitialQrSessionState,
  SESSION_TTL_MS,
} from './session-state.js'

type BrowserContextLike = {
  close?: () => Promise<void>
}

export type TencentBrowserSession = Record<string, any> & {
  sessionId: string
  loginType: string
  status: string
  notice: string
  createdAt: string
  updatedAt: string
  expiresAt: number
  autoCloseAt: number
  autoCloseTimer: ReturnType<typeof setTimeout> | null
  sessionDir: string
  browserContext: any
  page: any
  browserContextClosed?: boolean
  presentationSyncAttempts: number
}

type RegisterSessionInput = {
  loginType: string
  browserContext: BrowserContextLike
  page: unknown
}

type CloseSessionDependency = {
  closeSession: (session: TencentBrowserSession, options: { markClosed: boolean }) => Promise<void>
}

type CloseSessionOptions = {
  markClosed: boolean
  persistSessionState: (session: TencentBrowserSession) => Promise<unknown>
  closeBrowserIfIdle: () => Promise<unknown>
}

type PersistSessionStateOptions = {
  buildSessionPayload: (session: TencentBrowserSession) => unknown
}

const sessions = new Map<string, TencentBrowserSession>()

export async function registerTencentBrowserSession({
  loginType,
  browserContext,
  page,
}: RegisterSessionInput): Promise<TencentBrowserSession> {
  const sessionId = createSessionId()
  const sessionDir = path.join(BROWSER_SESSION_DATA_ROOT, sessionId)
  await fs.mkdir(sessionDir, { recursive: true })

  const now = new Date().toISOString()
  const session = {
    sessionId,
    loginType,
    status: 'created',
    notice: '浏览器会话已创建',
    createdAt: now,
    updatedAt: now,
    expiresAt: Date.now() + SESSION_TTL_MS,
    autoCloseAt: 0,
    autoCloseTimer: null,
    sessionDir,
    browserContext,
    page,
    qrImageBase64: '',
    qrImagePath: '',
    qrUpdatedAt: '',
    lastState: null,
    lastRedeem: null,
    lastReview: null,
    lastError: '',
    presentationSyncAttempts: 0,
  }

  sessions.set(sessionId, session)
  return session
}

export function listActiveTencentBrowserSessions(): TencentBrowserSession[] {
  return [...sessions.values()]
}

export async function getRequiredSession(sessionId: unknown): Promise<TencentBrowserSession> {
  const key = String(sessionId || '').trim()

  if (!key) {
    throw new SessionServiceError('缺少 sessionId', {
      statusCode: 400,
      errorCode: 'missing_session_id',
    })
  }

  const session = sessions.get(key)

  if (!session) {
    throw new SessionServiceError('浏览器会话不存在或已过期', {
      statusCode: 404,
      errorCode: 'session_not_found',
    })
  }

  return session
}

export function applyInitialQrSessionState(session: TencentBrowserSession): void {
  resetTencentSessionAutoClose(session)
  Object.assign(session, resolveInitialQrSessionState(session.loginType))
}

export function armRedeemedSessionAutoClose(session: TencentBrowserSession, { closeSession }: CloseSessionDependency): void {
  resetTencentSessionAutoClose(session)

  const autoCloseAt = Date.now() + REDEEM_SUCCESS_AUTO_CLOSE_MS
  session.autoCloseAt = autoCloseAt
  session.expiresAt = autoCloseAt
  session.autoCloseTimer = setTimeout(() => {
    void closeSession(session, { markClosed: true }).catch(() => {
      // ignore delayed auto-close failures
    })
  }, REDEEM_SUCCESS_AUTO_CLOSE_MS)
}

export function resetTencentSessionAutoClose(session?: TencentBrowserSession | null): void {
  if (session?.autoCloseTimer) {
    clearTimeout(session.autoCloseTimer)
    session.autoCloseTimer = null
  }

  if (session) {
    session.autoCloseAt = 0
  }
}

export async function cleanupExpiredTencentBrowserSessions({ closeSession }: CloseSessionDependency): Promise<void> {
  const expired = listActiveTencentBrowserSessions().filter((session) => session.expiresAt <= Date.now())

  await Promise.all(expired.map((session) => closeSession(session, { markClosed: false })))
}

export async function closeSession(
  session: TencentBrowserSession,
  {
    markClosed,
    persistSessionState,
    closeBrowserIfIdle,
  }: CloseSessionOptions,
): Promise<void> {
  resetTencentSessionAutoClose(session)
  sessions.delete(session.sessionId)

  try {
    await session.browserContext?.close()
  } catch {
    // ignore browser context close failures
  }

  session.browserContextClosed = true
  session.updatedAt = new Date().toISOString()

  if (markClosed) {
    session.status = 'closed'
    session.notice = '浏览器会话已关闭'
    await persistSessionState(session)
  }

  await closeBrowserIfIdle()
}

export async function persistSessionState(
  session: TencentBrowserSession,
  { buildSessionPayload }: PersistSessionStateOptions,
): Promise<void> {
  await fs.mkdir(session.sessionDir, { recursive: true })
  await fs.writeFile(
    path.join(session.sessionDir, 'session.json'),
    JSON.stringify(buildSessionPayload(session), null, 2),
    'utf8',
  )
}

function createSessionId(): string {
  let sessionId = ''

  do {
    sessionId = `txbs-${crypto.randomBytes(6).toString('hex')}`
  } while (sessions.has(sessionId))

  return sessionId
}
