import os
import re
import json
import logging
from datetime import datetime
from fastapi import APIRouter, Query, Request, UploadFile, File, HTTPException
from typing import Optional
from ...core.permission import get_current_user, is_admin_role
from ...core.config import settings
from ...core.database import get_db, get_system_db, _has_split_databases
from ...core.crypto import read_text_file, is_encrypted


async def _get_kb_db():
    """获取知识库数据库连接（优先 system.db，单库回退 get_db）"""
    if _has_split_databases():
        async for db in get_system_db():
            yield db
    else:
        async for db in get_db():
            yield db

logger = logging.getLogger(__name__)

router = APIRouter()

ZHISHIKU_BASE = getattr(settings, 'ZHISHIKU_PATH', './zhishiku')
VIEW_COUNTS_FILE = os.path.join(ZHISHIKU_BASE, '.view_counts.json')

# 后端内存缓存：避免每次请求都重新扫描磁盘
_docs_cache: dict = {}   # { category_key: [doc, ...] }
_cache_time: float = 0   # 缓存时间戳

CATEGORY_MAP = {
    'law': '法律法规',
    'case': '处罚案例',
    'standard': '处罚裁量',
}

CATEGORY_DIR_MAP = {
    '法律法规': 'law',
    '处罚案例': 'case',
    '处罚裁量': 'standard',
}


def _parse_frontmatter(text: str) -> dict:
    fm_regex = r'^---\s*\n(.*?)\n---\s*\n'
    match = re.match(fm_regex, text, re.DOTALL)
    if not match or not match.group(1):
        return {}
    fm = {}
    for line in match.group(1).split('\n'):
        ci = line.find(':')
        if ci > 0:
            key = line[:ci].strip()
            val = line[ci+1:].strip()
            if (val.startswith('"') and val.endswith('"')) or (val.startswith("'") and val.endswith("'")):
                val = val[1:-1]
            fm[key] = val
    return fm


def _strip_frontmatter(text: str) -> str:
    """去除 YAML frontmatter，返回纯正文内容"""
    fm_regex = r'^---\s*\n.*?\n---\s*\n'
    stripped = re.sub(fm_regex, '', text, count=1, flags=re.DOTALL)
    return stripped.strip()


def _load_view_counts() -> dict:
    try:
        if os.path.exists(VIEW_COUNTS_FILE):
            with open(VIEW_COUNTS_FILE, 'r', encoding='utf-8') as f:
                return json.load(f)
    except Exception:
        pass
    return {}


def _save_view_counts(counts: dict):
    try:
        with open(VIEW_COUNTS_FILE, 'w', encoding='utf-8') as f:
            json.dump(counts, f, ensure_ascii=False, indent=2)
    except Exception:
        pass


def _increment_view_count(rel_path: str) -> int:
    counts = _load_view_counts()
    current = counts.get(rel_path, 0) + 1
    counts[rel_path] = current
    _save_view_counts(counts)
    return current


def _scan_category_dir_fast(base_path: str, category_key: str) -> list:
    """零 I/O 快速扫描：仅遍历目录 + stat 元数据，不打开任何文件。
    region 从文件名或子目录名推导（law/从文件名，case/从子目录名）。
    ~14000 个文件 < 1 秒完成。"""
    results = []
    cat_dir = os.path.join(base_path, category_key)
    if not os.path.isdir(cat_dir):
        return results
    view_counts = _load_view_counts()
    for root, dirs, files in os.walk(cat_dir):
        for f in sorted(files):
            if not (f.endswith('.md') or f.endswith('.md.enc')):
                continue
            full_path = os.path.join(root, f)
            rel_path = os.path.relpath(full_path, base_path)
            if rel_path.endswith('.md.enc'):
                rel_path = rel_path[:-4]  # 去掉 .enc 后缀，保持与数据库记录一致
            try:
                stat = os.stat(full_path)
            except Exception:
                continue
            rel_dir = os.path.relpath(root, cat_dir)
            title = f.replace('.md.enc', '').replace('.md', '')

            # === region 推导策略（不打开文件）===
            fm_region = ''
            if category_key == 'law':
                # law/ 子目录是主题分类（法律法规/文旅法规等），不是省份分类
                # law/ 下所有文件本质上都是全国性法规 → 默认 region='全国'
                # 这样区域过滤时，所有法律法规都会被保留（符合"保留全国性法规"规则）
                fm_region = '全国'
            elif category_key == 'case':
                # case/ 子目录就是省份（如 "西藏自治区md"、"新疆兵团执法文书md"）
                if rel_dir and rel_dir != '.':
                    fm_region = rel_dir[:-2] if rel_dir.endswith('md') else rel_dir
            else:
                # standard/ 等其他分类
                fm_region = '全国'

            results.append({
                'id': rel_path.replace('/', '__').replace('\\', '__'),
                'title': title,
                'filename': f,
                'category': CATEGORY_MAP.get(category_key, category_key),
                'region': fm_region,
                'status': '有效',
                'viewCount': view_counts.get(rel_path, 0),
                'filePath': f'/zhishiku/{rel_path}',
                'relPath': rel_path,
                'size': stat.st_size,
                'updatedAt': datetime.fromtimestamp(stat.st_mtime).strftime('%Y-%m-%d %H:%M:%S'),
                'frontmatter': {},
                'subDir': rel_dir if rel_dir != '.' else '',
                '_content_preview': '',
            })
    return results


