import { promises as fs } from 'node:fs'
import path from 'node:path'
import { parseFrontMatter } from '../storage/parsers/front-matter.js'
import { parseMarkdownTable } from '../storage/parsers/markdown-table.js'
import { splitAliases } from '../util/aliases.js'
import { normalizeEntityType } from '../util/entity-type.js'

/**
 * 全量重建缓存（BEGIN → DELETE 六表 → 扫描源文件 INSERT → COMMIT，中途失败 ROLLBACK 不留半库）。
 * @param {string} repoPath - 书仓库根目录
 * @param {DatabaseSync} db - node:sqlite 数据库实例
 * @returns {Promise<{ok: boolean, warnings: string[], errors: string[]}>}
 */
export async function rebuildCache(repoPath, db) {
  const warnings = []
  const errors = []

  try {
    // P0-3/P1-1：整次重建包在一个事务里，别名冲突等中途失败 → ROLLBACK，不留半填库
    db.exec('BEGIN')

    // 1. 清空六表（chapters/threads/secrets/entities/entity_aliases/fingerprints）
    db.exec('DELETE FROM chapters')
    db.exec('DELETE FROM threads')
    db.exec('DELETE FROM secrets')
    db.exec('DELETE FROM entities')
    db.exec('DELETE FROM entity_aliases')
    db.exec('DELETE FROM fingerprints')

    // 2. 扫描章节文件 → 填充 chapters 表
    const chapterMap = await scanChapters(repoPath, db, warnings)

    // 3. 扫描条目文件 → 填充 threads 表（验证履历章节）
    await scanThreads(repoPath, db, chapterMap, warnings)

    // 4. 扫描信息差 → 填充 secrets 表
    await scanSecrets(repoPath, db, warnings)

    // 5. 解析名册 → 填充 entities + entity_aliases 表（验证别名唯一性）
    const aliasCheck = await scanEntities(repoPath, db)
    if (aliasCheck.warnings) warnings.push(...aliasCheck.warnings)
    if (!aliasCheck.ok) {
      db.exec('ROLLBACK')
      errors.push(...aliasCheck.errors)
      return { ok: false, warnings, errors }
    }

    // 6. 扫描角色卡 → 填充 entities 表
    await scanCharacters(repoPath, db, warnings)

    // 7. fingerprints 由体检按需重算（M5.5），重建不填

    db.exec('COMMIT')
    return { ok: true, warnings, errors }
  } catch (err) {
    try {
      db.exec('ROLLBACK')
    } catch {
      // 未处于事务中，忽略
    }
    errors.push(`重建失败：${err.message}`)
    return { ok: false, warnings, errors }
  }
}

/**
 * 扫描章节文件，填充 chapters 表。
 * 错误边界（B1/B-S）：解析失败 = 跳过 + warning；读文件/主键冲突 = 硬错冒泡触发 ROLLBACK
 * ——过宽的 try/catch 会让最新章从缓存静默消失，next 读偏小的 MAX(chapter_num) 重抄本章。
 * @returns {Promise<Map<number, string>>} 章号 → 文件路径映射
 */
async function scanChapters(repoPath, db, warnings) {
  const chapterDir = path.join(repoPath, '定稿', '正文')
  const chapterMap = new Map()

  let files
  try {
    files = await fs.readdir(chapterDir)
  } catch (err) {
    if (err.code === 'ENOENT') return chapterMap // 目录不存在（新书），跳过
    throw err
  }

  const insertStmt = db.prepare(`
    INSERT INTO chapters (chapter_num, title, volume_num, perspective, story_time, word_count, chapter_position, hook_type, mood_position, is_volume_end, file_path, is_key_chapter)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  `)

  for (const file of files) {
    if (!file.endsWith('.md')) continue

    const filePath = path.join(chapterDir, file)
    const content = await fs.readFile(filePath, 'utf8')
    const parsed = parseFrontMatter(content)

    if (!parsed.ok) {
      warnings.push(`章文件 定稿/正文/${file} 解析失败，未入缓存：${parsed.error}`)
      continue
    }
    const fm = parsed.data
    const chapterNum = fm.章号 || parseInt(file.match(/\d+/)?.[0] || '0', 10)

    if (chapterMap.has(chapterNum)) {
      throw new Error(
        `第 ${chapterNum} 章有重复档案：${path.basename(chapterMap.get(chapterNum))} 与 ${file} 同章号，请先处理再重建缓存`
      )
    }

    insertStmt.run(
      chapterNum,
      fm.标题 || '未命名',
      fm.卷 || 1,
      fm.视角 || null,
      fm.书内时间 || null,
      fm.字数 || 0,
      fm.章定位 || '推进',
      fm.钩子 || null,
      fm.情绪定位 || null,
      fm.收卷 === '是' || fm.收卷 === true ? 1 : 0,
      filePath,
      fm.是否关键章 ? 1 : 0
    )

    chapterMap.set(chapterNum, filePath)
  }

  return chapterMap
}

