import json
import logging
import re
import os
import time
import httpx
from typing import Optional
from pathlib import Path

from ..core.config import settings
from ..core.database import get_db, get_system_db, _has_split_databases
from ..core.model_resolver import get_scene_model_config, _decrypt_api_key
from ..core.crypto import read_text_file, is_encrypted

logger = logging.getLogger(__name__)

_kb_doc_index: list[dict] = []
_kb_index_ts: float = 0.0
_KB_INDEX_TTL = 300  # 5分钟缓存


async def get_user_context(user_id: str, db) -> dict:
    """从 users 表读取用户基本信息，返回结构化上下文。"""
    user_info = {
        "name": "",
        "org": "",
        "region": "",
        "region_path": "",
        "role": "",
        "unit_contact_phone": "",
        "unit_contact_address": "",
    }
    try:
        conn, cur = db
        await cur.execute(
            "SELECT real_name, org_name, enforcement_region, enforcement_region_path, role, "
            "unit_contact_phone, unit_contact_address "
            "FROM users WHERE id = %s OR username = %s LIMIT 1",
            (user_id, user_id),
        )
        row = await cur.fetchone()
        if row:
            r = dict(row)
            user_info["name"] = r.get("real_name", "")
            user_info["org"] = r.get("org_name", "")
            user_info["region"] = r.get("enforcement_region", "")
            user_info["region_path"] = r.get("enforcement_region_path", "")
            user_info["role"] = r.get("role", "")
            user_info["unit_contact_phone"] = r.get("unit_contact_phone", "")
            user_info["unit_contact_address"] = r.get("unit_contact_address", "")
    except Exception as e:
        logger.warning(f"Failed to load user context for {user_id}: {e}")
    return user_info


async def get_user_memory_content(user_id: str, db) -> str:
    """从 user_memories 表读取用户记忆内容。优先读数据库，失败读文件。"""
    # 1. 优先从数据库读取
    try:
        conn, cur = db
        await cur.execute(
            "SELECT content FROM user_memories WHERE user_id = %s AND memory_type = 'user' AND is_archived = 0 "
            "ORDER BY updated_at DESC LIMIT 1",
            (user_id,),
        )
        row = await cur.fetchone()
        if row and dict(row).get("content"):
            return row["content"]
    except Exception as e:
        logger.warning(f"Failed to load user memory from DB for {user_id}: {e}")

    # 2. 数据库无数据，尝试从文件读取
    import os
    from pathlib import Path
    base = os.getenv("MEMORIES_DIR", "") or "./data/memories"
    file_path = Path(base) / "users" / f"{user_id}.md"
    if file_path.exists():
        try:
            return file_path.read_text(encoding="utf-8")
        except OSError:
            pass

    return ""


def build_user_context_prompt(user_info: dict, memory_content: str = "") -> str:
    """将用户信息和偏好构建为 system prompt 片段。
    基本信息：实时从 users 表读取
    偏好：从 user_memories 表/文件读取
    """
    sections = []

    # 基本信息（实时读取）
    basic_parts = []
    if user_info.get("name"):
        basic_parts.append(f"用户姓名：{user_info['name']}")
    if user_info.get("org"):
        basic_parts.append(f"所在单位：{user_info['org']}")
    if user_info.get("region_path"):
        basic_parts.append(f"区域：{user_info['region_path']}")
    elif user_info.get("region"):
        basic_parts.append(f"区域：{user_info['region']}")
    if user_info.get("unit_contact_phone"):
        basic_parts.append(f"单位联系电话：{user_info['unit_contact_phone']}")
    if user_info.get("unit_contact_address"):
        basic_parts.append(f"单位联系地址：{user_info['unit_contact_address']}")

    if basic_parts:
        sections.append("=== 当前用户基本信息 ===\n" + "\n".join(basic_parts))

    # 用户偏好（从记忆文件读取）
    if memory_content and memory_content.strip():
        # 过滤掉"暂无"等占位内容
        content = memory_content.strip()
        if "（暂无" not in content or content.count("- ") > 0:
            sections.append("=== 用户偏好 ===\n" + content)

    if not sections:
        return ""
    return "\n\n".join(sections)