def _scan_category_dir(base_path: str, category_key: str, use_cache: bool = True) -> list:
    """完整扫描：读取每个文件前 4096 字节解析 frontmatter。
    用于"加载文档"按钮、点击具体文件时获取完整元数据。"""
    global _docs_cache, _cache_time

    # 优先使用缓存
    if use_cache and category_key in _docs_cache:
        return _docs_cache[category_key]

    results = []
    cat_dir = os.path.join(base_path, category_key)
    if not os.path.isdir(cat_dir):
        return results
    view_counts = _load_view_counts()
    for root, dirs, files in os.walk(cat_dir):
        for f in sorted(files):
            if not (f.endswith('.md') or f.endswith('.md.enc')):
                continue
            full_path = os.path.join(root, f)
            rel_path = os.path.relpath(full_path, base_path)
            if rel_path.endswith('.md.enc'):
                rel_path = rel_path[:-4]  # 去掉 .enc 后缀，保持与数据库记录一致
            stat = os.stat(full_path)
            try:
                content = read_text_file(full_path)[:4096]
                fm = _parse_frontmatter(content)
            except Exception:
                fm = {}
            rel_dir = os.path.relpath(root, cat_dir)
            # region 优先从 frontmatter 获取，其次从子目录名推导（去掉 md 后缀）
            fm_region = fm.get('region', '').strip()
            if fm_region:
                region = fm_region
            elif rel_dir and rel_dir != '.':
                # 子目录名去掉 "md" 后缀作为 region：如 "西藏自治区md" → "西藏自治区"
                region = rel_dir[:-2] if rel_dir.endswith('md') else rel_dir
            else:
                region = ''
            results.append({
                'id': rel_path.replace('/', '__').replace('\\', '__'),
                'title': fm.get('title', f.replace('.md.enc', '').replace('.md', '')),
                'filename': f,
                'category': CATEGORY_MAP.get(category_key, category_key),
                'region': region,
                'status': fm.get('status', '有效'),
                'viewCount': view_counts.get(rel_path, 0),
                'filePath': f'/zhishiku/{rel_path}',
                'relPath': rel_path,
                'size': stat.st_size,
                'updatedAt': datetime.fromtimestamp(stat.st_mtime).strftime('%Y-%m-%d %H:%M:%S'),
                'frontmatter': fm,
                'subDir': rel_dir if rel_dir != '.' else '',
                '_content_preview': content,  # 内部使用，用于区域关键词匹配
            })

    # 写入缓存
    _docs_cache[category_key] = results
    _cache_time = datetime.now().timestamp()
    return results


def _clear_docs_cache():
    """清除后端文档缓存（"加载文档"按钮或上传文件时调用）"""
    global _docs_cache, _cache_time, _stats_cache, _case_region_cache
    _docs_cache = {}
    _cache_time = 0
    _stats_cache.clear()
    _case_region_cache = None
    _total_count_cache.clear()
    # 同时清除 md_search 模块的文档缓存
    try:
        from ..user.md_search import invalidate_docs_cache
        invalidate_docs_cache()
    except ImportError:
        pass


def _match_region(doc: dict, keywords: list) -> bool:
    """判断文档是否匹配区域关键词（匹配文件名/region字段/内容预览）"""
    # 1) 文件名匹配
    filename = doc.get('filename', '')
    for kw in keywords:
        if kw in filename:
            return True
    # 2) region 字段匹配
    doc_region = doc.get('region', '')
    for kw in keywords:
        if kw in doc_region:
            return True
    # 3) 文件内容匹配（前 4096 字符，含 frontmatter + 正文开头）
    content_preview = doc.get('_content_preview', '')
    for kw in keywords:
        if kw in content_preview:
            return True
    return False


# 后端内置区域关键词映射（前端未传 region_keywords 时的 fallback）
_BUILTIN_REGION_KEYWORDS = {
    '全国': [],
    '北京市': ['北京', '北京市'], '天津市': ['天津', '天津市'],
    '上海市': ['上海', '上海市'], '重庆市': ['重庆', '重庆市'],
    '河北省': ['河北', '河北省'], '山西省': ['山西', '山西省'],
    '辽宁省': ['辽宁', '辽宁省'], '吉林省': ['吉林', '吉林省'],
    '黑龙江省': ['黑龙江', '黑龙江省', '龙江'],
    '江苏省': ['江苏', '江苏省'], '浙江省': ['浙江', '浙江省'],
    '安徽省': ['安徽', '安徽省'], '福建省': ['福建', '福建省'],
    '江西省': ['江西', '江西省'], '山东省': ['山东', '山东省'],
    '河南省': ['河南', '河南省'], '湖北省': ['湖北', '湖北省'],
    '湖南省': ['湖南', '湖南省'], '广东省': ['广东', '广东省'],
    '海南省': ['海南', '海南省'], '四川省': ['四川', '四川省'],
    '贵州省': ['贵州', '贵州省'], '云南省': ['云南', '云南省'],
    '陕西省': ['陕西', '陕西省'], '甘肃省': ['甘肃', '甘肃省'],
    '青海省': ['青海', '青海省'], '台湾省': ['台湾', '台湾省'],
    '内蒙古自治区': ['内蒙古', '内蒙', '内蒙古自治区'],
    '广西壮族自治区': ['广西', '广西壮族自治区'],
    '西藏自治区': ['西藏', '西藏自治区'],
    '宁夏回族自治区': ['宁夏', '宁夏回族自治区'],
    '新疆维吾尔自治区': ['新疆', '新疆维吾尔自治区', '新疆生产建设兵团', '兵团'],
    '香港特别行政区': ['香港', '香港特别行政区'],
    '澳门特别行政区': ['澳门', '澳门特别行政区'],
}


