from pathlib import Path
from typing import Any, Callable
from zipfile import ZIP_DEFLATED, ZipFile

from . import bexecute, bpath
from .btype import XPath


def _zip(zfile: Path, path_dict: dict[str, Path]):
    bpath.make(zfile.parent)
    with ZipFile(zfile, 'w', ZIP_DEFLATED) as f:
        for fname in sorted(path_dict.keys()):
            file = path_dict[fname]
            if file.is_file():
                f.write(file, fname)


def zip_file(zfile: XPath, target_file: XPath, name: str | None = None):
    zfile = bpath.get(zfile)
    target_file = bpath.get(target_file)
    if name is None:
        name = target_file.name
    _zip(zfile, {name: target_file})


def zip_folder(zfile: XPath, target_dir: XPath, filter_func: Callable[[Path], bool] | None = None):
    zfile = bpath.get(zfile)
    target_dir = bpath.get(target_dir)
    ary = bpath.list_path(target_dir, True)
    if filter_func:
        ary = list(filter(filter_func, ary))
    _zip(zfile, {str(x.relative_to(target_dir)): x for x in ary})


def unzip(file: XPath, output_dir: XPath | None = None):
    file = bpath.get(file)
    output_dir = output_dir or file.parent
    with ZipFile(file) as f:
        for sub_file in sorted(f.namelist()):
            try:
                # zipfile 代码中指定了cp437，这里会导致中文乱码
                encode_sub_file = sub_file.encode('cp437').decode('gbk')
            except Exception:
                encode_sub_file = sub_file
            f.extract(sub_file, output_dir)
            # 处理压缩包中的中文文件名在windows下乱码
            if sub_file != encode_sub_file:
                to_file = bpath.get(output_dir, encode_sub_file)
                bpath.get(output_dir, sub_file).rename(to_file)


async def seven_zip(zfile: XPath, *target: XPath):
    await _run_seven('a', zfile, *target)


async def seven_zip_folder(zfile: XPath, folder: XPath):
    await seven_zip(zfile, *Path(folder).glob('*'))


async def seven_unzip(zfile: XPath, output: XPath):
    await _run_seven('x', f'-o{output}', zfile)


async def seven_rename(zfile: XPath, from_name: str, to_name: str):
    await _run_seven('rn', zfile, from_name, to_name)


async def _run_seven(*args: Any):
    result_bytes, error_bytes, _ = await bexecute.run_quiet('7z', *args)
    assert not error_bytes, error_bytes.decode('gbk')
    assert b'Everything is Ok' in result_bytes, result_bytes.decode('gbk')
