"""
文件操作工具模块

提供常用的文件操作功能，支持：
- 文件读写、删除、复制
- 文件MD5计算
- 目录操作
- 压缩文件处理（tar、zip）
- 文件列表查询
- 本地MD5缓存管理
"""
import os
import time
import shutil
import hashlib
from hashlib import md5
from typing import List, Optional


class FileUtil:
    """
    文件操作工具类

    提供静态方法进行文件和目录操作，包含安全检查机制防止误删系统文件。

    Example:
        >>> FileUtil.write_file("/path/to/file.txt", ["line1", "line2"])
        >>> content = FileUtil.get_file_content("/path/to/file.txt")
        >>> file_list = FileUtil.get_file_list("/path/to/dir", child=True)
    """

    @staticmethod
    def write_file(file_path: str, data_list: List[str]) -> None:
        """
        将列表数据按行写入文件

        Args:
            file_path: 文件路径
            data_list: 数据列表，每个元素为一行
        """
        with open(file_path, 'w+', encoding='utf-8') as f:
            for line in data_list:
                f.write(str(line) + "\n")

    @staticmethod
    def split_file(file_path: str, target_path: str, line_count: int = 10000000) -> None:
        """
        按行数分割大文件

        Args:
            file_path: 源文件路径
            target_path: 目标目录路径
            line_count: 每个分割文件的行数
        """
        if not FileUtil.file_exists(file_path):
            return

        file_name = FileUtil.get_file_name(file_path)
        file_name = f"{file_name[:file_name.find('.')]}_split_"

        cmd_split = f"cd {target_path}; split -l {line_count} {file_path} {file_name};"
        os.system(cmd_split)
        FileUtil.rm_file(file_path)

    @staticmethod
    def untar_file(file_path: str, target_path: str) -> None:
        """
        解压tar.gz文件

        Args:
            file_path: tar.gz文件路径
            target_path: 解压目标目录
        """
        if os.path.exists(target_path):
            FileUtil.rm_file(target_path)
        FileUtil.mk_dirs(target_path)
        cmd_untar = f"tar -zxvf {file_path} -C {target_path}"
        os.system(cmd_untar)

    @staticmethod
    def unzip_file(file_path: str, target_path: str) -> None:
        """
        解压zip文件

        Args:
            file_path: zip文件路径
            target_path: 解压目标目录
        """
        if not os.path.exists(target_path):
            FileUtil.mk_dirs(target_path)

        if str(file_path).endswith(".zip"):
            cmd_unzip = f"unzip -o {file_path} -d {target_path}"
            os.system(cmd_unzip)
        else:
            print(f"Not a zip file: {file_path}")

    @staticmethod
    def file_exists(file_path: str) -> bool:
        """
        检查文件是否存在且有效

        Args:
            file_path: 文件路径

        Returns:
            文件存在且非空返回True
        """
        if not os.path.exists(file_path):
            print(f"File not exist: {file_path}")
            return False
        if not os.path.isfile(file_path):
            print(f"Not a file: {file_path}")
            return False
        if os.path.getsize(file_path) <= 0:
            print(f"File is empty: {file_path}")
            return False
        return True

    @staticmethod
    def build_file(path: str, file_name: str) -> str:
        """
        构建文件完整路径

        Args:
            path: 目录路径
            file_name: 文件名

        Returns:
            规范化的文件路径
        """
        result = f"{path}/{file_name}"
        while "//" in result:
            result = result.replace("//", "/")
        return result

    @staticmethod
    def get_file_md5(file_path: str) -> str:
        """
        计算文件的MD5值

        Args:
            file_path: 文件路径

        Returns:
            文件的MD5哈希值
        """
        file_path = FileUtil.clean_path(file_path)
        with open(file_path, 'rb') as f:
            file_md5 = hashlib.md5(f.read()).hexdigest()
        return file_md5

    @staticmethod
    def get_file_name(file_path: str, remove_suffix: bool = False) -> str:
        """
        从路径中提取文件名

        Args:
            file_path: 文件路径
            remove_suffix: 是否移除文件后缀

        Returns:
            文件名
        """
        if file_path is None:
            return ""

        file_path = FileUtil.clean_path(file_path)

        idx = file_path.rfind("/")
        if idx >= 0:
            file_name = file_path[idx + 1:]
        else:
            file_name = file_path

        if remove_suffix:
            dot_idx = file_name.rfind(".")
            if dot_idx > 0:
                file_name = file_name[:dot_idx]

        return file_name

    @staticmethod
    def get_file_list(
        file_path: str,
        child: bool = False,
        file_name_prefix: str = "",
        file_name_suffix: str = ""
    ) -> List[str]:
        """
        获取目录下的文件列表

        Args:
            file_path: 目录或文件路径
            child: 是否递归子目录
            file_name_prefix: 文件名前缀过滤
            file_name_suffix: 文件名后缀过滤

        Returns:
            文件路径列表
        """
        file_list: List[str] = []

        if not os.path.exists(file_path):
            print(f"Path not exist: {file_path}")
            return file_list

        file_path = FileUtil.clean_path(file_path)
        if os.path.isfile(file_path):
            return [file_path]

        for file_name in os.listdir(file_path):
            if file_name.startswith(".") or file_name.startswith("~"):
                continue

            temp_path = FileUtil.build_file(file_path, file_name)

            if os.path.isdir(temp_path):
                if child:
                    file_list.extend(
                        FileUtil.get_file_list(temp_path, child, file_name_prefix, file_name_suffix)
                    )
            elif os.path.isfile(temp_path):
                prefix_match = not file_name_prefix or file_name.startswith(file_name_prefix)
                suffix_match = not file_name_suffix or file_name.endswith(file_name_suffix)
                if prefix_match and suffix_match:
                    file_list.append(str(temp_path))

        return file_list

    @staticmethod
    def get_field_list(size: int) -> List[str]:
        """
        生成字段名列表

        Args:
            size: 字段数量

        Returns:
            格式为col_01, col_02...的字段名列表
        """
        if size <= 0:
            return []

        field_list = []
        for i in range(size):
            col_name = f"col_{str(i + 1).zfill(2)}"
            field_list.append(col_name)

        return field_list

    @staticmethod
    def get_date_current(date_format: str = "%Y-%m-%d %H:%M:%S") -> str:
        """
        获取当前日期时间字符串

        Args:
            date_format: 日期格式

        Returns:
            格式化的日期时间字符串
        """
        return time.strftime(date_format, time.localtime(time.time()))

    @staticmethod
    def is_empty(input_str: Optional[str]) -> bool:
        """
        检查字符串是否为空

        Args:
            input_str: 输入字符串

        Returns:
            字符串为None或空白时返回True
        """
        if input_str is None or str(input_str).strip() == "":
            return True
        return False

    @staticmethod
    def is_not_empty(input_str: Optional[str]) -> bool:
        """
        检查字符串是否非空

        Args:
            input_str: 输入字符串

        Returns:
            字符串非空时返回True
        """
        return not FileUtil.is_empty(input_str)

    @staticmethod
    def rm_file(file_path: str) -> bool:
        """
        安全删除文件或目录

        包含安全检查，防止误删系统文件。

        Args:
            file_path: 文件或目录路径

        Returns:
            删除是否成功

        Raises:
            UserWarning: 路径层级过浅时抛出
        """
        file_path = os.path.abspath(file_path)

        # 安全检查：限制目录层级，防止误删系统文件
        if file_path.count("/") <= 3 or len(file_path.strip()) <= 15:
            msg = f"No permission to remove {file_path}"
            print(msg)
            raise UserWarning(msg)

        if os.path.exists(file_path):
            if os.path.isdir(file_path):
                shutil.rmtree(file_path)
            elif os.path.isfile(file_path):
                os.remove(file_path)
            else:
                print(f"Illegal file: {file_path}")
                return False

        return True

    @staticmethod
    def mk_dirs(dir_path: str, is_file: bool = False) -> None:
        """
        创建目录

        Args:
            dir_path: 目录路径
            is_file: 如果为True，则dir_path被视为文件路径，创建其父目录
        """
        if is_file:
            dir_path = os.path.dirname(dir_path)
        if not os.path.exists(dir_path):
            os.makedirs(dir_path)

    @staticmethod
    def rebuild_file_path(file_path: str) -> str:
        """
        规范化文件路径，移除多余的斜杠

        Args:
            file_path: 文件路径

        Returns:
            规范化后的路径
        """
        if file_path is None:
            return ""

        temp_path = str(file_path).strip()
        if FileUtil.is_empty(temp_path):
            return ""

        while "//" in temp_path:
            temp_path = temp_path.replace("//", "/")

        return temp_path

    @staticmethod
    def get_file_content(file_path: str) -> str:
        """
        读取文件全部内容

        Args:
            file_path: 文件路径

        Returns:
            文件内容字符串
        """
        if file_path is None:
            return ""
        if not os.path.exists(file_path) or not os.path.isfile(file_path):
            return ""

        with open(file_path, 'r', encoding='utf-8') as f:
            return f.read()

    @staticmethod
    def get_dir_md5(file_path: str) -> str:
        """
        计算目录的MD5值（基于所有文件的MD5）

        Args:
            file_path: 目录路径

        Returns:
            目录的MD5哈希值
        """
        file_path = FileUtil.clean_path(file_path)
        file_list = FileUtil.get_file_list(file_path, child=True)

        md5_list = []
        for file_item in file_list:
            if file_item.startswith(".") or file_item.startswith("~"):
                continue
            file_md5 = FileUtil.get_file_md5(file_item)
            md5_list.append(file_md5)

        md5_list = sorted(md5_list)
        md5_str = "#".join(md5_list)
        return md5(md5_str.encode("utf-8")).hexdigest()

    @staticmethod
    def update_local_md5(file_path: str, local_file_info: str = "./local_file_info.csv") -> None:
        """
        更新本地MD5缓存

        Args:
            file_path: 文件或目录路径
            local_file_info: 缓存文件路径
        """
        FileUtil.remove_local_md5(file_path, local_file_info)
        FileUtil.add_local_md5(file_path, local_file_info)

    @staticmethod
    def add_local_md5(file_path: str, local_file_info: str = "./local_file_info.csv") -> None:
        """
        添加文件MD5到本地缓存

        Args:
            file_path: 文件或目录路径
            local_file_info: 缓存文件路径
        """
        if not os.path.exists(local_file_info):
            cmd_touch = f"touch {local_file_info}"
            os.popen(cmd_touch).readlines()

        file_path = FileUtil.clean_path(file_path)
        file_key = f"KEY_{md5(file_path.encode('utf-8')).hexdigest()}"
        file_md5 = FileUtil.get_dir_md5(file_path)
        data_time = time.time()

        cmd_add = f"echo {file_key}###{file_md5}###{data_time}###{file_path} >> {local_file_info}"
        os.popen(cmd_add).readlines()

    @staticmethod
    def remove_local_md5(file_path: str, local_file_info: str = "./local_file_info.csv") -> None:
        """
        从本地缓存中移除文件MD5

        Args:
            file_path: 文件或目录路径
            local_file_info: 缓存文件路径
        """
        file_path = FileUtil.clean_path(file_path)
        file_key = f"KEY_{md5(file_path.encode('utf-8')).hexdigest()}"

        cmd_remove = f"sed -i -e '/{file_key}/d' {local_file_info}"
        os.popen(cmd_remove).readlines()

    @staticmethod
    def get_local_md5(
        file_path: str,
        local_file_info: str = "./local_file_info.csv",
        timeout: int = 24 * 60 * 60
    ) -> Optional[str]:
        """
        从本地缓存获取文件MD5

        Args:
            file_path: 文件或目录路径
            local_file_info: 缓存文件路径
            timeout: 缓存超时时间（秒）

        Returns:
            MD5值，缓存不存在或过期时返回None
        """
        if not os.path.exists(local_file_info):
            return None

        file_path = FileUtil.clean_path(file_path)
        file_key = f"KEY_{md5(file_path.encode('utf-8')).hexdigest()}"

        cmd_grep = f"grep '{file_key}' {local_file_info}"
        ret_grep = os.popen(cmd_grep).readlines()

        if ret_grep is None or len(ret_grep) != 1:
            return None

        parts = str(ret_grep[0]).replace("\n", "").split("###")
        if len(parts) != 4:
            return None

        _, file_md5, file_time, _ = parts
        if time.time() - float(file_time) > timeout:
            FileUtil.remove_local_md5(file_path, local_file_info)
            return None

        return file_md5

    @staticmethod
    def check_local_md5(
        file_path: str,
        local_file_info: str = "./local_file_info.csv",
        timeout: int = 24 * 60 * 60
    ) -> bool:
        """
        检查文件MD5是否与本地缓存匹配

        Args:
            file_path: 文件或目录路径
            local_file_info: 缓存文件路径
            timeout: 缓存超时时间（秒）

        Returns:
            MD5匹配返回True
        """
        if not os.path.exists(local_file_info):
            return False

        file_path = FileUtil.clean_path(file_path)
        file_md5 = FileUtil.get_dir_md5(file_path)
        local_md5 = FileUtil.get_local_md5(file_path, local_file_info, timeout)

        return local_md5 is not None and local_md5 == file_md5

    @staticmethod
    def clean_path(file_path: str) -> str:
        """
        清理和规范化文件路径

        Args:
            file_path: 文件路径

        Returns:
            规范化后的绝对路径
        """
        file_path = str(os.path.abspath(file_path)).strip()
        while "//" in file_path:
            file_path = file_path.replace("//", "/")
        if file_path.endswith("/"):
            file_path = file_path[:-1]
        return file_path


if __name__ == "__main__":
    test_file = "./http_client.py"
    print(f"File exists: {os.path.isfile(test_file)}")

    test_file_not_exist = "./http_client_1.py"
    print(f"File exists: {os.path.isfile(test_file_not_exist)}")