def _get_builtin_region_keywords(region: str) -> list:
    """获取区域匹配关键词（后端内置映射，与前端 regionKeywords.ts 保持一致）"""
    return _BUILTIN_REGION_KEYWORDS.get(region, [region])


# 文档总数缓存（避免每次翻页都 COUNT(*)）
_total_count_cache: dict = {}  # {cache_key: (count, timestamp)}

# stats 接口内存缓存：数据极少变化（仅在"加载文档"按钮触发扫描后更新）
# 缓存 key = region，value = (stats_data, timestamp)
_stats_cache: dict = {}
_STATS_CACHE_TTL = 120  # 秒

# 处罚案例子区域分组缓存（扫描 52k 行，独立缓存，TTL 更长）
_case_region_cache: tuple = None  # (regions_list, timestamp)
_CASE_REGION_CACHE_TTL = 300  # 秒

async def _query_kb_docs(
    category: str = '',
    keyword: str = '',
    status: str = '',
    region: str = '',
    region_keywords: list = None,
    sub_region: str = '',
    page: int = 1,
    page_size: int = 50,
    lightweight: bool = False,
) -> dict:
    """从 kb_documents 表查询文档列表

    lightweight=True 时仅查询列表展示所需的最小字段（跳过 tags/anyou JSON 解析），
    用于首屏加载和翻页；lightweight=False 查询完整字段（用于详情/搜索）。
    """
    conditions = []
    params = []

    if category:
        conditions.append("category = %s")
        params.append(category)
    if keyword:
        kw = f"%{keyword}%"
        conditions.append("(title LIKE %s OR file_path LIKE %s)")
        params.extend([kw, kw])
    if status:
        conditions.append("status = %s")
        params.append(status)

    # 子区域过滤：处罚案例按省份精确过滤（前端选择子分类时使用）
    if sub_region:
        conditions.append("region = %s")
        params.append(sub_region)

    # 区域过滤：仅对法律法规和处罚裁量生效
    if region and region != '全国':
        if not region_keywords:
            region_keywords = _get_builtin_region_keywords(region)
        # 构建区域匹配条件：处罚案例不过滤，法律法规/处罚裁量按关键词匹配
        region_conditions = []
        for kw in region_keywords:
            region_conditions.append("region LIKE %s")
            params.append(f"%{kw}%")
        region_like = " OR ".join(region_conditions)
        conditions.append(
            f"(category = '处罚案例' OR region = '全国' OR title LIKE '%%中华人民共和国%%' OR {region_like})"
        )

    where_clause = " AND ".join(conditions) if conditions else "1=1"

    async for conn, cur in _get_kb_db():
        # 查询总数（无筛选条件时使用缓存，避免每次翻页都 COUNT(*)）
        import time as _ktime
        cache_key = f"{category}|{keyword}|{status}|{region}|{sub_region}"
        now = _ktime.time()
        total = None
        if where_clause == "1=1" and not keyword and not status and not region and not sub_region:
            cached = _total_count_cache.get(cache_key)
            if cached and (now - cached[1]) < 120:  # 120 秒缓存
                total = cached[0]
        if total is None:
            count_sql = f"SELECT COUNT(*) as total FROM kb_documents WHERE {where_clause}"
            await cur.execute(count_sql, params)
            row = await cur.fetchone()
            total = row['total'] if row else 0
            _total_count_cache[cache_key] = (total, now)

        # 分页查询
        offset = (page - 1) * page_size
        if lightweight:
            # 轻量模式：仅查询列表展示所需字段，跳过 tags/anyou JSON 解析
            data_sql = f"""
                SELECT id, title, region, category, status, file_path, file_size,
                       sub_dir, view_count, updated_at
                FROM kb_documents WHERE {where_clause}
                ORDER BY updated_at DESC
                LIMIT %s OFFSET %s
            """
        else:
            data_sql = f"""
                SELECT id, title, version, version_name, region, region_code, category,
                       effective_date, expiry_date, status, source, source_url, tags,
                       file_path, file_size, sub_dir, anyou, issuer, view_count,
                       qdrant_collection, chunk_strategy, chunk_count, import_status,
                       created_at, updated_at
                FROM kb_documents WHERE {where_clause}
                ORDER BY updated_at DESC
                LIMIT %s OFFSET %s
            """
        await cur.execute(data_sql, params + [page_size, offset])
        rows = await cur.fetchall()

        docs = []
        for raw_row in rows:
            # sqlite3.Row 不支持 .get()，需转为 dict
            row = dict(raw_row)

            if lightweight:
                # 轻量模式：构建最小响应体，跳过 JSON 解析
                doc = {
                    'id': str(row['id']),
                    'title': row.get('title') or '',
                    'filename': os.path.basename(row.get('file_path') or ''),
                    'category': row.get('category') or '',
                    'region': row.get('region') or '',
                    'status': row.get('status') or '有效',
                    'viewCount': row.get('view_count') or 0,
                    'filePath': f"/zhishiku/{row['file_path']}" if row.get('file_path') else '',
                    'relPath': row.get('file_path') or '',
                    'size': row.get('file_size') or 0,
                    'updatedAt': row.get('updated_at') or '',
                    'frontmatter': {
                        'title': row.get('title') or '',
                        'region': row.get('region') or '',
                        'category': row.get('category') or '',
                        'status': row.get('status') or '有效',
                    },
                    'subDir': row.get('sub_dir') or '',
                    'tags': [],
                    'anyou': [],
                }
            else:
                # 完整模式：解析 JSON 字段
                tags = row.get('tags', '[]') or '[]'
                if isinstance(tags, str):
                    try:
                        tags = json.loads(tags)
                    except Exception:
                        tags = []
                anyou = row.get('anyou', '[]') or '[]'
                if isinstance(anyou, str):
                    try:
                        anyou = json.loads(anyou)
                    except Exception:
                        anyou = []

                doc = {
                    'id': str(row['id']),
                    'title': row.get('title') or '',
                    'filename': os.path.basename(row.get('file_path') or ''),
                    'category': row.get('category') or '',
                    'region': row.get('region') or '',
                    'status': row.get('status') or '有效',
                    'viewCount': row.get('view_count') or 0,
                    'filePath': f"/zhishiku/{row['file_path']}" if row.get('file_path') else '',
                    'relPath': row.get('file_path') or '',
                    'size': row.get('file_size') or 0,
                    'updatedAt': row.get('updated_at') or '',
                    'frontmatter': {
                        'title': row.get('title') or '',
                        'version': row.get('version') or '',
                        'version_name': row.get('version_name') or '',
                        'region': row.get('region') or '',
                        'region_code': row.get('region_code') or '',
                        'category': row.get('category') or '',
                        'effective_date': row.get('effective_date') or '',
                        'expiry_date': row.get('expiry_date') or '',
                        'status': row.get('status') or '有效',
                        'source': row.get('source') or '',
                        'source_url': row.get('source_url') or '',
                        'tags': tags,
                        'anyou': anyou,
                        'issuer': row.get('issuer') or '',
                    },
                    'subDir': row.get('sub_dir') or '',
                    'tags': tags,
                    'anyou': anyou,
                }
            docs.append(doc)

        return {"list": docs, "total": total}


