"""
通用工具模块

提供常用的工具方法，包括：
- 字符串处理（MD5、清理、简化等）
- 日期时间处理（计算、格式化、区间等）
- 数字处理（解析、格式化）
- JSON/字典操作
- 环境变量读取
"""
import os
import ast
import json
import time
import datetime
from hashlib import md5
from typing import Optional, List, Dict, Any, Union

import arrow


class Xutil:
    """
    通用工具类

    提供静态方法进行常用的数据处理操作。

    Example:
        >>> Xutil.md5_str("hello")
        '5d41402abc4b2a76b9719d911017c592'
        >>> Xutil.get_date_current()
        '2024-01-01 12:00:00'
    """

    # ==================== 字符串处理 ====================

    @staticmethod
    def md5_str(str_input: Any, case: str = "LOWER") -> str:
        """
        计算字符串的MD5值

        Args:
            str_input: 输入字符串
            case: 结果大小写（LOWER/UPPER）

        Returns:
            MD5哈希字符串
        """
        ret = md5(str(str_input).strip().encode("utf-8")).hexdigest()
        if case == "UPPER":
            return ret.upper()
        elif case == "LOWER":
            return ret.lower()
        return ret

    @staticmethod
    def clean_str(str_input: Any, case: str = "LOWER") -> str:
        """
        清除字符串中的空格

        Args:
            str_input: 输入字符串
            case: 结果大小写（LOWER/UPPER/其他保持原样）

        Returns:
            去空格后的字符串
        """
        ret = str_input if str_input is not None else ""
        ret = str(ret).replace(" ", "").strip()
        if case == "UPPER":
            return ret.upper()
        elif case == "LOWER":
            return ret.lower()
        return ret

    @staticmethod
    def simple_str(str_input: Any) -> str:
        """
        简化字符串：移除换行、首尾空格，合并相邻空格

        Args:
            str_input: 输入字符串

        Returns:
            简化后的字符串
        """
        if str_input is None:
            return ""
        str_simple = str(str_input).replace("\n", " ").strip()
        while "  " in str_simple:
            str_simple = str_simple.replace("  ", " ")
        return str_simple

    @staticmethod
    def link_words(*words, link_str: str = " | ") -> str:
        """
        连接多个词语

        Args:
            *words: 要连接的词语
            link_str: 连接符

        Returns:
            连接后的字符串
        """
        word_list = [str(item) for item in words]
        return link_str.join(word_list)

    @staticmethod
    def split(str_input: str, link_str: str = " | ") -> List[str]:
        """
        按分隔符分割字符串

        Args:
            str_input: 输入字符串
            link_str: 分隔符

        Returns:
            分割后的列表
        """
        return str_input.split(link_str)

    @staticmethod
    def contains_chinese(str_input: Any) -> bool:
        """
        判断字符串是否包含中文

        Args:
            str_input: 输入字符串

        Returns:
            是否包含中文
        """
        if str_input and isinstance(str_input, str):
            for char in str_input:
                if '\u4e00' <= char <= '\u9fa5':
                    return True
        return False

    @staticmethod
    def recode_str(str_input: str, code_ori: str = "latin1", code_new: str = "gbk") -> str:
        """
        重新编码字符串（解决中文乱码问题）

        Args:
            str_input: 输入字符串
            code_ori: 原始编码
            code_new: 目标编码

        Returns:
            重新编码后的字符串
        """
        result = None
        try:
            result = str_input.encode(code_ori, 'ignore').decode(code_new, 'ignore')
        except UnicodeEncodeError as error:
            print(f"Failed to recode string: {str_input} | {error}")

        if result is None or result.strip() == "":
            result = str_input
        return result

    # ==================== 日期时间处理 ====================

    @staticmethod
    def count_years(date1: str, date2: str) -> float:
        """
        计算两个日期之间的年数

        Args:
            date1: 日期1
            date2: 日期2

        Returns:
            相差的年数
        """
        days = Xutil.count_days(date1, date2)
        return abs(days) / 365

    @staticmethod
    def count_days(begin_date: str, end_date: str) -> int:
        """
        计算两个日期之间的天数

        Args:
            begin_date: 开始日期
            end_date: 结束日期

        Returns:
            相差的天数
        """
        date_format = "%Y%m%d"
        date1 = str(begin_date).replace("-", "")[0:8]
        date2 = str(end_date).replace("-", "")[0:8]
        try:
            date1 = datetime.datetime.strptime(date1, date_format)
            date2 = datetime.datetime.strptime(date2, date_format)
        except ValueError:
            print("日期格式不正确，请输入正确的 YYYYMMDD 格式日期。")
            return 0
        delta = date2 - date1
        return delta.days

    @staticmethod
    def format_date(
        date_input: str,
        format_input: str = "%m %d %Y",
        format_output: str = "%Y-%m-%d"
    ) -> str:
        """
        格式化日期字符串

        Args:
            date_input: 输入日期
            format_input: 输入格式
            format_output: 输出格式

        Returns:
            格式化后的日期字符串
        """
        _date = datetime.datetime.strptime(str(date_input).strip(), format_input)
        return _date.strftime(format_output)

    @staticmethod
    def format_datetime(datetime_input: Any, format_output: str = "YYYY-MM-DD HH:mm:ss") -> str:
        """
        格式化日期时间

        Args:
            datetime_input: 输入日期时间
            format_output: 输出格式（arrow格式）

        Returns:
            格式化后的日期时间字符串
        """
        if not datetime_input:
            return ""
        return arrow.get(datetime_input).format(format_output)

    @staticmethod
    def format_timestamp(timestamp: int, format_output: str = "%Y-%m-%d") -> str:
        """
        格式化时间戳

        Args:
            timestamp: 时间戳
            format_output: 输出格式

        Returns:
            格式化后的日期字符串
        """
        timestamp = time.localtime(int(timestamp))
        return time.strftime(format_output, timestamp)

    @staticmethod
    def date2timestamp(date_str: str) -> int:
        """
        日期字符串转时间戳

        Args:
            date_str: 日期字符串（YYYYMMDD或YYYY-MM-DD）

        Returns:
            时间戳
        """
        date_str = date_str.replace("-", "")[0:8]
        date_obj = datetime.datetime.strptime(date_str, '%Y%m%d')
        return int(date_obj.timestamp())

    @staticmethod
    def get_date_difference(date_begin: str, date_end: str) -> int:
        """
        计算两个日期相隔天数

        Args:
            date_begin: 开始日期
            date_end: 结束日期

        Returns:
            相差天数
        """
        date_begin = date_begin.replace("-", "").replace("/", "")[0:8]
        date_end = date_end.replace("-", "").replace("/", "")[0:8]

        date_begin = time.strptime(date_begin, "%Y%m%d")
        date_end = time.strptime(date_end, "%Y%m%d")

        date_begin = datetime.datetime(date_begin[0], date_begin[1], date_begin[2])
        date_end = datetime.datetime(date_end[0], date_end[1], date_end[2])

        return (date_end - date_begin).days

    @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 get_date_before(
        date_input: Optional[str] = None,
        before_count: int = 1,
        date_format: str = "%Y-%m-%d"
    ) -> str:
        """
        获取指定日期的前N天

        Args:
            date_input: 指定日期，None取当天
            before_count: 前几天（正数前移，负数后移）
            date_format: 日期格式

        Returns:
            计算后的日期字符串
        """
        if date_input is None:
            date_base = datetime.datetime.now()
        else:
            date_base = datetime.datetime.strptime(date_input, date_format)

        offset = datetime.timedelta(days=-before_count)
        return (date_base + offset).strftime(date_format)

    @staticmethod
    def get_date_next(
        date_input: Optional[str] = None,
        next_count: int = 1,
        date_format: str = "%Y-%m-%d"
    ) -> str:
        """
        获取指定日期的后N天

        Args:
            date_input: 指定日期，None取当天
            next_count: 后几天
            date_format: 日期格式

        Returns:
            计算后的日期字符串
        """
        if date_input is None:
            date_base = datetime.datetime.now()
        else:
            date_base = datetime.datetime.strptime(date_input, date_format)

        offset = datetime.timedelta(days=next_count)
        return (date_base + offset).strftime(date_format)

    @staticmethod
    def get_date_between(
        date_begin: str,
        date_end: str,
        date_format: str = "%Y-%m-%d"
    ) -> List[str]:
        """
        获取日期区间内的所有日期

        Args:
            date_begin: 开始日期
            date_end: 结束日期
            date_format: 日期格式

        Returns:
            日期列表
        """
        if Xutil.is_empty(date_end):
            date_end = date_begin

        date_list = []
        date_begin = datetime.datetime.strptime(date_begin, date_format)
        date_end = datetime.datetime.strptime(date_end, date_format)
        while date_begin <= date_end:
            date_list.append(date_begin.strftime(date_format))
            date_begin += datetime.timedelta(days=1)
        return date_list

    @staticmethod
    def get_month_last_day(month_input: str) -> str:
        """
        获取指定月份的最后一天

        Args:
            month_input: 月份（YYYY-MM）

        Returns:
            该月最后一天的日期
        """
        if len(str(month_input)) == 6:
            month_input = f"{month_input[0:4]}-{month_input[4:6]}"
        month_next = Xutil.get_month_next(month_input)
        date_temp = f"{month_next}-01"
        return Xutil.get_date_before(date_temp)

    @staticmethod
    def get_month_date_first_and_last(
        month_input: str,
        date_format: str = "%Y-%m-%d"
    ) -> tuple:
        """
        获取指定月份的第一天和最后一天

        Args:
            month_input: 月份
            date_format: 日期格式

        Returns:
            (第一天, 最后一天)
        """
        month_input = month_input.replace("-", "")
        year = month_input[0:4]
        month = month_input[4:6]
        return Xutil.get_month_date(year, month, date_format)

    @staticmethod
    def get_month_date(year: Any, month: Any, date_format: str = "%Y-%m-%d") -> tuple:
        """
        获取指定年月的第一天和最后一天

        Args:
            year: 年份
            month: 月份
            date_format: 日期格式

        Returns:
            (第一天, 最后一天)
        """
        year = int(year)
        month = int(month)

        date_first = datetime.datetime(year, month, 1).strftime(date_format)
        if month == 12:
            date_last = datetime.datetime(year, month, 31).strftime(date_format)
        else:
            date_last = (datetime.datetime(year, month + 1, 1) - datetime.timedelta(days=1)).strftime(date_format)
        return date_first, date_last

    @staticmethod
    def get_month_date_list(month_input: str, date_format: str = "%Y-%m-%d") -> List[str]:
        """
        获取指定月份的所有日期

        Args:
            month_input: 月份
            date_format: 日期格式

        Returns:
            日期列表
        """
        date_first, date_last = Xutil.get_month_date_first_and_last(month_input, date_format)
        return Xutil.get_date_between(date_first, date_last, date_format)

    @staticmethod
    def get_month_before(
        month_input: Optional[str] = None,
        before_count: int = 1,
        month_format: str = "YYYY-MM"
    ) -> str:
        """
        获取指定月份的前N月

        Args:
            month_input: 指定月份，None取当月
            before_count: 前几月
            month_format: 月份格式

        Returns:
            计算后的月份
        """
        if not month_input:
            month_input = arrow.now().format(month_format)

        a = arrow.get(month_input, month_format)
        b = a.shift(months=-before_count)
        return b.format(month_format)

    @staticmethod
    def get_month_between(
        month_begin: str,
        month_end: str,
        month_format: str = "YYYY-MM"
    ) -> List[str]:
        """
        获取月份区间内的所有月份

        Args:
            month_begin: 开始月份
            month_end: 结束月份
            month_format: 月份格式

        Returns:
            月份列表
        """
        month_list = []
        while month_begin <= month_end:
            month_list.append(month_begin)
            a = arrow.get(month_begin, month_format)
            b = a.shift(months=1)
            month_begin = b.format(month_format)
        return month_list

    @staticmethod
    def get_month_next(
        month_input: Optional[str] = None,
        next_count: int = 1,
        month_format: str = "YYYY-MM"
    ) -> str:
        """
        获取指定月份的后N月

        Args:
            month_input: 指定月份，None取当月
            next_count: 后几月
            month_format: 月份格式

        Returns:
            计算后的月份
        """
        if not month_input:
            month_input = arrow.now().format(month_format)

        a = arrow.get(month_input, month_format)
        b = a.shift(months=next_count)
        return b.format(month_format)

    @staticmethod
    def get_quarter_last_date(data_date: str) -> str:
        """
        获取指定日期所在季度的最后一天

        Args:
            data_date: 日期（YYYY-MM-DD）

        Returns:
            季度最后一天
        """
        date = datetime.datetime.strptime(data_date, '%Y-%m-%d')
        year = date.year
        month = date.month

        if month <= 3:
            last_day = datetime.datetime(year, 3, 31)
        elif month <= 6:
            last_day = datetime.datetime(year, 6, 30)
        elif month <= 9:
            last_day = datetime.datetime(year, 9, 30)
        else:
            last_day = datetime.datetime(year, 12, 31)

        return last_day.strftime('%Y-%m-%d')

    @staticmethod
    def get_quarter_month_list(year: Any) -> List[str]:
        """
        获取指定年份的季度末月份列表

        Args:
            year: 年份

        Returns:
            季度末月份列表
        """
        year = str(year).strip()
        return [f"{year}-{month}" for month in ["03", "06", "09", "12"]]

    @staticmethod
    def get_quarter_date_list(year: Any) -> List[str]:
        """
        获取指定年份的季度末日期列表

        Args:
            year: 年份

        Returns:
            季度末日期列表
        """
        month_list = Xutil.get_quarter_month_list(year)
        return [Xutil.get_month_last_day(month) for month in month_list]

    @staticmethod
    def get_quarter_date_list_in_latest_years(
        year_count: int = 20,
        year_end: Optional[int] = None
    ) -> List[str]:
        """
        获取最近N年的季度末日期列表

        Args:
            year_count: 年数
            year_end: 结束年份

        Returns:
            季度末日期列表（降序）
        """
        date_list = []
        if year_end and year_end > 0:
            year_curr = int(year_end)
        else:
            year_curr = int(Xutil.get_date_before()[0:4])

        for i in range(year_count):
            year = year_curr - i
            date_list.extend(Xutil.get_quarter_date_list(year))

        date_current = Xutil.get_date_before()
        date_list = [item for item in date_list if item < date_current]
        date_list.sort(reverse=True)
        return date_list

    @staticmethod
    def get_quarter_date_list_in_years(
        year: Optional[int] = None,
        year_count: int = 3
    ) -> List[str]:
        """
        获取指定年份开始的N年季度末日期列表

        Args:
            year: 起始年份
            year_count: 年数

        Returns:
            季度末日期列表（降序）
        """
        date_list = []
        if year and year > 0:
            year_curr = int(year)
        else:
            year_curr = int(Xutil.get_date_before()[0:4])

        for i in range(year_count):
            y = year_curr - i
            date_list.extend(Xutil.get_quarter_date_list(y))

        date_current = Xutil.get_date_before()
        date_list = [item for item in date_list if item < date_current]
        date_list.sort(reverse=True)
        return date_list

    @staticmethod
    def get_default_sett_month() -> str:
        """获取默认结算月份（4天前所在月份）"""
        return Xutil.get_date_before(before_count=4)[0:7]

    @staticmethod
    def is_valid_date(date_input: str, date_format: str = "%Y-%m-%d") -> bool:
        """
        判断是否为有效日期

        Args:
            date_input: 日期字符串
            date_format: 日期格式

        Returns:
            是否有效
        """
        try:
            time.strptime(date_input, date_format)
            return True
        except (TypeError, ValueError):
            return False

    @staticmethod
    def is_invalid_date(date_input: str, date_format: str = "%Y-%m-%d") -> bool:
        """
        判断是否为无效日期

        Args:
            date_input: 日期字符串
            date_format: 日期格式

        Returns:
            是否无效
        """
        return not Xutil.is_valid_date(date_input, date_format)

    # ==================== 数字处理 ====================

    @staticmethod
    def format_float(num_input: Any, accuracy: int = 4) -> str:
        """
        格式化浮点数精度

        Args:
            num_input: 输入数字
            accuracy: 小数位数

        Returns:
            格式化后的字符串
        """
        if num_input in [None, "", "-"]:
            num_input = 0
        temp = str(num_input).replace(",", "").replace(" ", "")
        if "%" in temp:
            temp = str(float(temp.replace("%", "")) / 100)
        return f"%.{accuracy}f" % Xutil.parse_to_float(temp)

    @staticmethod
    def parse_to_int(num_input: Any, ignore_err: bool = True) -> int:
        """
        解析为整数

        Args:
            num_input: 输入值
            ignore_err: 是否忽略错误

        Returns:
            整数值
        """
        result = 0
        if Xutil.is_empty(num_input):
            return result
        try:
            result = int(float(num_input))
        except Exception as err:
            if not ignore_err:
                raise err
        return result

    @staticmethod
    def parse_to_float(num_input: Any, ignore_error: bool = True) -> float:
        """
        解析为浮点数

        Args:
            num_input: 输入值
            ignore_error: 是否忽略错误

        Returns:
            浮点数值
        """
        result = 0.0
        try:
            result = float(num_input)
        except Exception as err:
            if not ignore_error:
                raise err
        return result

    # ==================== JSON/字典操作 ====================

    @staticmethod
    def parse_to_json(str_input: str) -> Dict:
        """
        字符串转JSON对象

        Args:
            str_input: JSON字符串

        Returns:
            解析后的字典
        """
        try:
            res: dict = json.loads(str_input)
        except Exception:
            res: dict = ast.literal_eval(str_input)
        return res

    @staticmethod
    def parse_to_dict(
        input_str: str,
        split_outer: str = "&",
        split_inner: str = "="
    ) -> Dict[str, str]:
        """
        解析键值对字符串为字典

        Args:
            input_str: 输入字符串（如k1=v1&k2=v2）
            split_outer: 外层分隔符
            split_inner: 内层分隔符

        Returns:
            解析后的字典
        """
        if not input_str:
            return {}

        result_dict = {}
        for item in input_str.split(split_outer):
            idx = item.find(split_inner)
            if idx > 0:
                key = item[0:idx]
                value = item[idx + 1:]
                result_dict[key] = value
        return result_dict

    @staticmethod
    def get_from_dict(input_dict: Dict, key: str, default: Any = '') -> Any:
        """
        从字典安全获取值

        Args:
            input_dict: 输入字典
            key: 键名
            default: 默认值

        Returns:
            键对应的值或默认值
        """
        if not input_dict or key not in input_dict:
            return default
        result = input_dict[key]
        return default if result is None else result

    @staticmethod
    def get_float_from_dict(input_dict: Dict, key: str, default: float = 0.0) -> float:
        """
        从字典获取浮点数

        Args:
            input_dict: 输入字典
            key: 键名
            default: 默认值

        Returns:
            浮点数值
        """
        if Xutil.is_empty(input_dict) or key not in input_dict:
            return default
        value = input_dict[key]
        if Xutil.is_empty(value):
            return default
        return float(value)

    @staticmethod
    def get_int_from_dict(input_dict: Dict, key: str, default: int = 0) -> int:
        """
        从字典获取整数

        Args:
            input_dict: 输入字典
            key: 键名
            default: 默认值

        Returns:
            整数值
        """
        return int(Xutil.get_float_from_dict(input_dict, key, float(default)))

    # ==================== 通用判断 ====================

    @staticmethod
    def is_empty(value: Any) -> bool:
        """
        判断值是否为空

        Args:
            value: 输入值

        Returns:
            是否为空
        """
        if value is None:
            return True
        if str(value).strip() == "":
            return True
        if isinstance(value, (set, list, dict, tuple)) and len(value) == 0:
            return True
        return False

    @staticmethod
    def is_not_empty(value: Any) -> bool:
        """
        判断值是否非空

        Args:
            value: 输入值

        Returns:
            是否非空
        """
        return not Xutil.is_empty(value)

    @staticmethod
    def get_env_param(key: str) -> str:
        """
        获取环境变量

        Args:
            key: 环境变量名

        Returns:
            环境变量值
        """
        res = ""
        try:
            res = os.environ.get(key) or ""
        except Exception as err:
            print(err)
        return res


if __name__ == '__main__':
    print(Xutil.format_float(1, 4))