/**
 * 扫描条目文件，填充 threads 表。错误边界同 scanChapters（B1/B2）：
 * 解析失败 = warning；重号（含卷复盘绕过 createThread 写出的撞号文件）= 硬错冒泡。
 */
async function scanThreads(repoPath, db, chapterMap, warnings) {
  const types = [
    { dir: '伏笔', type: 'foreshadow' },
    { dir: '悬念', type: 'suspense' },
    { dir: '感情线', type: 'romance' },
  ]

  const insertStmt = db.prepare(`
    INSERT INTO threads (id, type, short_title, strength, status, opened_chapter, planned_end, last_advanced_chapter, file_path)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
  `)

  const seen = new Map()
  for (const { dir, type } of types) {
    const threadDir = path.join(repoPath, '大纲', dir)

    let files
    try {
      files = await fs.readdir(threadDir)
    } catch (err) {
      if (err.code === 'ENOENT') continue // 该类目录不存在，跳过
      throw err
    }

    for (const file of files) {
      if (!file.endsWith('.md')) continue

      const filePath = path.join(threadDir, file)
      const content = await fs.readFile(filePath, 'utf8')
      const parsed = parseFrontMatter(content)

      if (!parsed.ok) {
        warnings.push(`条目文件 大纲/${dir}/${file} 解析失败，未入缓存：${parsed.error}`)
        continue
      }
      const fm = parsed.data
      const stem = file.replace(/\.md$/, '')
      const id = stem.split('-').slice(0, 2).join('-') // 伏笔-001
      const shortTitle = stem.split('-').slice(2).join('-') || id

      if (seen.has(id)) {
        throw new Error(
          `条目 ${id} 有重复档案：${seen.get(id)} 与 ${file} 同编号，请先处理再重建缓存`
        )
      }
      seen.set(id, file)

      // 验证履历章节存在性
      const historySection = extractSection(parsed.body, '履历')
      if (historySection) {
        const historyChapters = parseHistoryChapters(historySection)
        for (const ch of historyChapters) {
          if (!chapterMap.has(ch)) {
            warnings.push(`条目 ${id} 履历引用章节 ${ch} 不存在`)
          }
        }
      }

      insertStmt.run(
        id,
        type,
        shortTitle,
        fm.强度 || '中',
        fm.状态 || '进行',
        fm.开启章 || 1,
        fm.预计收尾 || null,
        fm.最后推进章 || fm.开启章 || 1,
        filePath
      )
    }
  }
}

/**
 * 扫描信息差，填充 secrets 表。
 */
async function scanSecrets(repoPath, db, warnings) {
  const secretDir = path.join(repoPath, '定稿', '设定', '信息差')

  const insertStmt = db.prepare(`
    INSERT INTO secrets (id, short_title, known_to, reader_knows, registered_chapter, keywords, file_path)
    VALUES (?, ?, ?, ?, ?, ?, ?)
  `)

  let files
  try {
    files = await fs.readdir(secretDir)
  } catch (err) {
    if (err.code === 'ENOENT') return // 目录不存在，跳过
    throw err
  }

  for (const file of files) {
    if (!file.endsWith('.md')) continue

    const filePath = path.join(secretDir, file)
    const content = await fs.readFile(filePath, 'utf8')
    const parsed = parseFrontMatter(content)

    if (!parsed.ok) {
      warnings.push(`信息差文件 定稿/设定/信息差/${file} 解析失败，未入缓存：${parsed.error}`)
      continue
    }
    const fm = parsed.data
    const stem = file.replace('.md', '')
    const id = stem.split('-').slice(0, 2).join('-')
    const shortTitle = stem.split('-').slice(2).join('-') || id // 信息差-021-灭门真凶 → 灭门真凶

    insertStmt.run(
      id,
      shortTitle,
      JSON.stringify(fm.知情人 || []),
      fm.读者已知 ? 1 : 0,
      fm.登记章 || 1,
      JSON.stringify(fm.关键词 || []),
      filePath
    )
  }
}

/**
 * 解析名册，填充 entities + entity_aliases 表（验证别名唯一性）。
 * 名册非必需：缺失则跳过（角色卡可独立入 entities）；解析失败/别名冲突才算硬错。
 */