@router.get("/knowledge-lib/docs")
async def list_knowledge_docs(
    page: int = Query(1, ge=1),
    pageSize: int = Query(50, ge=1, le=100000),
    category: str = Query(''),
    keyword: str = Query(''),
    status: str = Query(''),
    region: str = Query(''),
    region_keywords: str = Query(''),
    sub_region: str = Query('', description="子区域过滤（处罚案例按省份过滤）"),
    fast: bool = Query(True, description="快速模式（数据库模式下忽略此参数）"),
    lightweight: bool = Query(False, description="轻量模式：仅返回列表展示所需字段，跳过 tags/anyou JSON 解析"),
):
    # 解析 region_keywords
    rk_list = []
    if region_keywords:
        try:
            rk_list = json.loads(region_keywords)
        except Exception:
            rk_list = []

    # 从数据库查询（启动时已去重 + 补录，保证数据与文件系统一致）
    result = {"list": [], "total": 0}
    try:
        result = await _query_kb_docs(
            category=category, keyword=keyword, status=status,
            region=region, region_keywords=rk_list or None,
            sub_region=sub_region,
            page=page, page_size=pageSize,
            lightweight=lightweight,
        )
    except Exception as e:
        logger.warning(f"数据库查询失败，回退到文件系统: {e}")

    # 数据库不可用时回退到文件系统（如加密密钥不匹配）
    if not result.get("list") and os.path.isdir(ZHISHIKU_BASE):
        cat_key = CATEGORY_DIR_MAP.get(category, '')
        if cat_key:
            # 扫描对应分类目录
            all_docs = _scan_category_dir(ZHISHIKU_BASE, cat_key, use_cache=True)
            # 内存过滤
            if keyword:
                kw = keyword.lower()
                all_docs = [d for d in all_docs if kw in (d.get('title', '') or '').lower() or kw in d.get('filename', '').lower()]
            if status:
                all_docs = [d for d in all_docs if d.get('status') == status]
            if region and region != '全国':
                rk = rk_list or _get_builtin_region_keywords(region)
                # 区域过滤：处罚案例不过滤，法律法规/处罚裁量按关键词匹配
                if category != '处罚案例':
                    all_docs = [d for d in all_docs
                                if d.get('region') == '全国'
                                or '中华人民共和国' in (d.get('title', '') or '')
                                or any(kw in (d.get('title', '') or '') or kw in (d.get('region', '') or '') or kw in d.get('filename', '')
                                       for kw in rk)]
            if sub_region:
                all_docs = [d for d in all_docs if d.get('region', '') == sub_region]
            # 排序：按更新时间倒序
            all_docs.sort(key=lambda d: d.get('updatedAt', ''), reverse=True)
            total = len(all_docs)
            # 分页
            offset = (page - 1) * pageSize
            paged = all_docs[offset:offset + pageSize]
            result = {"list": paged, "total": total}

    return {"success": True, "data": result}


