import asyncio
import binascii
import hashlib
import importlib.resources
import json
import os
import pickle
import random
import subprocess
import sys
import uuid
from contextlib import contextmanager
from functools import wraps
from typing import Any, Callable, Coroutine

import psutil

from beni import bcolor

from .btype import AnyType, AsyncFuncType, FuncType, IfsType, Params, XPath


def split_lines(value: str) -> list[str]:
    """
    将字符串按行拆分为数组
    去掉每行首尾空格和空行
    过滤掉空白行
    """
    lines = value.replace('\r\n', '\n').split('\n')
    lines = [line.strip() for line in lines]
    lines = [line for line in lines if line]
    return lines


def split_words(value: str) -> list[str]:
    """
    将字符串按行和按空格拆分为数组
    去掉每个单词首尾空格和空单词
    过滤掉空白单词
    过滤掉重复单词
    """
    value = value.replace('\r\n', '\n')
    value = value.replace('\n', ' ')
    words = value.split(' ')
    words = [word.strip() for word in words]
    words = [word for word in words if word]
    words = list(set(words))
    return words


def json_dumps_mini(value: Any):
    return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(',', ':'))


def md5_bytes(data: bytes):
    return hashlib.md5(data).hexdigest()


def md5_str(content: str):
    return md5_bytes(content.encode())


def md5_data(data: Any):
    return md5_bytes(pickle.dumps(data))


def crc_bytes(data: bytes):
    return hex(binascii.crc32(data))[2:].zfill(8)


def crc_str(content: str):
    return crc_bytes(content.encode())


def crc_data(data: Any):
    return crc_bytes(pickle.dumps(data))


def is_exe() -> bool:
    return hasattr(sys, '_MEIPASS')


def sys_utf8():
    if is_platform_windows():
        subprocess.run(['chcp', '65001'], shell=True)


def add_env_path(path: XPath):
    value = os.getenv('path') or ''
    value = ';'.join([value, str(path)])
    os.putenv('path', value)


def make_validate_code(length: int):
    min_value = 10 ** (length - 1)
    max_value = int('9' * length)
    return str(random.randrange(min_value, max_value))


def get_value_inside(value: IfsType, min_value: IfsType, max_value: IfsType):
    "包括最小值和最大值"
    value = min(value, max_value)
    value = max(value, min_value)
    return value


def get_percent_value(target_value: float, min_value: float, max_value: float, min_result: float, max_result: float):
    "根据百分之计算指定数值"
    if target_value >= max_value:
        return max_result
    elif target_value <= min_value:
        return min_result
    else:
        percent = (target_value - min_value) / (max_value - min_value)
        return min_result + (max_result - min_result) * percent


def get_increase(from_value: float, to_value: float):
    return to_value / from_value - 1


def to_float(value: IfsType, default: float = 0):
    result = default
    try:
        result = float(value)
    except Exception:
        pass
    return result


def to_int(value: IfsType, default: int = 0):
    result = default
    try:
        result = int(value)
    except Exception:
        pass
    return result


def to_any(value: Any):
    return value


def get_sql_placement(ary: list[Any] | set[Any], placement: str = '?'):
    return '(' + ','.join([placement for _ in range(len(ary))]) + ')'


def get_wrapped(data: Any):
    result = data
    while hasattr(result, '__wrapped__'):
        result = getattr(result, '__wrapped__')
    return result


def retry(times: int):
    def func(func: AsyncFuncType) -> AsyncFuncType:
        @wraps(func)
        async def wrapper(*args: Any, **kwargs: Any):
            current = 0
            while True:
                try:
                    return await func(*args, **kwargs)
                except Exception:
                    current += 1
                    if current >= times:
                        raise

        return to_any(wrapper)

    return func


def sync_call(func: Callable[Params, Coroutine[Any, Any, AnyType]]) -> Callable[Params, AnyType]:
    @wraps(func)
    def wraper(*args: Params.args, **kwargs: Params.kwargs):
        return asyncio.run(func(*args, **kwargs))

    return to_any(wraper)


_once_call_set: set[int] = set()


def once_call(func: FuncType) -> FuncType:
    @wraps(func)
    def wraper(*args: Any, **kwargs: Any):
        assert id(func) not in _once_call_set, f'函数 {func.__module__}.{func.__name__} 只能调用一次'
        _once_call_set.add(id(func))
        return func(*args, **kwargs)

    return to_any(wraper)


def Counter(value: int = 0):
    def _(v: int = 1):
        nonlocal value
        value += v
        return value

    return _


def get_mac_address():
    return uuid.UUID(int=uuid.getnode()).hex[-12:]


@contextmanager
def try_run():
    try:
        yield
    except Exception:
        pass


def obfuscate(s: bytes) -> bytes:
    n = random.randint(1, min(9, len(s) // 4))
    chunks = [s[i : i + n] for i in range(0, len(s), n)]
    even_chunks = chunks[::2]
    odd_chunks = chunks[1::2]
    if len(chunks) % 2 == 1:
        odd_chunks.append(even_chunks.pop())
    obfuscated = b''.join(even_chunks + odd_chunks)
    mid = len(obfuscated) // 2
    obfuscated = obfuscated[:mid] + str(n).encode() + obfuscated[mid:]
    return obfuscated


def deobfuscate(s: bytes) -> bytes:
    mid = len(s) // 2
    if len(s) % 2 == 0:
        n = int(chr(s[mid - 1]))
        s = s[: mid - 1] + s[mid:]
    else:
        n = int(chr(s[mid]))
        s = s[:mid] + s[mid + 1 :]
    chunks = [s[i : i + n] for i in range(0, len(s), n)]
    half = len(chunks) // 2
    even_chunks = chunks[:half]
    odd_chunks = chunks[half:]
    if len(chunks) % 2 == 1:
        even_chunks.append(b'')
    deobfuscated = b''.join(sum(zip(even_chunks, odd_chunks), ())) + b''.join(even_chunks[len(odd_chunks) :])
    return deobfuscated


def shuffle_sequence(data: AnyType) -> AnyType:
    "打乱序列"
    temp_data = to_any(data)
    size = len(temp_data)
    n = int(size / 3) - size % 10
    return temp_data[: size - n * 2] + temp_data[size - n :] + temp_data[size - n * 2 : size - n]


def is_platform_windows() -> bool:
    return sys.platform == 'win32'


def is_platform_linux() -> bool:
    return sys.platform == 'linux'


def is_windows_powershell() -> bool:
    return is_platform_windows() and _check_process('powershell.exe', 'pwsh.exe')


def is_windows_cmd() -> bool:
    return is_platform_windows() and _check_process('cmd.exe')


def confirm(msg: str = '是否确认继续？') -> None:
    code = ''.join(chr(random.randint(ord('A'), ord('Z'))) for _ in range(3))
    input_msg = f'{msg} 【 {bcolor.yellow(" ".join(code))} 】'
    while True:
        user_input = input(input_msg).strip().upper()
        if user_input == code:
            return


@contextmanager
def use_resources(package: str, name: str):
    """
    使用资源文件，返回一个临时文件路径
    package 参数示例 bcmd.resources
    name 参数示例 project/note.md
    """
    with importlib.resources.path(package, name) as target:
        yield target


def _check_process(*target_ary: str):
    process = psutil.Process().parent()
    while process:
        if process.name() in target_ary:
            return True
        process = process.parent()
    return False
