from typing import Dict, List, Optional, Union, Type, Any, overload
from pydantic import BaseModel
from pydantic.utils import Representation
import re
import inspect
from .element import MessageElement
from .messageChain import MessageChain
from .utils import split_once, split
from ..exceptions import ParamsUnmatched, InvalidOptionName, NullName, InvalidFormatMap, NullTextMessage
from arclet.letoderea.entities.decorator import EventDecorator
from arclet.letoderea.utils import ArgumentPackage


AnyIP = r"(\d+)\.(\d+)\.(\d+)\.(\d+)"
AnyDigit = r"(\d+)"
AnyStr = r"(.+)"
AnyUrl = r"(http[s]?://.+)"
Bool = r"(True|False)"
Argument_T = Union[str, Type[MessageElement]]


class Args:
    args: Dict[str, Argument_T]
    _default: Dict[str, Union[str, MessageElement]]
    empty: bool = False

    def __init__(self, **kwargs):
        self.args = {k: v for k, v in kwargs.items() if k not in ('name', 'type')}
        self._default = {}
        if not self.args:
            self.empty = True

    def default(self, **kwargs):
        self._default = {k: v for k, v in kwargs.items() if k not in ('name', 'type')}
        return self

    def check(self, keyword: str):
        if keyword in self._default:
            if self._default[keyword] is None:
                return inspect.Signature.empty
            return self._default[keyword]

    def params(self, sep: str = " "):
        argument_string = ""
        i = 0
        length = len(self.args)
        for k, v in self.args.items():
            arg = f"<{k}"
            if not isinstance(v, str):
                arg += f": Type_{v.__name__}"
            if k in self._default:
                default = self._default[k]
                if default is None:
                    arg += " default: Empty"
                elif isinstance(default, str):
                    arg += f" default: {default}"
                else:
                    arg += f" default: Type_{default.__name__}"
            argument_string += arg + ">"
            i += 1
            if i != length:
                argument_string += sep
        return argument_string

    def __iter__(self):
        for k, v in self.args.items():
            yield k, v

    def __len__(self):
        return len(self.args)

    def __repr__(self):
        if self.empty:
            return "Empty"
        repr_string = ""
        for k, v in self.args.items():
            text = f"'{k}': {v}"
            if k in self._default:
                text += f" default={self._default[k]}"
            repr_string += text + ",\n"
        return "\n" + repr_string + ""


class CommandInterface(BaseModel):
    name: str
    separator: str = " "

    def separate(self, sep: str):
        self.separator = sep
        return self

    class Config:
        arbitrary_types_allowed = True
        extra = "allow"


class OptionInterface(CommandInterface):
    type: str
    args: Args


class Option(OptionInterface):
    type: str = "OPT"

    def __init__(self, name: str, args: Optional[Args] = None, **kwargs):
        if name == "":
            raise NullName
        if re.match(r"^[`~?/.,<>;\':\"|!@#$%^&*()_+=\[\]}{]+.*$", name):
            raise InvalidOptionName
        super().__init__(
            name=name,
            args=args or Args(**kwargs)
        )

    def help(self, help_string: str):
        setattr(self, "help_doc", f"# {help_string}\n"
                                  f"  {self.name}{self.separator}{self.args.params(self.separator)}\n")
        return self


class Subcommand(OptionInterface):
    type: str = "SBC"
    options: List[Option]

    def __init__(self, name: str, *option: Option, args: Optional[Args] = None, **kwargs):
        if name == "":
            raise NullName
        if re.match(r"^[`~?/.,<>;\':\"|!@#$%^&*()_+=\[\]}{]+.*$", name):
            raise InvalidOptionName
        super().__init__(
            name=name,
            options=list(option),
            args=args or Args(**kwargs)
        )

    def help(self, help_string: str):
        option_string = "".join(list(map(lambda x: getattr(x, "help_doc", ""), self.options)))
        option_help = "## 该子命令内可用的选项有:\n " if option_string else ""
        setattr(self, "help_doc", f"# {help_string}\n"
                                  f"  {self.name}{self.separator}{self.args.params(self.separator)}\n"
                                  f"{option_help}{option_string}")
        return self


Options_T = List[OptionInterface]
_builtin_option = Option("-help")