@router.get("/knowledge-lib/doc-content")
async def get_doc_content(
    path: str = Query(..., description="相对路径，如 law/xxx.md"),
):
    # 优先从数据库读取
    try:
        async for conn, cur in _get_kb_db():
            await cur.execute(
                "SELECT content, title, region, version, version_name, status, source, source_url, tags, anyou, effective_date, expiry_date, region_code FROM kb_documents WHERE file_path = %s",
                (path,)
            )
            row = await cur.fetchone()
            if row:
                row = dict(row)  # sqlite3.Row 不支持 .get()
            if row and row.get('content'):
                tags = row.get('tags', '[]') or '[]'
                if isinstance(tags, str):
                    try: tags = json.loads(tags)
                    except Exception: tags = []
                anyou = row.get('anyou', '[]') or '[]'
                if isinstance(anyou, str):
                    try: anyou = json.loads(anyou)
                    except Exception: anyou = []
                return {
                    "success": True,
                    "data": {
                        "content": row['content'],
                        "frontmatter": {
                            'title': row.get('title') or '',
                            'version': row.get('version') or '',
                            'version_name': row.get('version_name') or '',
                            'region': row.get('region') or '',
                            'region_code': row.get('region_code') or '',
                            'status': row.get('status') or '有效',
                            'source': row.get('source') or '',
                            'source_url': row.get('source_url') or '',
                            'tags': tags,
                            'anyou': anyou,
                            'effective_date': row.get('effective_date') or '',
                            'expiry_date': row.get('expiry_date') or '',
                        },
                        "title": row.get('title') or '',
                    }
                }
    except Exception:
        pass

    # Fallback：文件系统读取
    full_path = os.path.join(ZHISHIKU_BASE, path)
    # 优先尝试 .md.enc 文件，再尝试 .md 文件
    if not os.path.isfile(full_path):
        enc_path = full_path + '.enc'
        if os.path.isfile(enc_path):
            full_path = enc_path
    if not os.path.isfile(full_path):
        raise HTTPException(status_code=404, detail=f"文件不存在: {path}")
    try:
        text = read_text_file(full_path)
        fm = _parse_frontmatter(text)
        content = re.sub(r'^---\s*\n.*?\n---\s*\n', '', text, flags=re.DOTALL).strip()
        title = fm.get('title', os.path.basename(path).replace('.md.enc', '').replace('.md', ''))
        return {
            "success": True,
            "data": {
                "content": content,
                "frontmatter": fm,
                "title": title,
            }
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"读取文件失败: {str(e)}")


@router.post("/knowledge-lib/upload")
async def upload_doc(
    request: Request,
    category: str = Query(..., description="法律法规/处罚裁量/处罚案例"),
    file: UploadFile = File(...),
):
    user = get_current_user(request)
    if not user or not is_admin_role(user.get('role', '')):
        raise HTTPException(status_code=403, detail="需要管理员权限")

    cat_key = CATEGORY_DIR_MAP.get(category, '')
    if not cat_key:
        raise HTTPException(status_code=400, detail=f"无效分类: {category}")

    target_dir = os.path.join(ZHISHIKU_BASE, cat_key)
    os.makedirs(target_dir, exist_ok=True)

    dest_path = os.path.join(target_dir, file.filename)
    try:
        content = await file.read()
        with open(dest_path, 'wb') as f:
            f.write(content)
        # 上传新文件后清除缓存
        _clear_docs_cache()
        fm = _parse_frontmatter(content.decode('utf-8', errors='ignore'))

        # 同步写入数据库
        try:
            content_text = content.decode('utf-8', errors='replace')
            fm_db = _parse_frontmatter(content_text)
            body_content = _strip_frontmatter(content_text)
            async for conn, cur in _get_kb_db():
                await cur.execute(
                    """INSERT OR REPLACE INTO kb_documents
                       (title, category, region, status, content, file_path, file_size, sub_dir, anyou, issuer, tags, import_status)
                       VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""",
                    (
                        fm_db.get('title', file.filename.replace('.md', '')),
                        category,
                        fm_db.get('region', '全国' if cat_key == 'law' else ''),
                        fm_db.get('status', '有效'),
                        body_content,
                        f"{cat_key}/{file.filename}",
                        len(content),
                        '',
                        json.dumps(fm_db.get('anyou', []), ensure_ascii=False) if isinstance(fm_db.get('anyou'), list) else str(fm_db.get('anyou', '')),
                        fm_db.get('issuer', ''),
                        json.dumps(fm_db.get('tags', []), ensure_ascii=False),
                        'completed',
                    )
                )
                await conn.commit()
        except Exception:
            pass

        return {
            "success": True,
            "data": {
                "filename": file.filename,
                "filePath": f"/zhishiku/{cat_key}/{file.filename}",
                "frontmatter": fm,
            }
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"上传失败: {str(e)}")


