import { Hono } from 'hono'
import { requireIdentity } from '../middleware/auth.js'
import { genId } from '../utils/id.js'
import { DB } from '../db.js'
import { writeFileSync, mkdirSync, createReadStream, existsSync, statSync, statfsSync } from 'fs'
import { Readable } from 'stream'
import { fileURLToPath } from 'url'
import path from 'path'

const __dirname = path.dirname(fileURLToPath(import.meta.url))
// 上传目录：cf-worker/uploads/（部署时可通过 UPLOADS_DIR 环境变量覆盖）
// 注意：本文件位于 src/routes/，需向上两级才能到达 cf-worker/uploads
const UPLOADS_ROOT = process.env.UPLOADS_DIR || path.join(__dirname, '../../uploads')
const ACCEPT_EXT = new Set(['md', 'markdown', 'txt', 'zip'])
const MAX_UPLOAD_SIZE = 50 * 1024 * 1024

const app = new Hono()

/**
 * POST /api/uploads
 * 接收 multipart/form-data，保存到本地磁盘，登记 files 表
 * 返回：{ file_id, r2_key(本地路径), filename, size }
 */
app.post('/', async (c) => {
  try {
    const unauth = requireIdentity(c)
    if (unauth) return unauth
    const id = c.get('identity')

    const form = await c.req.formData().catch(() => null)
    if (!form) return c.json({ error: 'invalid_form' }, 400)

    const file = form.get('file')
    if (!file || typeof file === 'string') {
      return c.json({ error: 'no_file', message: '缺少 file 字段' }, 400)
    }

    const filename = file.name || 'package.zip'
    const size = file.size || 0
    const mime = file.type || 'application/octet-stream'
    const ext = (filename.split('.').pop() || '').toLowerCase()

    if (!ACCEPT_EXT.has(ext)) {
      return c.json({ error: 'invalid_file_type', message: '仅支持 md / markdown / txt / zip 文件' }, 422)
    }
    if (size > MAX_UPLOAD_SIZE) {
      return c.json({ error: 'file_too_large', message: '文件过大，上限 50 MB' }, 413)
    }

    // 磁盘空间检查：低于 100MB 拒绝上传
    try {
      const fsStats = statfsSync(UPLOADS_ROOT)
      const freeBytes = fsStats.bavail * fsStats.bsize
      if (freeBytes < 100 * 1024 * 1024) {
        return c.json({ error: 'insufficient_disk_space', message: '服务器磁盘空间不足，请联系管理员' }, 507)
      }
    } catch (fsErr) {
      console.error('[disk-check-failed]', fsErr)
      // statfsSync 不可用时降级跳过，不阻塞上传
    }

    // 文件存储路径：uploads/2026-05-21/fileXxx_original.zip
    const today = new Date().toISOString().slice(0, 10)
    const fid = genId('file')
    const safeName = filename.replace(/[^\w.\-]+/g, '_')
    const r2_key = `pkg/${today}/${fid}_${safeName}`  // 兼容原有 schema 字段名

    const dirPath = path.join(UPLOADS_ROOT, 'pkg', today)
    mkdirSync(dirPath, { recursive: true })
    const localPath = path.join(UPLOADS_ROOT, 'pkg', today, `${fid}_${safeName}`)

    const arrayBuffer = await file.arrayBuffer()
    writeFileSync(localPath, Buffer.from(arrayBuffer))

    await DB.prepare(
      `INSERT INTO files (id, r2_key, filename, size, mime, uploader_phone, uploader_email)
       VALUES (?, ?, ?, ?, ?, ?, ?)`
    ).bind(fid, r2_key, filename, size, mime, id.phone || null, id.email || null).run()

    c.set('audit', {
      action: 'file.upload',
      target_type: 'file',
      target_id: fid,
      target_name: filename,
      meta: { r2_key, size, mime }
    })

    return c.json({
      data: { file_id: fid, r2_key, filename, size, mime }
    }, 201)
  } catch (err) {
    console.error('[upload-error]', err)
    return c.json({ error: '上传失败，服务器内部错误' }, 500)
  }
})

/**
 * GET /api/uploads/download?key=pkg/2026-05-21/fileXxx_name.zip
 * 下载原始包（无需身份认证，凭 key 直接下载）
 */
app.get('/download', async (c) => {
  try {
    const key = c.req.query('key') || ''
    // 防路径穿越：基本过滤
    if (!key || key.startsWith('/')) {
      return c.json({ error: 'invalid_key' }, 400)
    }
    // 加强路径穿越防护：normalize 后再次校验，拒绝 ..、绝对路径、嵌套 ../
    const normalized = path.normalize(key).replace(/\\/g, '/')
    if (normalized.startsWith('..') || normalized.startsWith('/') || normalized.includes('/../')) {
      return c.json({ error: 'invalid_key' }, 400)
    }
    // 路径穿越防护：resolve 后确保仍在 UPLOADS_ROOT 内
    const filePath = path.resolve(UPLOADS_ROOT, path.normalize(key))
    const uploadsResolved = path.resolve(UPLOADS_ROOT)
    if (!filePath.startsWith(uploadsResolved + path.sep) && filePath !== uploadsResolved) {
      return c.json({ error: 'forbidden', message: '路径越权访问' }, 403)
    }
    if (!existsSync(filePath)) return c.json({ error: 'not_found' }, 404)

    const stat = statSync(filePath)
    const filename = path.basename(filePath)
    const nodeStream = createReadStream(filePath)
    const webStream = Readable.toWeb(nodeStream)

    return new Response(webStream, {
      status: 200,
      headers: {
        'Content-Type': 'application/octet-stream',
        'Content-Disposition': `attachment; filename*=UTF-8''${encodeURIComponent(filename)}`,
        'Content-Length': String(stat.size),
        'Cache-Control': 'no-store',
      }
    })
  } catch (err) {
    console.error('[download-error]', err)
    return c.json({ error: '下载失败，服务器内部错误' }, 500)
  }
})

/**
 * GET /api/uploads/orphans  孤儿文件列表（运营查）
 */
app.get('/orphans', async (c) => {
  try {
    // 身份校验：未登录拒访
    const identity = c.get('identity') || {}
    if (!identity.phone && !identity.email) {
      return c.json({ error: 'unauthenticated' }, 401)
    }
    // 仅超级管理员可查看（包含上传者手机号/邮箱等个人信息）
    if (identity.email !== '494161546@qq.com' && identity.phone !== '13136092523') {
      return c.json({ error: 'forbidden' }, 403)
    }

    const { results } = await DB.prepare(
      `SELECT id, r2_key, filename, size, uploader_phone, uploader_email, created_at
       FROM files WHERE bound_skill_id IS NULL
       ORDER BY created_at DESC LIMIT 500`
    ).all()
    return c.json({ data: results || [] })
  } catch (err) {
    console.error('[orphans-error]', err)
    return c.json({ error: '查询孤儿文件失败' }, 500)
  }
})

export default app