class Arpamar(Representation):
    """
    亚帕玛尔(Arpamar), Alconna的珍藏宝书

    Example:
        1.`Arpamar.main_argument`: 当 Alconna 写入了 main_argument 时,该参数返回对应的解析出来的值

        2.`Arpamar.header`: 当 Alconna 的 command 内写有正则表达式时,该参数返回对应的匹配值

        3.`Arpamar.has`: 判断 Arpamar 内是否有对应的属性

        4.`Arpamar.get`: 返回 Arpamar 中指定的属性

        5.`Arpamar.matched`: 返回命令是否匹配成功

    """

    def __init__(self):
        self.current_index: int = 0  # 记录解析时当前字符串的index
        self.is_str: bool = False  # 是否解析的是string
        self.results: Dict[str, Any] = {'options': {}, 'main_args': {}}
        self.elements: Dict[int, MessageElement] = {}
        self.raw_texts: List[List[Union[int, str]]] = []
        self.need_main_args: bool = False
        self.matched: bool = False
        self.head_matched: bool = False

        self._options: Dict[str, Any] = {}
        self._args: Dict[str, Any] = {}

    __slots__ = ["current_index", "is_str", "results", "elements", "raw_texts", "need_main_args", "matched",
                 "head_matched", "_options", "_args"]

    @property
    def main_args(self):
        if self.need_main_args:
            return self.results.get('main_args')

    @property
    def header(self):
        if 'header' in self.results:
            return self.results['header']
        else:
            return self.head_matched

    @property
    def all_matched_args(self):
        return {**self.results['main_args'], **self._args}

    @property
    def option_args(self):
        return self._args

    def encapsulate_result(self) -> None:
        if not self.results.get('header'):
            del self.results['header']
        for k, v in self.results['options'].items():
            self._options.setdefault(k, v)
            if isinstance(v, dict):
                for kk, vv in v.items():
                    if not isinstance(vv, dict):
                        self._args[kk] = vv
                    else:
                        self._args.update(vv)
        del self.results['options']

    def get(self, name: str) -> Union[Dict, str, MessageElement]:
        if name in self._options:
            return self._options[name]
        elif name in self._args:
            return self._args[name]

    def has(self, name: str) -> bool:
        return any([name in self._args, name in self._options])

    def __getitem__(self, item: str):
        if item in self._options:
            return self._options[item]
        elif item in self._args:
            return self._args[item]

    def split_by(self, separate: str):
        _text: str = ""  # 重置
        _rest_text: str = ""

        if self.raw_texts[self.current_index][0]:  # 如果命令头匹配后还有字符串没匹配到
            _text, _rest_text = split_once(self.raw_texts[self.current_index][0], separate)

        elif not self.is_str and len(self.raw_texts) > 1:  # 如果命令头匹配后字符串为空则有两种可能，这里选择不止一段字符串
            self.current_index += 1
            _text, _rest_text = split_once(self.raw_texts[self.current_index][0], separate)

        return _text, _rest_text

    class Config:
        extra = 'allow'


