/**
 * server.js - Node.js 入口（阿里云轻量服务器版本）
 * 同时承担：API 服务 + 静态前端文件托管
 * 启动：node server.js
 */
import 'dotenv/config'
import { serve } from '@hono/node-server'
import { fileURLToPath } from 'url'
import path from 'path'
import fs from 'fs'

// ────────────────────────────────────────────────────────────────────────
// 全局兜底：吞掉 Node 内置 undici 在 SSE / fetch 场景下偶发的 terminated /
// UND_ERR_SOCKET / abort 类瞬态 rejection，避免 PM2 cluster worker 被无谓重启。
// 这些错误已在路由层显式捕获并向客户端返回错误事件；undici 内部还会再抛一次
// 内部 promise 的 rejection（来自 Fetch.onAborted），必须在进程级吃掉。
// ────────────────────────────────────────────────────────────────────────
const TRANSIENT_NET_RE = /terminated|UND_ERR_SOCKET|UND_ERR_REQ_RETRY|aborted|abort|other side closed|ECONNRESET|EPIPE|HPE_/i
function isTransientNetError(reason) {
  if (!reason) return false
  const msg = String(reason?.message || reason || '')
  const causeMsg = String(reason?.cause?.message || '')
  const causeCode = String(reason?.cause?.code || reason?.code || '')
  return TRANSIENT_NET_RE.test(msg + ' ' + causeMsg + ' ' + causeCode)
}
process.on('unhandledRejection', (reason) => {
  if (isTransientNetError(reason)) {
    console.warn('[server] suppressed transient unhandledRejection:', reason?.message || reason)
    return
  }
  console.error('[server] unhandledRejection:', reason)
})
process.on('uncaughtException', (err) => {
  if (isTransientNetError(err)) {
    console.warn('[server] suppressed transient uncaughtException:', err?.message || err)
    return
  }
  console.error('[server] uncaughtException:', err)
})

import app from './src/index.js'

const __dirname = path.dirname(fileURLToPath(import.meta.url))
// 前端 dist 目录（项目根目录的 dist/）
const DIST_DIR = process.env.DIST_DIR || path.join(__dirname, '../dist')

// ─── 静态文件内存缓存（LRU 策略，最多 200 条）───
const FILE_CACHE_MAX = 200
const fileCache = new Map()
function readCached(filePath) {
  if (fileCache.has(filePath)) {
    // 命中时删除再重新 set，保证该 key 移到末尾（最新访问）
    const buf = fileCache.get(filePath)
    fileCache.delete(filePath)
    fileCache.set(filePath, buf)
    return buf
  }
  const buf = fs.readFileSync(filePath)
  fileCache.set(filePath, buf)
  // 超过上限时删除最早插入的 key（Map 迭代顺序 = 插入顺序）
  if (fileCache.size > FILE_CACHE_MAX) {
    const oldest = fileCache.keys().next().value
    fileCache.delete(oldest)
  }
  return buf
}

// 根据路径决定 Cache-Control
// assets/ 下的文件名带 contenthash，永久缓存；index.html 不缓存（保证更新后立即生效）
function cacheControl(reqPath, filePath) {
  if (reqPath.startsWith('/assets/')) {
    return 'public, max-age=31536000, immutable'   // 1 年
  }
  const ext = path.extname(filePath)
  if (['.js', '.css', '.woff', '.woff2', '.ttf', '.png', '.jpg', '.webp', '.svg'].includes(ext)) {
    return 'public, max-age=86400'                  // 无 hash 的静态资源：1 天
  }
  return 'no-cache, no-store, must-revalidate'      // index.html 等：不缓存
}

// 静态文件 MIME 类型表
const MIME = {
  '.html': 'text/html; charset=utf-8',
  '.js':   'application/javascript',
  '.mjs':  'application/javascript',
  '.css':  'text/css',
  '.png':  'image/png',
  '.jpg':  'image/jpeg',
  '.jpeg': 'image/jpeg',
  '.svg':  'image/svg+xml',
  '.ico':  'image/x-icon',
  '.json': 'application/json',
  '.woff': 'font/woff',
  '.woff2':'font/woff2',
  '.ttf':  'font/ttf',
  '.webp': 'image/webp',
}

// 服务静态前端文件（非 /api 路径）
app.use('/*', async (c, next) => {
  const reqPath = c.req.path

  // API 请求直接交后续路由
  if (reqPath.startsWith('/api/')) return next()

  const filePath = path.join(DIST_DIR, reqPath === '/' ? 'index.html' : reqPath)
  if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
    const ext = path.extname(filePath)
    const content = readCached(filePath)
    return new Response(content, {
      headers: {
        'Content-Type': MIME[ext] || 'application/octet-stream',
        'Cache-Control': cacheControl(reqPath, filePath),
      }
    })
  }

  // SPA fallback：所有未匹配路径返回 index.html（前端路由）
  const indexPath = path.join(DIST_DIR, 'index.html')
  if (fs.existsSync(indexPath)) {
    const content = readCached(indexPath)
    return new Response(content, {
      headers: {
        'Content-Type': 'text/html; charset=utf-8',
        'Cache-Control': 'no-cache, no-store, must-revalidate',
      }
    })
  }

  return next()
})

const port = Number(process.env.PORT || 4000)
serve({ fetch: app.fetch, port }, (info) => {
  console.log(`✅ AIPROS API + 前端 运行中`)
  console.log(`   地址: http://localhost:${info.port}`)
  console.log(`   数据库: ${process.env.DB_PATH || 'data/aipro.db'}`)
  console.log(`   前端: ${DIST_DIR}`)
  console.log(`   上传目录: ${process.env.UPLOADS_DIR || 'uploads/'}`)
})
