# utils/static_cache.py
import datetime
import os
import re
import threading
from urllib.parse import parse_qs, urlencode

from apps.core.models.static_settings import StaticSettings
from django.conf import settings
from django.core.cache import cache


class StaticCacheManager:
    """基于Django原生缓存的静态内容管理器"""

    def __init__(self, cache_timeout=600, static_timeout=82400):

        self.cache_timeout = cache_timeout
        self.static_timeout = static_timeout

    def _static_dir(self):
        static_path = os.path.join(settings.BASE_DIR,
                                   StaticSettings.get_solo().static_dir)
        return static_path

    def _generate_cache_key(self, filename):
        """生成符合项目规范的缓存键"""

        return filename.replace('/', '_')

    def format_multilevel_path_with_params(self, path, query_params=None):
        """
        处理带有多参数查询字符串的多级URL路径，生成安全且唯一的文件名（或路径标识）。
        
        此方法通过提取URL路径和查询参数，过滤非法文件名字符，构建一个可用于文件操作或缓存键的字符串。
        适用于处理类似 `/api/data/?keywords=XX&category=tech&page=2` 这样的复杂URL场景。

        Args:
            path (str): URL的路径部分，通常来自 `request.path_info`。
                        例如："/api/users/list/", "/search/"。
            query_params (dict|str, optional): 查询参数字典或原始查询字符串。通常来自 `request.GET`。
                        字典示例: {'keywords': 'XX', 'page': 2, 'sort': 'name'}。
                        字符串示例: "keywords=XX&page=2"。
                        默认为None，表示不添加查询参数。

        Returns:
            str: 根据路径和编码后的参数生成的安全字符串。格式为 `路径部分_参数键1=参数值1_参数键2=参数值2...`。
                    例如：输入 path="/api/data/", query_params={'k': 'a/b', 'page': 2}，
                    可能返回 "/api/data/_k=a_b_page=2/"。

        Raises:
            ValueError: 如果 `query_params` 是无法解析的字符串形式。
        """
        # 1. 预处理路径：确保路径格式统一，以单斜杠开头和结尾
        clean_path = path.strip().rstrip('/') + '/'
        # 进一步清理路径中的多个连续斜杠（如果有）
        clean_path = re.sub(r'/+', '/', clean_path)

        # 2. 准备参数字符串部分
        params_str = ""
        if query_params is not None:
            # 2.1 解析参数（如果传入的是字符串，如 "a=1&b=2"）
            if isinstance(query_params, str):
                # 使用urllib.parse.parse_qs解析查询字符串，注意这会处理重复键和URL解码
                try:
                    params_dict = parse_qs(query_params,
                                           keep_blank_values=True,
                                           strict_parsing=True)
                    # parse_qs返回列表值，因为我们知道没有重复键（或者我们不关心），这里简化处理，取第一个值
                    # 更健壮的做法是处理多值，但根据常见用法，此处简化为一维字典
                    simplified_params = {}
                    for k, v in params_dict.items():
                        if isinstance(v, list) and len(v) > 0:
                            simplified_params[k] = v[0]  # 取第一个值，或根据需求连接所有值
                        else:
                            simplified_params[k] = v
                    params_to_encode = simplified_params
                except ValueError:
                    raise ValueError(f"无法解析的查询字符串: {query_params}")
            elif isinstance(query_params, dict):
                params_to_encode = query_params
            else:
                raise TypeError("query_params 必须是字典或字符串")

            # 2.2 过滤参数键和值中的非法文件名字符 (Windows/Linux常见保留字和不允许字符)
            # 定义非法字符的正则表达式模式 (包括空格、引号、斜杠、冒号、问号、星号、尖括号、竖线等)
            # 注意：保留字符如 \ / : * ? " < > | 以及空格、换行等控制字符，以及可能引起Shell注入的字符如 ` $ ; !等
            illegal_file_chars_pattern = r'[\\/*?:"<>|\s`$\';!]'
            safe_params = {}
            for key, value in params_to_encode.items():
                # 确保键和值为字符串后进行过滤
                safe_key = re.sub(illegal_file_chars_pattern, '_', str(key))
                safe_value = re.sub(illegal_file_chars_pattern, '_',
                                    str(value))
                safe_params[safe_key] = safe_value

            # 2.3 将参数字典编码为字符串 (例如：'key1=value1_key2=value2')
            if safe_params:
                # 使用urlencode，但替换&为_以适应文件名，同时不进行额外的URL编码（因为我们已经替换了非法字符）
                encoded_params = urlencode(safe_params,
                                           safe='',
                                           quote_via=lambda s, safe: s)
                params_str = "_" + encoded_params.replace('&',
                                                          '_')  # 用下划线连接不同键值对

        # 3. 构建最终的新路径字符串
        # 格式：/清理后的路径/_(安全参数字符串)/
        # 如果参数为空，则new_path就是clean_path
        if params_str:
            # 确保参数部分插入在路径最后一个斜杠之前
            # 例如：/api/data/ + _k=v => /api/data_k=v/
            new_path = clean_path.rstrip('/') + params_str + '/'
        else:
            new_path = clean_path

        return new_path

    # 示例1: 基本使用，类似原方法
    # path = "/api/data/"
    # params_dict = {"page": 2}
    # result = obj.format_multilevel_path_with_params(path, params_dict)
    # print(result)  # 输出: /api/data/_page=2/

    # # 示例2: 多个参数
    # path = "/search/"
    # params_complex = {"keywords": "python web", "category": "dev", "sort": "name", "page": 2}
    # result = obj.format_multilevel_path_with_params(path, params_complex)
    # print(result)  # 输出: /search/_keywords=python_web_category=dev_sort=name_page=2/
    # # 注意空格被替换为下划线

    # # 示例3: 参数中包含非法文件名字符
    # path = "/download/"
    # params_illegal = {"file*name": "my:data.txt", "type": "a/b"}
    # result = obj.format_multilevel_path_with_params(path, params_illegal)
    # print(result)  # 输出: /download/_file_name=my_data.txt_type=a_b/
    # # 注意 *, :, / 被替换为下划线

    # # 示例4: 传入查询字符串
    # path = "/api/data/"
    # query_string = "keywords=test&page=1&filter=a=b"
    # result = obj.format_multilevel_path_with_params(path, query_string)
    # print(result)  # 输出: /api/data/_keywords=test_page=1_filter=a_b/
    # # 注意等号(=)和与号(&)在参数字符串中是允许的，但&被替换为_，=保留

    # # 示例5: 无参数
    # path = "/static/css/style.css"
    # result = obj.format_multilevel_path_with_params(path)
    # print(result)  # 输出: /static/css/style.css/

    def ensure_html_extension(self, filename):
        '''
        用于重构静态文件，包括静态文件名适用的格式为“/”,“/index.html”,“/a/”,“/a.html”,"/a/b/c.html"
        如果为`/a/b/c/d/?page=2`这种形式，主用该类format_multilevel_path_with_page方法重构后传入get或create方法中
        '''
        root, _ = os.path.splitext(filename)
        if root == '/':
            root = '/index'
        elif root.endswith('/'):  # 如果路径以 / 结尾，移除末尾的 /
            root = root[:-1]
        return f"{root}.html"

    def get(self, filename):
        """获取缓存内容,判断有没有，无值则读取静态\n可通过request.path_info直接得到文件名"""
        filename = self.ensure_html_extension(filename)
        static_file = f'{self._static_dir()}{filename}'
        content = ''
        cache_key = self._generate_cache_key(filename)

        if not settings.DEBUG and self.cache_timeout > 0:

            content = cache.get(cache_key)

        if content:
            return content
        #验证文件静态时间

        try:
            file_mtime = os.path.getmtime(static_file)
            age = (
                datetime.datetime.now() -
                datetime.datetime.fromtimestamp(file_mtime)).total_seconds()
        except FileNotFoundError:
            age = 0

        if not settings.DEBUG and os.path.exists(static_file) and (
                self.static_timeout == 999 or age < self.static_timeout):

            with open(static_file, "r", encoding='utf-8',
                      errors='replace') as f:
                content = f.read()
                if content:
                    self._async_cache_set(cache_key, content)
                return content
        else:
            return None

    def create(self, filename, content):
        filename = self.ensure_html_extension(filename)
        static_file = f'{self._static_dir()}{filename}'
        cache_key = self._generate_cache_key(filename)
        """异步缓存写入（多进程实现）"""

        os.makedirs(os.path.dirname(static_file), exist_ok=True)
        threading.Thread(target=self._save_content,
                         args=(static_file, content),
                         daemon=True).start()
        if self.cache_timeout > 0:
            self._async_cache_set(cache_key, content)
        return True

    def _async_cache_set(self, cache_key, content):
        """增强版缓存写入"""
        try:

            # 限制过大内容
            if len(content) > 1024 * 1024:  # 1MB限制
                raise ValueError("Content too large")

            cache.set(cache_key, content, timeout=self.cache_timeout)

        except UnicodeEncodeError:
            print("Invalid UTF-8 content")
        except ValueError as ve:
            print(f"Validation error: {str(ve)}")
        except Exception as e:
            print(f"Critical error: {str(e)}")

    def _save_content(self, file_path, content):
        """静态文件更新的保存方法（使用原子替换）"""
        # 1. 将内容先写入一个临时文件
        temp_file = f"{file_path}.tmp"
        try:

            with open(temp_file, 'w', encoding='utf-8', errors='replace') as f:
                f.write(content)

            # 2. 使用os.replace进行原子替换
            os.replace(temp_file, file_path)

        except OSError as e:
            print(f"_save_content Error:文件保存失败 {file_path}: {str(e)}")
            # 清理可能残留的临时文件
            if os.path.exists(temp_file):
                try:
                    os.remove(temp_file)
                except OSError:
                    pass
            raise

    def _remove_file(self, filename):
        get_filename = filename
        filename = self.ensure_html_extension(filename)
        static_file = f'{self._static_dir()}{filename}'
        try:
            os.remove(static_file)

            return f'{filename} 移除成功'
        except FileNotFoundError as e:
            return f'系统不存在该静态文件，可能早已移除，不要重复操作。'

    def _remove_static_dir(self):
        import shutil
        try:
            shutil.rmtree(self._static_dir())
            return f"文件夹 '{StaticSettings.get_solo().static_dir}' 及其内容已删除"
        except FileNotFoundError:

            return f'文件夹“{StaticSettings.get_solo().static_dir}” 移除成功'
