import asyncio
import os
import shutil
import subprocess
import uuid
from contextlib import contextmanager
from pathlib import Path
from typing import Any

import aiofiles

from .btype import XPath


def get(path: XPath, *expand: Any):
    if type(path) is not Path:
        path = Path(path)
    if expand:
        return path.joinpath('/'.join([str(x) for x in expand])).resolve()
    else:
        return path.resolve()


def user(*expand: Any):
    return get(Path('~').expanduser(), *expand)


def desktop(*expand: Any):
    return user('Desktop', *expand)


def workspace(*expand: Any):
    return get('/.beni-workspace', *expand)


def temp_file():
    return workspace(f'temp/{uuid.uuid4()}.tmp')


def temp_path():
    return workspace(f'temp/{uuid.uuid4()}')


def change_relative(target: XPath, from_relative: XPath, to_relative: XPath):
    target = get(target)
    from_relative = get(from_relative)
    to_relative = get(to_relative)
    assert target.is_relative_to(from_relative)
    return to_relative.joinpath(target.relative_to(from_relative))


def open_path(path: XPath):
    subprocess.run(['explorer', str(path)])


def remove(*paths: XPath):
    for path in paths:
        path = get(path)
        if path.is_file():
            path.unlink(True)
        elif path.is_dir():
            shutil.rmtree(path)


def make(*paths: XPath):
    for path in paths:
        path = get(path)
        path.mkdir(parents=True, exist_ok=True)


def clear_dir(*dir_list: XPath):
    for dir in dir_list:
        remove(*[x for x in get(dir).iterdir()])


def copy(src: XPath, dst: XPath):
    src = get(src)
    dst = get(dst)
    make(dst.parent)
    if src.is_file():
        shutil.copyfile(src, dst)
    elif src.is_dir():
        shutil.copytree(src, dst)
    else:
        if not src.exists():
            raise Exception(f'copy error: src not exists {src}')
        else:
            raise Exception(f'copy error: src not support {src}')


def copy_many(data_dict: dict[XPath, XPath]):
    for src, dst in data_dict.items():
        copy(src, dst)


def copy_overwrite(src: XPath, dst: XPath):
    "强制覆盖（判断文件和目录类型，如果源位置和目标位置类型不同则失败）"
    for item in list_file(src, True):
        copy(item, change_relative(item, src, dst))
    for item in list_dir(src, True):
        dst_dir = change_relative(item, src, dst)
        if not dst_dir.exists():
            make(dst_dir)
        elif not dst_dir.is_dir():
            raise Exception(f'copy_overwrite error: dst is not dir {dst_dir}')


def move(src: XPath, dst: XPath, force: bool = False):
    src = get(src)
    dst = get(dst)
    if dst.exists():
        if force:
            remove(dst)
        else:
            raise Exception(f'move error: dst exists {dst}')
    make(dst.parent)
    os.rename(src, dst)


def move_many(data_dict: dict[XPath, XPath], force: bool = False):
    for src, dst in data_dict.items():
        move(src, dst, force)


def rename_name(src: XPath, name: str):
    src = get(src)
    src.rename(src.with_name(name))


def rename_stem(src: XPath, stem_name: str):
    src = get(src)
    src.rename(src.with_stem(stem_name))


def rename_suffix(src: XPath, suffix_name: str):
    src = get(src)
    src.rename(src.with_suffix(suffix_name))


def list_path(path: XPath, recursive: bool = False):
    "获取指定路径下文件以及目录列表"
    path = get(path)
    if recursive:
        return list(path.glob('**/*'))
    else:
        return list(path.glob('*'))


def list_file(path: XPath, recursive: bool = False):
    "获取指定路径下文件列表"
    path = get(path)
    if recursive:
        return list(filter(lambda x: x.is_file(), path.glob('**/*')))
    else:
        return list(filter(lambda x: x.is_file(), path.glob('*')))


def list_dir(path: XPath, recursive: bool = False):
    "获取指定路径下目录列表"
    path = get(path)
    if recursive:
        return list(filter(lambda x: x.is_dir(), path.glob('**/*')))
    else:
        return list(filter(lambda x: x.is_dir(), path.glob('*')))


@contextmanager
def use_temp_file():
    file = temp_file()
    try:
        yield file
    finally:
        remove(file)


@contextmanager
def use_temp_path(is_make_path: bool = False):
    path = temp_path()
    if is_make_path:
        make(path)
    try:
        yield path
    finally:
        remove(path)


@contextmanager
def change_path(path: XPath):
    path = Path(path)
    current_path = os.getcwd()
    try:
        os.chdir(str(path))
        yield
    finally:
        os.chdir(current_path)


async def remove_secure(*paths: XPath, passes: int = 7):
    file_list: list[Path] = []
    for path in paths:
        file_list.extend(list_file(path, True))
    file_list = list(set([x.absolute() for x in file_list]))
    await asyncio.gather(*[_remove_file_secure(x, passes) for x in file_list])
    for path in paths:
        remove(path)


async def _remove_file_secure(file: Path, passes: int):
    # 获取文件大小
    file_size = file.stat().st_size

    # 多次覆写模式（符合DoD标准）
    patterns = [
        b'\x55' * 1024,  # 模式 0x55
        b'\xaa' * 1024,  # 模式 0xAA
        os.urandom(1024),  # 随机数据
        b'\x00' * 1024,  # 全零
        b'\xff' * 1024,  # 全一
    ]

    async with aiofiles.open(file, 'rb+') as f:
        # 多次覆写
        for i in range(passes):
            # 选择覆写模式
            if i < len(patterns):
                pattern = patterns[i]
            else:
                pattern = os.urandom(1024)  # 后续用随机数据

            # 定位到文件开始
            await f.seek(0)

            # 分块写入防止内存溢出
            written = 0
            while written < file_size:
                block_size = min(1024, file_size - written)
                await f.write(pattern[:block_size])
                written += block_size

            # 强制写入磁盘
            await f.flush()
            os.fsync(f.fileno())

        # 最后用全零覆写文件元数据
        await f.truncate(0)