@router.put("/knowledge-lib/doc")
async def save_doc_content(
    request: Request,
    path: str = Query(..., description="相对路径，如 law/xxx.md"),
):
    """保存文档内容到源文件"""
    user = get_current_user(request)
    if not user or not is_admin_role(user.get('role', '')):
        raise HTTPException(status_code=403, detail="需要管理员权限")

    full_path = os.path.join(ZHISHIKU_BASE, path)
    # 优先尝试 .md.enc 文件，再尝试 .md 文件
    if not os.path.isfile(full_path):
        enc_path = full_path + '.enc'
        if os.path.isfile(enc_path):
            full_path = enc_path
    if not os.path.isfile(full_path):
        raise HTTPException(status_code=404, detail=f"文件不存在: {path}")

    try:
        body = await request.json()
        title = body.get("title", "")
        content = body.get("content", None)
        frontmatter = body.get("frontmatter", {})

        # 如果提供了 title，更新 frontmatter
        if title:
            frontmatter["title"] = title

        # 读取原文件
        raw_text = read_text_file(full_path)

        # 如果未提供 content，使用原文件内容（仅更新 frontmatter）
        if content is None:
            content = raw_text

        old_fm = _parse_frontmatter(raw_text)

        # 合并 frontmatter：以新传的为准，缺失字段保留原值
        merged_fm = {**old_fm, **frontmatter}
        if title:
            merged_fm["title"] = title

        # 构建 frontmatter YAML 文本
        fm_lines = ["---"]
        for k, v in merged_fm.items():
            if v is not None and v != '':
                fm_lines.append(f"{k}: {v}")
        fm_lines.append("---")

        # 重新组合文件内容
        body_only = _strip_frontmatter(content)
        new_text = "\n".join(fm_lines) + "\n\n" + body_only

        with open(full_path, 'w', encoding='utf-8') as f:
            f.write(new_text)

        # 同步更新数据库
        try:
            async for conn, cur in _get_kb_db():
                await cur.execute(
                    "UPDATE kb_documents SET title = %s, content = %s, region = %s, status = %s, source = %s, tags = %s, anyou = %s, effective_date = %s, updated_at = datetime('now') WHERE file_path = %s",
                    (title, body_only, merged_fm.get('region', ''), merged_fm.get('status', '有效'),
                     merged_fm.get('issuer', '') or merged_fm.get('source', ''),
                     json.dumps(merged_fm.get('tags', []), ensure_ascii=False),
                     json.dumps(merged_fm.get('anyou', []), ensure_ascii=False) if isinstance(merged_fm.get('anyou'), list) else merged_fm.get('anyou', ''),
                     merged_fm.get('effective_date', ''), path)
                )
                await conn.commit()
        except Exception:
            pass  # 数据库更新失败不影响文件保存

        return {"success": True, "data": {"message": "保存成功", "path": path}}
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"保存失败: {str(e)}")


@router.delete("/knowledge-lib/doc")
async def delete_doc(
    request: Request,
    path: str = Query(..., description="相对路径"),
):
    user = get_current_user(request)
    if not user or not is_admin_role(user.get('role', '')):
        raise HTTPException(status_code=403, detail="需要管理员权限")

    full_path = os.path.join(ZHISHIKU_BASE, path)
    # 优先尝试 .md.enc 文件，再尝试 .md 文件
    if not os.path.isfile(full_path):
        enc_path = full_path + '.enc'
        if os.path.isfile(enc_path):
            full_path = enc_path
    if not os.path.isfile(full_path):
        raise HTTPException(status_code=404, detail="文件不存在")

    try:
        os.remove(full_path)

        # 同步删除数据库记录
        try:
            async for conn, cur in _get_kb_db():
                await cur.execute(
                    "DELETE FROM kb_documents WHERE file_path = %s",
                    (path,)
                )
                await conn.commit()
        except Exception:
            pass

        return {"success": True, "data": {"message": "删除成功"}}
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"删除失败: {str(e)}")


@router.post("/knowledge-lib/view")
async def increment_view_count(
    path: str = Query(..., description="文档相对路径，如 law/xxx.md"),
):
    full_path = os.path.join(ZHISHIKU_BASE, path)
    if not os.path.isfile(full_path):
        raise HTTPException(status_code=404, detail=f"文件不存在: {path}")
    count = _increment_view_count(path)

    # 同步更新数据库浏览计数
    try:
        async for conn, cur in _get_kb_db():
            await cur.execute(
                "UPDATE kb_documents SET view_count = view_count + 1 WHERE file_path = %s",
                (path,)
            )
            await conn.commit()
    except Exception:
        pass

    return {"success": True, "data": {"viewCount": count}}


