import asyncio
import inspect
import re
from contextlib import contextmanager
from pathlib import Path
from types import FrameType
from typing import Any, NoReturn, cast

from click.testing import Result
from colorama import Fore
from typer import Typer
from typer.testing import CliRunner

from beni.btype import Null, XPath

from . import bcolor, bfunc, block

_app = Typer()


_sub_apps: list[Typer] = []


def main(
    lock_flag: int = 0,
    bin_path: XPath = Null,
) -> None:
    with _task(lock_flag, bin_path):
        try:
            for sub_app in _sub_apps:
                _app.add_typer(sub_app)
            _app()
        except BaseException as ex:
            if type(ex) is SystemExit and ex.code in (0, 1, 2):
                # 0 - 正常结束
                # 1 - 手动中断（Ctrl+C）
                # 2 - Error: Missing command.
                pass
            else:
                raise


command = _app.command


def new_sub_app(help: str, *, sub_app_name: str = '') -> Typer:
    frame = (cast(FrameType, inspect.currentframe())).f_back
    assert frame
    file_name = Path(inspect.getframeinfo(frame).filename).stem
    sub_app_name = sub_app_name or re.sub(r'(?<!^)(?=[A-Z])', '-', file_name).lower()
    sub_app = Typer(name=sub_app_name, help=help)
    _sub_apps.append(sub_app)
    return sub_app


def abort(*error_msgs: Any) -> NoReturn:
    msg = '\n'.join([str(x) for x in error_msgs])
    if not msg:
        raise Exception('程序中断，未提供错误信息')
    raise _AbortException(msg)


def assert_true(condition: Any, *error_msgs: Any) -> None:
    if not condition:
        abort(*error_msgs)


def confirm(msg: str) -> None:
    try:
        bfunc.confirm(msg)
    except Exception:
        print('')
        abort('用户取消操作')


@contextmanager
def _task(
    lock_flag: int = 0,
    bin_path: XPath = Null,
):
    # bfunc.sys_utf8() # 由于不是每次都需要用到，界面显示了不美观 Active code page: 65001
    if bin_path:
        bfunc.add_env_path(bin_path)
    try:
        if lock_flag:
            with block.port_lock(lock_flag):
                yield
        else:
            yield
    except _AbortException as ex:
        bcolor.print_red(ex.msg)
    except BaseException:
        bcolor.set_colors(Fore.LIGHTRED_EX)
        raise


class _AbortException(BaseException):
    def __init__(self, msg: str):
        super().__init__()
        self.msg = msg


_runner: CliRunner = Null


async def test_call(*args: Any) -> Result:
    global _runner
    if not _runner:
        for sub_app in _sub_apps:
            _app.add_typer(sub_app)
        _runner = CliRunner()
    loop = asyncio.get_event_loop()
    return await loop.run_in_executor(None, _runner.invoke, _app, args)