async function scanEntities(repoPath, db) {
  const rosterPath = path.join(repoPath, '定稿', '设定', '名册.md')
  const aliasMap = new Map() // alias → entity_id

  let content
  try {
    content = await fs.readFile(rosterPath, 'utf8')
  } catch {
    // 无名册：跳过（角色卡 scanCharacters 仍会入档），不算失败
    return { ok: true, errors: [] }
  }

  try {
    const table = parseMarkdownTable(content)

    if (!table.ok) {
      // 名册格式解析不动 → 软跳过（不阻断重建；chapters/threads/secrets 不应因此回滚）。
      // 别名冲突才是硬错（见下），走 ok:false 触发 ROLLBACK。
      return { ok: true, errors: [], warnings: [`名册解析失败，已跳过实体/别名入库：${table.error}`] }
    }

    const entityStmt = db.prepare(`
      INSERT INTO entities (id, type, status, location, realm, possessions, last_changed_chapter, file_path)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?)
    `)

    const aliasStmt = db.prepare(`
      INSERT INTO entity_aliases (alias, entity_id)
      VALUES (?, ?)
    `)

    for (const row of table.rows) {
      const entityId = row.正名
      // 名册类型列是作者域中文（G-3），入库统一翻成机器域英文——查询链全按英文过滤
      const type = normalizeEntityType(row.类型)

      entityStmt.run(entityId, type, null, null, null, null, parseInt(row.首现章 || '1', 10), rosterPath)

      // 处理别名（splitAliases 单源：全角逗号/顿号同刀，缺列不炸）
      const aliases = splitAliases(row.别名)
      for (const alias of aliases) {
        if (aliasMap.has(alias)) {
          return {
            ok: false,
            errors: [`别名冲突：「${alias}」同时指向「${aliasMap.get(alias)}」和「${entityId}」`],
          }
        }
        aliasMap.set(alias, entityId)
        aliasStmt.run(alias, entityId)
      }
    }

    return { ok: true, errors: [] }
  } catch (err) {
    return { ok: false, errors: [`名册扫描失败：${err.message}`] }
  }
}

/**
 * 扫描角色卡，填充 entities 表。
 */
async function scanCharacters(repoPath, db, warnings) {
  const charDir = path.join(repoPath, '定稿', '设定', '角色')

  // upsert：角色卡可能不在名册里（名册非强制全覆盖），只 UPDATE 会丢这些角色。
  // 在名册里则补全字段并把 file_path 指向更详细的角色卡。
  // type 也随 upsert 归一成 'character'（G-2：有角色卡即角色，名册行类型写错不至于让角色隐身）。
  const upsertStmt = db.prepare(`
    INSERT INTO entities (id, type, status, location, realm, possessions, last_changed_chapter, file_path)
    VALUES (?, 'character', ?, ?, ?, ?, ?, ?)
    ON CONFLICT(id) DO UPDATE SET
      type = 'character',
      status = excluded.status,
      location = excluded.location,
      realm = excluded.realm,
      possessions = excluded.possessions,
      last_changed_chapter = excluded.last_changed_chapter,
      file_path = excluded.file_path
  `)

  let files
  try {
    files = await fs.readdir(charDir)
  } catch (err) {
    if (err.code === 'ENOENT') return // 目录不存在，跳过
    throw err
  }

  for (const file of files) {
    if (!file.endsWith('.md')) continue

    const name = file.replace('.md', '')
    const filePath = path.join(charDir, file)
    const content = await fs.readFile(filePath, 'utf8')
    const parsed = parseFrontMatter(content)

    if (!parsed.ok) {
      warnings.push(`角色卡 定稿/设定/角色/${file} 解析失败，未入缓存：${parsed.error}`)
      continue
    }
    const fm = parsed.data
    upsertStmt.run(
      name,
      fm.状态 || null,
      fm.位置 || null,
      fm.境界 || null,
      JSON.stringify(fm.持有 || []),
      fm.最后变更章 || 1,
      filePath
    )
  }
}

/**
 * 从正文提取指定段落。
 */
function extractSection(body, sectionTitle) {
  const lines = body.split('\n')
  let inSection = false
  const sectionLines = []

  for (const line of lines) {
    if (line.startsWith('##')) {
      if (inSection) break
      if (line.includes(sectionTitle)) {
        inSection = true
        continue
      }
    }
    if (inSection) sectionLines.push(line)
  }

  return sectionLines.join('\n').trim()
}

/**
 * 解析履历中的章号。
 */
function parseHistoryChapters(historyText) {
  const chapters = []
  const lines = historyText.split('\n')

  for (const line of lines) {
    const match = line.match(/第(\d+)章/)
    if (match) {
      chapters.push(parseInt(match[1], 10))
    }
  }

  return chapters
}
