"""知识库问答 handler"""
import asyncio
import html
import logging
import re

from telegram import Update
from telegram.ext import CommandHandler, ContextTypes, filters

import config
from services.message_utils import record_bot_message

logger = logging.getLogger("spam_guard")


def looks_like_markdown(text: str) -> bool:
    return bool(re.search(r"(^|\n)(#{1,3}\s|[-*]\s|\d+\.\s|```)|\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`", text))


def optimize_plain_answer(text: str) -> str:
    """非 Markdown 回答也做一点可读性整理。"""
    text = text.strip()
    if looks_like_markdown(text):
        return text
    lines = [line.strip() for line in text.splitlines() if line.strip()]
    if len(lines) <= 1:
        return f"*回答*\n{text}"
    return "*回答*\n" + "\n".join(f"- {line}" if not line.startswith(("-", "•", "✅", "⚠️", "📚")) else line for line in lines)


def markdown_to_html(text: str) -> str:
    """把常见 Markdown 转成 Telegram HTML，规避 Markdown 解析器对 _/链接/长文本的误判。"""
    code_blocks: list[str] = []

    def stash_code_block(match: re.Match) -> str:
        code = html.escape(match.group(1).strip())
        code_blocks.append(f"<pre><code>{code}</code></pre>")
        return f"@@CODE_BLOCK_{len(code_blocks) - 1}@@"

    text = re.sub(r"```(?:\w+)?\n?(.*?)```", stash_code_block, text, flags=re.S)
    text = html.escape(text)
    text = re.sub(r"^#{1,3}\s+(.+)$", r"<b>\1</b>", text, flags=re.M)
    text = re.sub(r"\*\*([^*\n]+)\*\*", r"<b>\1</b>", text)
    text = re.sub(r"(?<!\*)\*([^*\n]+)\*(?!\*)", r"<b>\1</b>", text)
    text = re.sub(r"`([^`\n]+)`", r"<code>\1</code>", text)
    for i, block in enumerate(code_blocks):
        text = text.replace(f"@@CODE_BLOCK_{i}@@", block)
    return text


def compact_answer(text: str, max_len: int = 3800) -> str:
    """尽量压到一条 Telegram 消息；超长时保留开头和关键尾部。"""
    text = optimize_plain_answer(text)
    if len(text) <= max_len:
        return text
    tail_markers = ["**文档**", "📚", "相关文档", "文档"]
    tail = ""
    for marker in tail_markers:
        idx = text.rfind(marker)
        if idx > max_len // 2:
            tail = text[idx:].strip()
            break
    head_len = max_len - len(tail) - 80 if tail else max_len - 40
    head = text[:max(1000, head_len)].rstrip()
    return head + "\n\n...（内容较长已压缩，可追问具体步骤）" + (("\n\n" + tail) if tail else "")


def split_answer(text: str, max_len: int = 3900, max_parts: int = 2) -> list[str]:
    """优先单条；实在超长最多拆 2 条。"""
    if len(text) <= max_len:
        return [text]
    parts = []
    rest = text
    while rest and len(parts) < max_parts:
        cut = min(len(rest), max_len)
        if cut < len(rest):
            better = rest.rfind("\n\n", 0, cut)
            if better > max_len // 2:
                cut = better
        parts.append(rest[:cut].strip())
        rest = rest[cut:].strip()
    if rest and parts:
        parts[-1] += "\n\n...（后续内容已省略，可继续追问）"
    return parts


async def safe_send(update: Update, context: ContextTypes.DEFAULT_TYPE, text: str, markdown: bool | None = None):
    """优先回复原消息；Markdown 内容转 HTML 发送，失败自动降级纯文本。"""
    chat_id = update.effective_chat.id
    use_markup = looks_like_markdown(text) if markdown is None else markdown
    send_text = markdown_to_html(text) if use_markup else text

    async def _record(msg, body):
        await record_bot_message(msg, body)
        return msg

    if use_markup:
        try:
            return await _record(await update.message.reply_text(send_text, parse_mode="HTML", disable_web_page_preview=True), send_text)
        except Exception as e:
            if "Message to be replied not found" in str(e):
                try:
                    return await _record(await context.bot.send_message(chat_id=chat_id, text=send_text, parse_mode="HTML", disable_web_page_preview=True), send_text)
                except Exception:
                    return await _record(await context.bot.send_message(chat_id=chat_id, text=text, disable_web_page_preview=True), text)
            logger.warning(f"HTML 格式发送失败，降级普通文本: {e}")

    try:
        return await _record(await update.message.reply_text(text, disable_web_page_preview=True), text)
    except Exception as e:
        if "Message to be replied not found" not in str(e):
            logger.warning(f"发送回复失败，降级普通发送: {e}")
        return await _record(await context.bot.send_message(chat_id=chat_id, text=text, disable_web_page_preview=True), text)


async def cmd_ask(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """向知识库提问"""
    query = " ".join(context.args) if context.args else ""
    if not query:
        reply = await safe_send(update, context,
            "❓ 用法: /ask <你的问题>\n"
            "例如: /ask 怎么安装收割机\n"
            f"文档地址: {config.SITE_URL}"
        )
        asyncio.create_task(auto_delete(update.message, seconds=60))
        asyncio.create_task(auto_delete(reply, seconds=60))
        return

    logger.debug(f"📩 用户提问: [{update.message.from_user.username}] {query[:80]}")
    searching_msg = await safe_send(update, context, "🔍 正在搜索知识库并整理详细答案...")
    asyncio.create_task(auto_delete(update.message, seconds=60))

    try:
        from services.knowledge_base import ask_knowledge_base

        answer = compact_answer(await ask_knowledge_base(query))
        replies = []
        parts = split_answer(answer)
        for i, part in enumerate(parts, 1):
            prefix = f"*回答 {i}/{len(parts)}*\n\n" if len(parts) > 1 else ""
            reply = await safe_send(update, context, prefix + part, markdown=looks_like_markdown(prefix + part))
            replies.append(reply)
        for reply in replies:
            asyncio.create_task(auto_delete(reply, seconds=60))
        asyncio.create_task(auto_delete(searching_msg, seconds=0))
    except Exception as e:
        logger.error(f"❌ 回答失败: {e}")
        reply = await safe_send(update, context, f"❌ 回答失败: {e}")
        asyncio.create_task(auto_delete(reply, seconds=60))
        asyncio.create_task(auto_delete(searching_msg, seconds=0))


async def auto_delete(message, seconds=60):
    try:
        await asyncio.sleep(seconds)
        await message.delete()
    except Exception:
        pass


def register_knowledge(app):
    app.add_handler(CommandHandler("ask", cmd_ask, filters.ChatType.GROUPS))