async def _get_case_regions_cached() -> list:
    """获取处罚案例子区域分组（带缓存，避免每次扫描 52k 行）"""
    global _case_region_cache
    import time as _ktime
    now = _ktime.time()
    if _case_region_cache and (now - _case_region_cache[1]) < _CASE_REGION_CACHE_TTL:
        return _case_region_cache[0]

    case_regions = []
    try:
        async for conn, cur in _get_kb_db():
            await cur.execute(
                "SELECT region, COUNT(*) as cnt FROM kb_documents WHERE category = '处罚案例' GROUP BY region ORDER BY cnt DESC"
            )
            for row in await cur.fetchall():
                r = dict(row)
                case_regions.append({"name": r['region'] or '其他', "count": r['cnt']})
    except Exception as e:
        logger.warning(f"获取案例区域分组失败: {e}")

    _case_region_cache = (case_regions, now)
    return case_regions


@router.get("/knowledge-lib/stats")
async def get_knowledge_stats(
    region: str = Query(''),
    region_keywords: str = Query(''),
):
    """轻量统计接口：返回各分类和子分类的文档数量（优先从数据库读取，带内存缓存）"""
    import time as _ktime

    # 缓存命中检查：stats 数据极少变化（仅在"加载文档"按钮触发扫描后更新）
    cache_key = region or '全国'
    now = _ktime.time()
    cached = _stats_cache.get(cache_key)
    if cached and (now - cached[1]) < _STATS_CACHE_TTL:
        return {"success": True, "data": cached[0]}

    stats = {}

    # 解析区域关键词
    rk_list = []
    if region_keywords:
        try:
            rk_list = json.loads(region_keywords) if isinstance(region_keywords, str) else region_keywords
        except Exception:
            rk_list = []

    # 构建区域过滤条件（与 _query_kb_docs 一致）
    region_where = ""
    region_params = []
    if region and region != '全国':
        if not rk_list:
            rk_list = _get_builtin_region_keywords(region)
        if rk_list:
            region_conds = []
            for kw in rk_list:
                region_conds.append("region LIKE %s")
                region_params.append(f"%{kw}%")
            region_like = " OR ".join(region_conds)
            # 法律法规/处罚裁量按区域过滤；处罚案例不过滤（按子区域分组展示）
            region_where = f" AND (category = '处罚案例' OR region = '全国' OR title LIKE '%%中华人民共和国%%' OR {region_like})"

    # 优先从数据库统计（数据准确，按 region 字段分组）
    db_stats = {}
    try:
        async for conn, cur in _get_kb_db():
            # 各分类总数（带区域过滤）
            sql = f"SELECT category, COUNT(*) as cnt FROM kb_documents WHERE 1=1{region_where} GROUP BY category"
            await cur.execute(sql, region_params)
            for row in await cur.fetchall():
                r = dict(row)
                db_stats[r['category']] = r['cnt']

        # 处罚案例子区域分组（使用独立缓存，避免每次扫描 52k 行）
        case_regions = await _get_case_regions_cached()

        # 各分类总数
        total_all = sum(db_stats.values())

        for cat_label in ['法律法规', '处罚裁量', '处罚案例']:
            count = db_stats.get(cat_label, 0)
            entry = {"count": count}
            if cat_label == '处罚案例':
                entry["sub_categories"] = case_regions
            else:
                entry["sub_categories"] = []
            stats[cat_label] = entry

        result_data = {
            "categories": stats,
            "total": total_all,
        }
        # 写入缓存
        _stats_cache[cache_key] = (result_data, now)

        return {
            "success": True,
            "data": result_data,
        }
    except Exception as e:
        logger.warning(f"从数据库统计失败，回退到文件系统: {e}")

    # 回退：文件系统扫描
    base = ZHISHIKU_BASE
    for cat_key, cat_label in [('law', '法律法规'), ('standard', '处罚裁量'), ('case', '处罚案例')]:
        cat_dir = os.path.join(base, cat_key)
        if not os.path.isdir(cat_dir):
            stats[cat_label] = {"count": 0, "sub_categories": []}
            continue

        total = 0
        sub_categories = {}
        for root, dirs, files in os.walk(cat_dir):
            md_count = len([f for f in files if f.endswith('.md') or f.endswith('.md.enc')])
            total += md_count
            if cat_key == 'case' and md_count > 0:
                rel_dir = os.path.relpath(root, cat_dir)
                if rel_dir != '.':
                    region_name = rel_dir[:-2] if rel_dir.endswith('md') else rel_dir
                    sub_categories[region_name] = sub_categories.get(region_name, 0) + md_count

        entry = {"count": total}
        if sub_categories:
            entry["sub_categories"] = [{"name": k, "count": v} for k, v in sorted(sub_categories.items(), key=lambda x: -x[1])]
        else:
            entry["sub_categories"] = []
        stats[cat_label] = entry

    total_all = sum(s["count"] for s in stats.values())

    return {
        "success": True,
        "data": {
            "categories": stats,
            "total": total_all,
        }
    }


