import copy
import re
from pathlib import Path

import nonebot
from nonebot import on_command
from nonebot.adapters import Bot, Event, Message
from nonebot.log import logger
from nonebot.message import handle_event
from nonebot.params import CommandArg

from konabot.common.database import DatabaseManager
from konabot.common.longtask import DepLongTaskTarget

ROOT_PATH = Path(__file__).resolve().parent

cmd = on_command(cmd="语法糖", aliases={"糖", "sugar"}, block=True)

db_manager = DatabaseManager()
driver = nonebot.get_driver()


@driver.on_startup
async def register_startup_hook():
    await init_db()


@driver.on_shutdown
async def register_shutdown_hook():
    await db_manager.close_all_connections()


async def init_db():
    await db_manager.execute_by_sql_file(ROOT_PATH / "sql" / "create_table.sql")

    table_info = await db_manager.query("PRAGMA table_info(syntactic_sugar)")
    columns = {str(row.get("name")) for row in table_info}
    if "channel_id" not in columns:
        await db_manager.execute(
            "ALTER TABLE syntactic_sugar ADD COLUMN channel_id VARCHAR(255) NOT NULL DEFAULT ''"
        )

    await db_manager.execute("DROP INDEX IF EXISTS idx_syntactic_sugar_name_belong_to")
    await db_manager.execute(
        "CREATE UNIQUE INDEX IF NOT EXISTS idx_syntactic_sugar_name_channel_target "
        "ON syntactic_sugar(name, channel_id, belong_to)"
    )


def _extract_reply_plain_text(evt: Event) -> str:
    reply = getattr(evt, "reply", None)
    if reply is None:
        return ""

    reply_message = getattr(reply, "message", None)
    if reply_message is None:
        return ""

    extract_plain_text = getattr(reply_message, "extract_plain_text", None)
    if callable(extract_plain_text):
        return extract_plain_text().strip()
    return str(reply_message).strip()


def _split_variables(tokens: list[str]) -> tuple[list[str], dict[str, str]]:
    positional: list[str] = []
    named: dict[str, str] = {}

    for token in tokens:
        if "=" in token:
            key, value = token.split("=", 1)
            key = key.strip()
            if key:
                named[key] = value
            continue
        positional.append(token)

    return positional, named


def _render_template(content: str, positional: list[str], named: dict[str, str]) -> str:
    def replace(match: re.Match[str]) -> str:
        key = match.group(1).strip()
        if key.isdigit():
            idx = int(key) - 1
            if 0 <= idx < len(positional):
                return positional[idx]
            return match.group(0)
        return named.get(key, match.group(0))

    return re.sub(r"\{([^{}]+)\}", replace, content)


async def _store_sugar(name: str, content: str, belong_to: str, channel_id: str):
    await db_manager.execute_by_sql_file(
        ROOT_PATH / "sql" / "insert_sugar.sql",
        (name, content, belong_to, channel_id),
    )


async def _delete_sugar(name: str, belong_to: str, channel_id: str):
    await db_manager.execute(
        "DELETE FROM syntactic_sugar WHERE name = ? AND belong_to = ? AND channel_id = ?",
        (name, belong_to, channel_id),
    )


async def _find_sugar(name: str, belong_to: str, channel_id: str) -> str | None:
    rows = await db_manager.query(
        (
            "SELECT content FROM syntactic_sugar "
            "WHERE name = ? AND channel_id = ? "
            "ORDER BY CASE WHEN belong_to = ? THEN 0 ELSE 1 END, id ASC "
            "LIMIT 1"
        ),
        (name, channel_id, belong_to),
    )
    if not rows:
        return None
    return rows[0].get("content")


async def _reinject_command(bot: Bot, evt: Event, command_text: str) -> bool:
    depth = int(getattr(evt, "_syntactic_sugar_depth", 0))
    if depth >= 3:
        return False

    try:
        cloned_evt = copy.deepcopy(evt)
    except Exception:
        logger.exception("语法糖克隆事件失败")
        return False

    message = getattr(cloned_evt, "message", None)
    if message is None:
        return False

    try:
        msg_obj = type(message)(command_text)
    except Exception:
        msg_obj = command_text

    setattr(cloned_evt, "message", msg_obj)
    if hasattr(cloned_evt, "original_message"):
        setattr(cloned_evt, "original_message", msg_obj)
    if hasattr(cloned_evt, "raw_message"):
        setattr(cloned_evt, "raw_message", command_text)

    setattr(cloned_evt, "_syntactic_sugar_depth", depth + 1)

    try:
        await handle_event(bot, cloned_evt)
    except Exception:
        logger.exception("语法糖回注事件失败")
        return False
    return True


@cmd.handle()
async def _(bot: Bot, evt: Event, target: DepLongTaskTarget, args: Message = CommandArg()):
    raw = args.extract_plain_text().strip()
    if not raw:
        return

    tokens = raw.split()
    action = tokens[0]
    target_id = target.target_id
    channel_id = target.channel_id

    if action == "存入":
        if len(tokens) < 2:
            await cmd.finish("请提供要存入的名称")
        name = tokens[1].strip()
        content = " ".join(tokens[2:]).strip()
        if not content:
            content = _extract_reply_plain_text(evt)
        if not content:
            await cmd.finish("请提供要存入的内容")

        await _store_sugar(name, content, target_id, channel_id)
        await cmd.finish(f"糖已存入：「{name}」！")

    if action == "删除":
        if len(tokens) < 2:
            await cmd.finish("请提供要删除的名称")
        name = tokens[1].strip()
        await _delete_sugar(name, target_id, channel_id)
        await cmd.finish(f"已删除糖：「{name}」！")

    if action == "查看":
        if len(tokens) < 2:
            await cmd.finish("请提供要查看的名称")
        name = tokens[1].strip()
        content = await _find_sugar(name, target_id, channel_id)
        if content is None:
            await cmd.finish(f"没有糖：「{name}」")
        await cmd.finish(f"糖的内容：「{content}」")


    name = action
    content = await _find_sugar(name, target_id, channel_id)
    if content is None:
        await cmd.finish(f"没有糖：「{name}」")

    positional, named = _split_variables(tokens[1:])
    rendered = _render_template(content, positional, named)

    ok = await _reinject_command(bot, evt, rendered)
    if not ok:
        await cmd.finish(f"糖的展开结果：「{rendered}」")