def build_retrieval_context(results: list[dict]) -> str:
    """将预检索结果构建为 LLM 上下文。"""
    if not results:
        return "（未检索到相关法规、案例或裁量基准）"

    lines = ["=== 检索到的相关法规/案例/裁量基准 ==="]
    for i, r in enumerate(results[:10], 1):
        title = r.get("title", "未知")
        article = r.get("article_num", "")
        content = r.get("content", "")[:400]
        region = r.get("region", "")
        score = r.get("score", 0)
        lines.append(f"{i}. {title} {article} (区域: {region or '全国'}, 相似度: {score:.2f})\n   {content}")
    return "\n".join(lines)


def format_frontend_references(results: list[dict]) -> list[dict]:
    """将预检索结果格式化为前端 references 数据结构。"""
    refs = []
    for r in results[:10]:
        title = r.get("title", "")
        if not title:
            continue
        refs.append({
            "title": title,
            "source": "kb",
            "link": f"/knowledge/detail/{r.get('id')}?collection={r.get('collection', 'market_regulation_laws')}",
            "score": r.get("score"),
            "excerpt": r.get("content", "")[:200],
        })
    return refs


async def stream_llm_direct(
    prompt: str,
    model_config: dict,
    system_prompt: str = "",
):
    """直连 LLM 流式调用，yield content chunks。"""
    api_key = _decrypt_api_key(model_config.get("api_key", ""))
    api_base = model_config.get("api_base", "")
    model_name = model_config.get("model_name", "")

    if not api_key or not api_base or not model_name:
        yield "模型配置不可用，请检查 AI 管理-模型配置。"
        return

    messages = []
    if system_prompt:
        messages.append({"role": "system", "content": system_prompt})
    messages.append({"role": "user", "content": prompt})

    payload = {
        "model": model_name,
        "messages": messages,
        "stream": True,
        "temperature": 0.3,
        "max_tokens": 4096,
    }

    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    }

    url = api_base.rstrip("/")
    if not url.endswith("/chat/completions"):
        url = url + "/chat/completions"

    async with httpx.AsyncClient(timeout=httpx.Timeout(120.0, connect=10.0)) as client:
        try:
            async with client.stream("POST", url, json=payload, headers=headers) as response:
                if response.status_code >= 400:
                    error_text = await response.aread()
                    logger.error(f"Direct LLM error: {response.status_code} {error_text[:200]}")
                    yield f"[模型调用错误: {response.status_code}]"
                    return

                async for line in response.aiter_lines():
                    line = line.strip()
                    if not line or not line.startswith("data: "):
                        continue
                    data = line[6:].strip()
                    if data == "[DONE]":
                        break
                    try:
                        parsed = json.loads(data)
                        delta = parsed.get("choices", [{}])[0].get("delta", {})
                        content = delta.get("content", "")
                        if content:
                            yield content
                    except (json.JSONDecodeError, IndexError, KeyError):
                        continue
        except Exception as e:
            logger.error(f"Direct LLM stream error: {e}")
            yield f"[生成错误: {str(e)}]"


def should_use_quick_mode(mode: str, max_score: float) -> bool:
    """根据用户选择的 mode 和预检索相似度判断是否应该使用快速回答。"""
    if mode == "quick":
        return True
    if mode == "deep":
        return False
    return max_score >= SIMILARITY_THRESHOLD_HIGH


def _parse_frontmatter(text: str) -> dict:
    """解析 Markdown 文件的 YAML frontmatter。"""
    fm_match = re.match(r'^---\s*\n(.*?)\n---\s*\n', text, re.DOTALL)
    if not fm_match or not fm_match.group(1):
        return {}
    fm = {}
    for line in fm_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