@router.post("/knowledge-lib/scan")
async def scan_knowledge_docs(
    force: bool = Query(False, description="强制重新扫描（清除缓存）"),
):
    """扫描知识库目录，返回分类统计和文档列表（前端"加载文档"按钮调用）"""
    # 清除后端缓存，强制重新扫描磁盘
    _clear_docs_cache()
    base = ZHISHIKU_BASE
    if not os.path.isdir(base):
        return {"success": False, "error": f"知识库目录不存在: {base}", "data": {"categories": [], "total": 0}}

    cats_to_scan = ['law', 'case', 'standard']
    all_docs = []
    category_stats = {}

    for ck in cats_to_scan:
        docs = _scan_category_dir(base, ck, use_cache=False)
        cat_label = CATEGORY_MAP.get(ck, ck)
        category_stats[cat_label] = {
            "key": ck,
            "label": cat_label,
            "count": len(docs),
        }
        # 统计子分类（仅 case）
        if ck == 'case':
            sub_stats = {}
            for d in docs:
                r = d.get('region', '') or d.get('subDir', '') or '其他'
                sub_stats[r] = sub_stats.get(r, 0) + 1
            category_stats[cat_label]["sub_categories"] = [
                {"name": k, "count": v} for k, v in sorted(sub_stats.items())
            ]
        all_docs.extend(docs)

    total = len(all_docs)

    # 同步扫描结果到数据库
    try:
        async for conn, cur in _get_kb_db():
            synced = 0
            for doc in all_docs:
                rel_path = doc.get('relPath', '')
                if not rel_path:
                    continue
                # 检查是否已存在
                await cur.execute(
                    "SELECT id FROM kb_documents WHERE file_path = %s",
                    (rel_path,)
                )
                existing = await cur.fetchone()
                if existing:
                    # 更新元数据（不更新 content，避免大文本写入）
                    await cur.execute(
                        """UPDATE kb_documents SET title = %s, category = %s, region = %s, status = %s,
                           file_size = %s, sub_dir = %s, view_count = %s, updated_at = datetime('now')
                           WHERE file_path = %s""",
                        (doc.get('title', ''), doc.get('category', ''), doc.get('region', ''),
                         doc.get('status', '有效'), doc.get('size', 0), doc.get('subDir', ''),
                         doc.get('viewCount', 0), rel_path)
                    )
                else:
                    # 新增记录：从文件读取 content
                    content_text = ''
                    try:
                        from ...core.crypto import read_text_file as _read_text
                        full_path = os.path.join(ZHISHIKU_BASE, rel_path)
                        if os.path.exists(full_path):
                            content_text = _read_text(full_path)
                        elif os.path.exists(full_path + '.enc'):
                            content_text = _read_text(full_path + '.enc')
                    except Exception:
                        pass
                    await cur.execute(
                        """INSERT INTO kb_documents
                           (title, category, region, status, content, file_path, file_size, sub_dir, view_count, import_status)
                           VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""",
                        (doc.get('title', ''), doc.get('category', ''), doc.get('region', ''),
                         doc.get('status', '有效'), content_text, rel_path, doc.get('size', 0),
                         doc.get('subDir', ''), doc.get('viewCount', 0), 'done')
                    )
                synced += 1
            await conn.commit()
            logger.info(f"知识库扫描同步到数据库：{synced} 条记录")
    except Exception as e:
        logger.warning(f"知识库扫描同步到数据库失败（非致命）: {e}")

    return {
        "success": True,
        "data": {
            "categories": category_stats,
            "total": total,
            "zhishiku_path": base,
            "scanned_at": datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
        },
    }


@router.get("/knowledge-lib/scan")
async def get_knowledge_scan_status():
    """获取知识库当前扫描状态（轻量版，不返回完整列表）"""
    base = ZHISHIKU_BASE
    cats_to_scan = ['law', 'case', 'standard']
    category_stats = {}
    total = 0

    for ck in cats_to_scan:
        cat_dir = os.path.join(base, ck)
        count = 0
        sub_stats = {}
        if os.path.isdir(cat_dir):
            for root, dirs, files in os.walk(cat_dir):
                rel_dir = os.path.relpath(root, cat_dir)
                md_files = [f for f in files if f.endswith('.md') or f.endswith('.md.enc')]
                count += len(md_files)
                if ck == 'case' and md_files and rel_dir != '.':
                    clean_name = rel_dir[:-2] if rel_dir.endswith('md') else rel_dir
                    sub_stats[clean_name] = len(md_files)
        cat_label = CATEGORY_MAP.get(ck, ck)
        entry = {"key": ck, "label": cat_label, "count": count}
        if sub_stats:
            entry["sub_categories"] = [{"name": k, "count": v} for k, v in sorted(sub_stats.items())]
        category_stats[cat_label] = entry
        total += count

    return {
        "success": True,
        "data": {
            "categories": category_stats,
            "total": total,
            "zhishiku_path": base,
        },
    }

