import asyncio
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum
from typing import cast

from loguru import logger
from nonebot.adapters import Event


MAX_WHILE_ITERATIONS = 100


@dataclass
class TextHandlerEnvironment:
    is_trusted: bool
    event: Event | None = None
    buffers: dict[str, str] = field(default_factory=dict)


@dataclass
class TextHandleResult:
    code: int
    ostream: str | None
    attachment: bytes | None = None


class TextHandler(ABC):
    name: str = ""
    keywords: list[str] = []

    @abstractmethod
    async def handle(
        self, env: TextHandlerEnvironment, istream: str | None, args: list[str]
    ) -> TextHandleResult: ...

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__}: {self.name} [{''.join(self.keywords)}]>"


class TextHandlerSync(TextHandler):
    @abstractmethod
    def handle_sync(
        self, env: TextHandlerEnvironment, istream: str | None, args: list[str]
    ) -> TextHandleResult: ...

    async def handle(
        self, env: TextHandlerEnvironment, istream: str | None, args: list[str]
    ) -> TextHandleResult:
        def _hs():
            return self.handle_sync(env, istream, args)

        return await asyncio.to_thread(_hs)


@dataclass
class Redirect:
    target: str
    append: bool = False


@dataclass
class CommandNode:
    name: str
    handler: TextHandler
    args: list[str]
    redirects: list[Redirect] = field(default_factory=list)


@dataclass
class PipelineNode:
    commands: list[CommandNode] = field(default_factory=list)
    negate: bool = False


@dataclass
class ConditionalPipeline:
    op: str | None
    pipeline: PipelineNode


@dataclass
class CommandGroup:
    chains: list[ConditionalPipeline] = field(default_factory=list)


@dataclass
class IfNode:
    condition: CommandGroup
    then_body: "Script"
    else_body: "Script | None" = None


@dataclass
class WhileNode:
    condition: CommandGroup
    body: "Script"


@dataclass
class Script:
    statements: list[CommandGroup | IfNode | WhileNode] = field(default_factory=list)


class TokenKind(Enum):
    WORD = "word"
    OP = "op"


@dataclass
class Token:
    kind: TokenKind
    value: str