class Alconna(CommandInterface):
    """
    亚尔康娜（Alconna），Cesloi的妹妹

    用于更加奇怪(大雾)精确的命令解析，支持String与MessageChain

    样例：Alconna(
        headers=[""],
        command="name",
        options=[
            Subcommand("sub_name",Option("sub-opt", sub_arg=sub_arg), args=sub_main_arg),
            Option("opt", arg=arg)
            ]
        main_argument=main_argument
        )

    其中
        - name: 命令名称
        - sub_name: 子命令名称
        - sub-opt: 子命令选项名称
        - sub_arg: 子命令选项参数
        - sub_main_arg: 子命令主参数
        - opt: 命令选项名称
        - arg: 命令选项参数

    Args:
        headers: 呼叫该命令的命令头，一般是你的机器人的名字或者符号，与 command 至少有一个填写
        command: 命令名称，你的命令的名字，与 headers 至少有一个填写
        options: 命令选项，你的命令可选择的所有 option ，包括子命令与单独的选项
        main_args: 主参数，填入后当且仅当命令中含有该参数时才会成功解析
    """

    name = "Alconna"
    headers: List[str]
    command: str
    options: Options_T
    main_args: Args

    def __init__(
            self,
            headers: List[str] = None,
            command: Optional[str] = None,
            options: Options_T = None,
            main_args: Optional[Args] = None,
            exception_in_time: bool = False,
            **kwargs

    ):
        # headers与command二者必须有其一
        if all([all([not headers, not command]), not options, not main_args]):
            raise NullName
        super().__init__(
            headers=headers or [""],
            command=command or "",
            options=options or [],
            main_args=main_args or Args(**kwargs),
        )
        self.exception_in_time = exception_in_time
        self.options.append(_builtin_option)
        self._initialise_arguments()

    def help(self, help_string: str):
        help_string += "\n" if help_string else ""
        command_string = f"{'|'.join(self._command_headers)}{self.separator}"
        option_string = "".join(list(map(lambda x: getattr(x, "help_doc", ""),
                                         filter(lambda x: isinstance(x, Option), self.options))))
        subcommand_string = "".join(list(map(lambda x: getattr(x, "help_doc", ""),
                                             filter(lambda x: isinstance(x, Subcommand), self.options))))
        option_help = "可用的选项有:\n" if option_string else ""
        subcommand_help = "可用的子命令有:\n" if subcommand_string else ""
        setattr(self, "help_doc", f"{command_string}{self.main_args.params(self.separator)}{help_string}\n"
                                  f"{subcommand_help}{subcommand_string}"
                                  f"{option_help}{option_string}")
        return self

    def get_help(self):
        return getattr(self, "help_doc", getattr(self.help(""), "help_doc"))

    @classmethod
    @overload
    def format(
            cls,
            format_string: str,
            format_args: List[Union[Argument_T, Args, Option, List[Option]]],
            reflect_map: Optional[Dict[str, str]] = None
    ) -> "Alconna":
        ...

    @classmethod
    @overload
    def format(
            cls,
            format_string: str,
            format_args: Dict[str, Union[Union[Argument_T, Args, Option, List[Option]]]],
            reflect_map: Optional[Dict[str, str]] = None
    ) -> "Alconna":
        ...

    @classmethod
    def format(
            cls,
            format_string: str,
            format_args: ...,
            reflect_map: Optional[Dict[str, str]] = None
    ) -> "Alconna":
        strings = split(format_string)
        command = strings.pop(0)
        options = []
        main_args = None

        _string_stack: List[str] = list()
        for i in range(len(strings)):
            if not (arg := re.findall(r"{(.+)}", strings[i])):
                _string_stack.append(strings[i])
                continue

            key = arg[0] if not reflect_map else (reflect_map[arg[0]] if reflect_map.get(arg[0]) else arg[0])

            if isinstance(format_args, List) and arg[0].isdigit():
                value = format_args[int(arg[0])]
            elif isinstance(format_args, Dict):
                value = format_args[arg[0]]
            else:
                raise InvalidFormatMap

            stack_count = len(_string_stack)
            if stack_count == 2:
                sub_name, opt_name = _string_stack
                if isinstance(value, Args):
                    options.append(Subcommand(sub_name, Option(opt_name, args=value)))
                elif not isinstance(value, Option) and not isinstance(value, List):
                    options.append(Subcommand(sub_name, Option(opt_name, **{key: value})))
                _string_stack.clear()

            if stack_count == 1:
                may_name = _string_stack.pop(0)
                if isinstance(value, Option):
                    options.append(Subcommand(may_name, value))
                elif isinstance(value, List):
                    options.append(Subcommand(may_name, *value))
                elif isinstance(value, Args):
                    options.append(Option(may_name, args=value))
                else:
                    options.append(Option(may_name, **{key: value}))

            if stack_count == 0:
                if i == 0:
                    if isinstance(value, Args):
                        main_args = value
                    elif not isinstance(value, Option) and not isinstance(value, List):
                        main_args = Args(**{key: value})
                    else:
                        if isinstance(value, Option):
                            options.append(value)
                        elif isinstance(value, List):
                            options[-1].options.extend(value)
                        elif isinstance(value, Args):
                            options[-1].args = value
                        else:
                            options[-1].args.args.update({key: value})

        alc = cls(command=command, options=options, main_args=main_args)
        return alc

    def add_options(self, options: List[OptionInterface]):
        self.options.extend(options)
        self._initialise_arguments()

    def _initialise_arguments(self):
        # params是除开命令头的剩下部分
        self._params: Dict[str, Union[Args, Dict[str, Any]]] = {"main_args": self.main_args}
        for opts in self.dict()['options']:
            if opts['type'] == "SBC":
                opts.setdefault('sub_params', {"sub_args": opts['args']})
                for sub_opts in opts['options']:
                    opts['sub_params'][sub_opts['name']] = sub_opts
            self._params[opts['name']] = opts

        self._command_headers: List[str] = []  # 依据headers与command生成一个列表，其中含有所有的命令头
        if self.headers != [""]:
            for i in self.headers:
                self._command_headers.append(i + self.command)
        elif self.command:
            self._command_headers.append(self.command)

    def _analyse_args(
            self,
            opt_args: Args,
            may_args: str,
            sep: str,
            rest_text: str
    ) -> Dict[str, Any]:

        _option_dict: Dict[str, Any] = {}
        for k, v in opt_args:
            if isinstance(v, str):
                if sep != self.separator:
                    may_arg, may_args = split_once(may_args, sep)
                else:
                    may_arg, rest_text = self.result.split_by(sep)
                if not (_arg_find := re.findall('^' + v + '$', may_arg)):
                    if (default := opt_args.check(k)) is not None:
                        _arg_find = [default]
                    else:
                        raise ParamsUnmatched(f"this param {may_arg} does not right")
                if may_arg == v:
                    _arg_find[0] = Ellipsis
                _option_dict[k] = _arg_find[0]
                if sep == self.separator:
                    self.result.raw_texts[self.result.current_index][0] = rest_text
            else:
                may_element_index = self.result.raw_texts[self.result.current_index][1] + 1
                if type(self.result.elements[may_element_index]) is v:
                    _option_dict[k] = self.result.elements.pop(may_element_index)
                elif (default := opt_args.check(k)) is not None:
                    _option_dict[k] = default
                else:
                    raise ParamsUnmatched(
                        f"this element type {type(self.result.elements[may_element_index])} does not right")
        return _option_dict

    def _analyse_option(
            self,
            param: Dict[str, Union[str, Args]],
            text: str,
            rest_text: str
    ) -> Dict[str, Any]:

        opt: str = param['name']
        args: Args = param['args']
        sep: str = param['separator']
        name, may_args = split_once(text, sep)
        if sep == self.separator:  # 在sep等于separator的情况下name是被提前切出来的
            name = text
        if not re.match('^' + opt + '$', name):  # 先匹配选项名称
            raise ParamsUnmatched(f"{name} dose not matched with {opt}")
        self.result.raw_texts[self.result.current_index][0] = rest_text
        name = name.lstrip("-")
        if args.empty:
            return {name: Ellipsis}
        return {name: self._analyse_args(args, may_args, sep, rest_text)}

    def _analyse_subcommand(
            self,
            param: Dict[str, Union[str, Dict, Args]],
            text: str,
            rest_text: str
    ) -> Dict[str, Any]:
        command: str = param['name']
        sep: str = param['separator']
        sub_params: Dict = param['sub_params']
        name, may_text = split_once(text, sep)
        if sep == self.separator:
            name = text
        if not re.match('^' + command + '$', name):
            raise ParamsUnmatched(f"{name} dose not matched with {command}")

        self.result.raw_texts[self.result.current_index][0] = may_text
        if sep == self.separator:
            self.result.raw_texts[self.result.current_index][0] = rest_text

        name = name.lstrip("-")
        if param['args'].empty and not param['options']:
            return {name: Ellipsis}

        subcommand = {}
        _get_args = False
        for i in range(len(sub_params)):
            try:
                _text, _rest_text = self.result.split_by(sep)
                if not (sub_param := sub_params.get(_text)):
                    sub_param = sub_params['sub_args']
                    for sp in sub_params:
                        if _text.startswith(sp):
                            sub_param = sub_params.get(sp)
                            break
                if isinstance(sub_param, dict):
                    # if sub_param.get('type'):
                    subcommand.update(self._analyse_option(sub_param, _text, _rest_text))
                elif not _get_args:
                    if args := self._analyse_args(sub_param, _text, sep, _rest_text):
                        _get_args = True
                        subcommand.update(args)
            except (IndexError, KeyError):
                continue
            except ParamsUnmatched:
                if self.exception_in_time:
                    raise
                break

        if sep != self.separator:
            self.result.raw_texts[self.result.current_index][0] = rest_text
        return {name: subcommand}

    def _analyse_header(self) -> str:
        head_text, self.result.raw_texts[0][0] = self.result.split_by(self.separator)
        for ch in self._command_headers:
            if not (_head_find := re.findall('^' + ch + '$', head_text)):
                continue
            self.result.head_matched = True
            if _head_find[0] != ch:
                return _head_find[0]
        if not self.result.head_matched:
            raise ParamsUnmatched(f"{head_text} does not matched")

    def analyse_message(self, message: Union[str, MessageChain]) -> Arpamar:
        if hasattr(self, "result"):
            del self.result
        self.result: Arpamar = Arpamar()

        if not self.main_args.empty:
            self.result.need_main_args = True  # 如果need_marg那么match的元素里一定得有main_argument

        if isinstance(message, str):
            self.result.is_str = True
            self.result.raw_texts.append([message, 0])
        else:
            for i, ele in enumerate(message):
                if ele.__class__.__name__ not in ("Plain", "Source", "Quote", "File"):
                    self.result.elements[i] = ele
                elif ele.__class__.__name__ == "Plain":
                    self.result.raw_texts.append([ele.text.lstrip(' '), i])

        if not self.result.raw_texts:
            if self.exception_in_time:
                raise NullTextMessage
            self.result.results.clear()
            return self.result

        try:
            self.result.results['header'] = self._analyse_header()
        except ParamsUnmatched:
            self.result.results.clear()
            return self.result

        for i in range(len(self._params)):
            if all([t[0] == "" for t in self.result.raw_texts]):
                break
            try:
                _text, _rest_text = self.result.split_by(self.separator)
                _param = self._params.get(_text)
                if not _param:
                    _param = self._params['main_args']
                    for p in self._params:
                        if _text.startswith(p):
                            _param = self._params.get(p)
                            break
                if isinstance(_param, dict):
                    if _param['type'] == 'OPT':
                        self.result.results['options'].update(self._analyse_option(_param, _text, _rest_text))
                    elif _param['type'] == 'SBC':
                        self.result.results['options'].update(self._analyse_subcommand(_param, _text, _rest_text))
                elif not self.result.results.get("main_args"):
                    self.result.results['main_args'] = self._analyse_args(_param, _text, self.separator, _rest_text)
            except (IndexError, KeyError):
                pass
            except ParamsUnmatched:
                if self.exception_in_time:
                    raise
                break

        try:
            may_element_index = self.result.raw_texts[self.result.current_index][1] + 1
            for k, v in self.main_args:
                if type(self.result.elements[may_element_index]) is v:
                    self.result.results['main_args'][k] = self.result.elements.pop(may_element_index)
                elif (default := self.main_args.check(k)) is not None:
                    self.result.results['main_args'][k] = default
                else:
                    raise ParamsUnmatched(
                        f"this element type {type(self.result.elements[may_element_index])} does not right")
        except (KeyError, IndexError):
            pass
        except ParamsUnmatched:
            if self.exception_in_time:
                raise
            pass

        if len(self.result.elements) == 0 and all([t[0] == "" for t in self.result.raw_texts]) and (
                not self.result.need_main_args or (
                self.result.need_main_args and not self.result.has('help') and self.result.results.get('main_args')
        )
        ):
            self.result.matched = True
            self.result.encapsulate_result()
        else:
            if self.exception_in_time:
                raise ParamsUnmatched(", ".join([t[0] for t in self.result.raw_texts]))
            self.result.results.clear()
        return self.result


class AlconnaParser(EventDecorator):
    """
    Alconna的装饰器形式
    """

    def __init__(self, *, alconna: Alconna):
        super().__init__(target_type=MessageChain)
        self.alconna = alconna

    def supply(self, target_argument: ArgumentPackage) -> Arpamar:
        if target_argument.annotation == MessageChain:
            return self.alconna.analyse_message(target_argument.value)