async def _build_kb_doc_index() -> list[dict]:
    """从数据库构建知识库文档的标题索引。"""
    global _kb_doc_index, _kb_index_ts

    if _kb_doc_index and (time.time() - _kb_index_ts) < _KB_INDEX_TTL:
        return _kb_doc_index

    results = []

    try:
        async for conn, cur in (get_system_db() if _has_split_databases() else get_db()):
            await cur.execute("SELECT file_path, title, category, region FROM kb_documents")
            rows = await cur.fetchall()

            for raw_row in rows:
                row = dict(raw_row)
                rel_path = row.get('file_path', '') or ''
                if not rel_path:
                    continue
                doc_id = rel_path.replace('/', '__').replace('\\', '__')
                filename = os.path.basename(rel_path)
                doc_title = row.get('title', '') or filename.replace('.md', '')

                # 映射分类目录名
                category = row.get('category', '') or ''
                cat_dir = 'law'
                if category == '处罚案例':
                    cat_dir = 'case'
                elif category == '处罚裁量':
                    cat_dir = 'standard'

                results.append({
                    'id': doc_id,
                    'title': doc_title,
                    'name': filename.replace('.md', ''),
                    'category': cat_dir,
                    'region': row.get('region', '') or '',
                })

        logger.info(f"Built KB doc index from DB: {len(results)} documents")
    except Exception as e:
        logger.warning(f"从数据库构建索引失败: {e}")

    _kb_doc_index = results
    _kb_index_ts = time.time()
    return results


def _normalize_title(text: str) -> str:
    """移除书名号、空格等用于匹配的标准化。"""
    return text.replace('《', '').replace('》', '').replace(' ', '').replace('\u3000', '').strip()


def _region_match(doc_region: str, user_region_path: str) -> bool:
    """判断文档区域是否与用户区域匹配。

    匹配规则：
      - 文档 region 为 "全国" → 始终匹配
      - 用户区域路径包含文档区域名 → 匹配（如用户"福建省/厦门市" 匹配文档"福建省"或"厦门市"）
      - 文档区域为空 → 视为全国
    """
    if not doc_region or doc_region == '全国':
        return True
    if not user_region_path:
        return True
    return doc_region in user_region_path or user_region_path.startswith(doc_region)


async def extract_content_references(content: str, region_filter: str | None = None) -> list[dict]:
    """从 AI 回答内容中提取文档引用，匹配知识库文档。

    识别模式：
      1. 《xxx》 书名号包裹的文档名
      2. "xxx法" 末尾带"法"字的法规名（如《食品安全法》→ 食品安全法）

    每个匹配会尝试在知识库文档索引中查找对应文档，返回 references。
    """
    index = await _build_kb_doc_index()
    if not index or not content:
        return []

    # 区域过滤：优先匹配同区域/全国/上级区域文档
    if region_filter:
        filtered_index = [d for d in index if _region_match(d.get('region', ''), region_filter)]
        if filtered_index:
            index = filtered_index

    # 提取《...》中的内容
    quoted_matches = re.findall(r'《([^》]{2,60})》', content)
    candidates = list(dict.fromkeys([_normalize_title(m) for m in quoted_matches]))

    if not candidates:
        return []

    # 为每个候选词匹配最佳文档
    matched_refs = []
    seen_ids = set()

    for candidate in candidates:
        candidate_matches: list[dict] = []

        for doc in index:
            doc_title = _normalize_title(doc['title'])
            doc_name = _normalize_title(doc['name'])

            score = 0.0

            if candidate == doc_title or candidate == doc_name:
                score = 1.0
            elif candidate in doc_title:
                score = max(0.7, len(candidate) / len(doc_title))
            elif candidate in doc_name:
                score = max(0.6, len(candidate) / len(doc_name) * 0.9)
            elif len(candidate) > len(doc_title) and doc_title in candidate:
                score = 0.5

            if score >= 0.4 and doc['id'] not in seen_ids:
                candidate_matches.append({
                    'doc': doc,
                    'score': score,
                })

        # 每个候选词取 top-3
        candidate_matches.sort(key=lambda x: x['score'], reverse=True)
        for m in candidate_matches[:3]:
            doc = m['doc']
            if doc['id'] not in seen_ids:
                seen_ids.add(doc['id'])
                matched_refs.append({
                    'title': doc['title'][:80],
                    'source': 'kb_lib',
                    'link': f'/knowledge/lib/{doc["id"]}',
                    'score': round(m['score'], 2),
                    'excerpt': f'知识库文档 · {doc.get("category", "")}',
                })

    # 按分数排序，总数不超过 20
    matched_refs.sort(key=lambda x: x.get('score', 0), reverse=True)
    return matched_refs[:20]