class PipelineRunner:
    handlers: list[TextHandler]

    def __init__(self) -> None:
        self.handlers = []

    @staticmethod
    def get_runner():
        if "singleton" not in PipelineRunner.__annotations__:
            PipelineRunner.__annotations__["singleton"] = PipelineRunner()
        return cast(PipelineRunner, PipelineRunner.__annotations__.get("singleton"))

    def register(self, handler: TextHandler):
        self.handlers.append(handler)

    def _resolve_handler(self, cmd_name: str) -> TextHandler | str:
        matched = [
            h for h in self.handlers if cmd_name == h.name or cmd_name in h.keywords
        ]
        if not matched:
            return f"不存在名为 {cmd_name} 的函数"
        if len(matched) > 1:
            logger.warning(
                f"指令能对应超过一个文本处理器 CMD={cmd_name} handlers={self.handlers}"
            )
        return matched[0]

    def tokenize(self, script: str) -> list[Token] | str:
        tokens: list[Token] = []
        buf = ""
        quote: str | None = None
        escape = False
        i = 0
        operators = {"|", ";", ">", "&&", "||", ">>", "!"}
        escape_map = {
            "n": "\n",
            "r": "\r",
            "t": "\t",
            "0": "\0",
            "a": "\a",
            "b": "\b",
            "f": "\f",
            "v": "\v",
            "\\": "\\",
            '"': '"',
            "'": "'",
        }

        def flush_word(force: bool = False):
            nonlocal buf
            if buf or force:
                tokens.append(Token(TokenKind.WORD, buf))
                buf = ""

        while i < len(script):
            c = script[i]

            if quote is not None:
                if escape:
                    buf += escape_map.get(c, c)
                    escape = False
                elif c == "\\":
                    escape = True
                elif c == quote:
                    quote = None
                    flush_word(force=True)  # 引号闭合时强制 flush，即使是空字符串
                else:
                    buf += c
                i += 1
                continue

            if c in "'\"":
                quote = c
                i += 1
                continue

            if c.isspace():
                flush_word()
                i += 1
                continue

            two = script[i : i + 2]
            if two in operators:
                flush_word()
                tokens.append(Token(TokenKind.OP, two))
                i += 2
                continue

            if c in {"|", ";", ">", "!"}:
                flush_word()
                tokens.append(Token(TokenKind.OP, c))
                i += 1
                continue

            if c == "\\":
                if i + 1 < len(script):
                    i += 1
                    buf += escape_map.get(script[i], script[i])
                else:
                    buf += c
                i += 1
                continue

            buf += c
            i += 1

        if quote is not None:
            return "存在未闭合的引号"
        if escape:
            buf += "\\"

        flush_word()
        return tokens

    def parse_pipeline(self, script: str) -> Script | str:
        tokens = self.tokenize(script)
        if isinstance(tokens, str):
            return tokens
        if not tokens:
            return Script()

        pos = 0

        def peek(offset: int = 0) -> Token | None:
            idx = pos + offset
            return tokens[idx] if idx < len(tokens) else None

        def consume() -> Token:
            nonlocal pos
            tok = tokens[pos]
            pos += 1
            return tok

        def consume_if_op(value: str) -> bool:
            tok = peek()
            if tok is not None and tok.kind == TokenKind.OP and tok.value == value:
                consume()
                return True
            return False

        def consume_if_word(value: str) -> bool:
            tok = peek()
            if tok is not None and tok.kind == TokenKind.WORD and tok.value == value:
                consume()
                return True
            return False

        def expect_word(msg: str) -> Token | str:
            tok = peek()
            if tok is None or tok.kind != TokenKind.WORD:
                return msg
            return consume()

        def parse_command() -> CommandNode | str:
            first = expect_word("缺少指令名")
            if isinstance(first, str):
                return first

            handler = self._resolve_handler(first.value)
            if isinstance(handler, str):
                return handler

            args: list[str] = []
            redirects: list[Redirect] = []

            while True:
                tok = peek()
                if tok is None:
                    break
                if tok.kind == TokenKind.OP and tok.value in {"|", ";", "&&", "||"}:
                    break
                if tok.kind == TokenKind.OP and tok.value in {">", ">>"}:
                    op_tok = consume()
                    target = expect_word("重定向操作符后面需要缓存名")
                    if isinstance(target, str):
                        return target
                    redirects.append(
                        Redirect(target=target.value, append=op_tok.value == ">>")
                    )
                    continue
                if tok.kind != TokenKind.WORD:
                    return f"无法解析的 token: {tok.value}"
                args.append(consume().value)

            return CommandNode(
                name=first.value,
                handler=handler,
                args=args,
                redirects=redirects,
            )

        def parse_pipe() -> PipelineNode | str:
            negate = False
            while consume_if_op("!"):
                negate = not negate

            pipeline = PipelineNode(negate=negate)
            command = parse_command()
            if isinstance(command, str):
                return command
            pipeline.commands.append(command)

            while True:
                tok = peek()
                if tok is None or tok.kind != TokenKind.OP or tok.value != "|":
                    break
                consume()
                next_command = parse_command()
                if isinstance(next_command, str):
                    return next_command
                pipeline.commands.append(next_command)

            return pipeline

        def parse_chain() -> CommandGroup | str:
            group = CommandGroup()
            first_pipeline = parse_pipe()
            if isinstance(first_pipeline, str):
                return first_pipeline
            group.chains.append(ConditionalPipeline(op=None, pipeline=first_pipeline))

            while True:
                tok = peek()
                if tok is None or tok.kind != TokenKind.OP or tok.value not in {"&&", "||"}:
                    break
                op = consume().value
                next_pipeline = parse_pipe()
                if isinstance(next_pipeline, str):
                    return next_pipeline
                group.chains.append(ConditionalPipeline(op=op, pipeline=next_pipeline))

            return group

        def parse_if() -> IfNode | str:
            if not consume_if_word("if"):
                return "缺少 if"

            condition = parse_chain()
            if isinstance(condition, str):
                return condition

            consume_if_op(";")
            if not consume_if_word("then"):
                return "if 语句缺少 then"

            then_body = parse_script(stop_words={"else", "fi"})
            if isinstance(then_body, str):
                return then_body

            else_body: Script | None = None
            if consume_if_word("else"):
                else_body = parse_script(stop_words={"fi"})
                if isinstance(else_body, str):
                    return else_body

            if not consume_if_word("fi"):
                return "if 语句缺少 fi"

            return IfNode(condition=condition, then_body=then_body, else_body=else_body)

        def parse_while() -> WhileNode | str:
            if not consume_if_word("while"):
                return "缺少 while"

            condition = parse_chain()
            if isinstance(condition, str):
                return condition

            consume_if_op(";")
            if not consume_if_word("do"):
                return "while 语句缺少 do"

            body = parse_script(stop_words={"done"})
            if isinstance(body, str):
                return body

            if not consume_if_word("done"):
                return "while 语句缺少 done"

            return WhileNode(condition=condition, body=body)

        def parse_statement() -> CommandGroup | IfNode | WhileNode | str:
            tok = peek()
            if tok is not None and tok.kind == TokenKind.WORD:
                if tok.value == "if":
                    return parse_if()
                if tok.value == "while":
                    return parse_while()
            return parse_chain()

        def parse_script(stop_words: set[str] | None = None) -> Script | str:
            parsed = Script()
            nonlocal pos

            while pos < len(tokens):
                tok = peek()
                if tok is None:
                    break

                if stop_words and tok.kind == TokenKind.WORD and tok.value in stop_words:
                    break

                if tok.kind == TokenKind.OP and tok.value == ";":
                    consume()
                    continue

                statement = parse_statement()
                if isinstance(statement, str):
                    return statement
                parsed.statements.append(statement)

                tok = peek()
                if tok is not None and tok.kind == TokenKind.OP and tok.value == ";":
                    consume()

            return parsed

        parsed = parse_script()
        if isinstance(parsed, str):
            return parsed
        if pos != len(tokens):
            tok = tokens[pos]
            return f"无法解析的 token: {tok.value}"
        return parsed

    async def _execute_command(
        self,
        command: CommandNode,
        istream: str | None,
        env: TextHandlerEnvironment,
    ) -> TextHandleResult:
        logger.debug(
            f"Executing: {command.name} args={command.args} redirects={command.redirects}"
        )
        result = await command.handler.handle(env, istream, command.args)

        if result.code != 0:
            return result

        if command.redirects:
            content = result.ostream or ""
            for redirect in command.redirects:
                if redirect.append:
                    old_content = env.buffers.get(redirect.target, "")
                    env.buffers[redirect.target] = old_content + content
                else:
                    env.buffers[redirect.target] = content
            return TextHandleResult(code=0, ostream=None, attachment=result.attachment)

        return result

    async def _execute_pipeline(
        self,
        pipeline: PipelineNode,
        istream: str | None,
        env: TextHandlerEnvironment,
    ) -> TextHandleResult:
        current_stream = istream
        last_result = TextHandleResult(code=0, ostream=None)

        for command in pipeline.commands:
            try:
                last_result = await self._execute_command(command, current_stream, env)
            except Exception as e:
                logger.error(f"Pipeline execution failed at {command.name}")
                logger.exception(e)
                return TextHandleResult(code=-1, ostream="处理流水线时出现 python 错误")

            if last_result.code != 0:
                if pipeline.negate:
                    return TextHandleResult(code=0, ostream=None)
                return last_result
            current_stream = last_result.ostream

        if pipeline.negate:
            return TextHandleResult(code=1, ostream=None)
        return last_result

    async def _execute_group(
        self,
        group: CommandGroup,
        istream: str | None,
        env: TextHandlerEnvironment,
    ) -> TextHandleResult:
        last_result = TextHandleResult(code=0, ostream=None)

        for chain in group.chains:
            should_run = True
            if chain.op == "&&":
                should_run = last_result.code == 0
            elif chain.op == "||":
                should_run = last_result.code != 0

            if should_run:
                last_result = await self._execute_pipeline(chain.pipeline, istream, env)

        return last_result

    async def _execute_if(
        self,
        if_node: IfNode,
        istream: str | None,
        env: TextHandlerEnvironment,
    ) -> TextHandleResult:
        condition_result = await self._execute_group(if_node.condition, istream, env)
        if condition_result.code == 0:
            results = await self.run_pipeline(if_node.then_body, istream, env)
        else:
            results = (
                await self.run_pipeline(if_node.else_body, istream, env)
                if if_node.else_body is not None
                else [TextHandleResult(code=0, ostream=None)]
            )
        return results[-1] if results else TextHandleResult(code=0, ostream=None)

    async def _execute_while(
        self,
        while_node: WhileNode,
        istream: str | None,
        env: TextHandlerEnvironment,
    ) -> TextHandleResult:
        last_result = TextHandleResult(code=0, ostream=None)

        for _ in range(MAX_WHILE_ITERATIONS):
            condition_result = await self._execute_group(while_node.condition, istream, env)
            if condition_result.code != 0:
                return last_result

            body_results = await self.run_pipeline(while_node.body, istream, env)
            if body_results:
                last_result = body_results[-1]
                if last_result.code != 0:
                    return last_result

        return TextHandleResult(
            code=2,
            ostream=f"while 循环超过最大迭代次数限制（{MAX_WHILE_ITERATIONS}）",
        )

    async def run_pipeline(
        self,
        pipeline: Script,
        istream: str | None,
        env: TextHandlerEnvironment | None = None,
    ) -> list[TextHandleResult]:
        if env is None:
            env = TextHandlerEnvironment(is_trusted=False, event=None, buffers={})

        results: list[TextHandleResult] = []

        for statement in pipeline.statements:
            try:
                if isinstance(statement, IfNode):
                    results.append(await self._execute_if(statement, istream, env))
                elif isinstance(statement, WhileNode):
                    results.append(await self._execute_while(statement, istream, env))
                else:
                    results.append(await self._execute_group(statement, istream, env))
            except Exception as e:
                logger.error(f"Pipeline execution failed: {e}")
                logger.exception(e)
                results.append(
                    TextHandleResult(code=-1, ostream="处理流水线时出现 python 错误")
                )
                return results

        return results


def register_text_handlers(*handlers: TextHandler):
    for handler in handlers:
        PipelineRunner.get_runner().register(handler)
