# core/templatetags/theme_tags.py
import random
import re
from datetime import timezone
from itertools import chain

from apps.ads.models.ad_content import Advertisement
from apps.ads.models.delivery_rule import AdDeliveryRule
from apps.content.models import (Article, Category, Download, ImageGallery,
                                 Tag, Wiki)
from apps.core.models.global_dns import GlobalDNSConfig
from apps.core.models.sensitive_config import SensitiveConfig
from apps.core.models.url_style import UrlStyle
from apps.core.utils.sensitive_filter import SensitiveFilter
from apps.links.models import LinkGroup
from django import template
from django.core.cache import cache
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.db import models
from django.db.models import Q
from django.http import Http404
from django.template.defaultfilters import stringfilter
from django.templatetags.static import static
from django.urls import resolve
from django.utils.html import strip_tags
from django.utils.safestring import mark_safe

register = template.Library()


@register.simple_tag
def theme_static(path, theme="default"):
    """动态加载主题静态文件的标签"""
    return static(f"{theme}/static/{path}")


def resolve_category_param(param):
    """统一处理分类参数并优先返回对象
    参数支持：Category对象/ID/ID列表/空值
    返回：Category对象/None/ID列表
    """
    if isinstance(param, Category):
        return param
    elif param is None:
        return None
    elif hasattr(param, '__iter__') and not isinstance(param, str):
        return [int(c) for c in param if str(c).isdigit()]
    elif str(param).isdigit():
        try:
            return Category.objects.get(pk=int(param))
        except Category.DoesNotExist:
            return None
    return None


FIELD_WHITELIST = {
    'title', 'meta_description', 'create_time', 'update_time', 'pk', 'hits',
    'category', 'author', 'thumb_image'
}


@register.simple_tag
def cate_list(
        category=None,
        value='title,meta_description,update_time,pk',
        status=99,
        order_by='-update_time',
        shownum=0,
        include_children=True):
    """
    增强版分类内容列表模板标签
    :param category: 分类节点对象
    :param value: 返回字段(逗号分隔)
    :param status: 状态筛选(支持多状态逗号分隔)
    :param order_by: 排序字段(逗号分隔)
    :param shownum: 返回数量限制(0表示不限制)
    :param include_children: 是否包含子节点内容
    """
    if not category or not hasattr(category, 'content_type'):
        return []

    # 获取目标分类列表
    target_categories = [category]
    if include_children:
        target_categories.extend(category.get_descendants())

    querysets = []
    for cat in target_categories:
        model_class = cat.content_type.model_class()

        # 字段过滤
        valid_fields = [f.strip() for f in value.split(',')
                        if f.strip() in FIELD_WHITELIST]
        valid_fields += ['category__name', 'category__slug', 'category__id']

        # 状态处理
        status_choices = {choice.value for choice in getattr(
            model_class, 'StatusChoices', [])}

        status_list = []
        for s in str(status).split(','):
            if s == '99' and hasattr(model_class.StatusChoices, 'REVIEW'):
                status_list.append(model_class.StatusChoices.REVIEW.value)
            elif s.isdigit():
                status_list.append(int(s))

        valid_status = [s for s in status_list if s in status_choices] or \
            [model_class.StatusChoices.REVIEW.value]

        # 构建查询
        queryset = model_class.objects.filter(
            status__in=valid_status,
            category=cat
        ).select_related('category')

        if valid_fields:
            queryset = queryset.only(*valid_fields)
        querysets.append(queryset)

    # 合并并排序结果
    combined = list(chain(*querysets))
    if order_by:
        order_fields = [f.strip() for f in order_by.split(',')
                        if f.strip().lstrip('-') in FIELD_WHITELIST]
        if order_fields:
            reverse = order_fields[0].startswith('-')
            order_attr = order_fields[0].lstrip('-')
            combined.sort(key=lambda x: getattr(x, order_attr, 0),
                          reverse=reverse)

    return combined[:shownum] if shownum > 0 else combined


# 获取草稿和归档文章，按点击量排序
# {% article_cate_list status="1,6" order_by="-hits" as special_articles %}

# 获取指定分类的热门文章
# {% article_cate_list category=3 value="title,thumb_image,hits" order_by="-hits" %}
# 获取文章列表 #
# {% article_cate_list category=3 as articles %}

# # 智能分页处理 #}
# {% smart_paginator articles as page_obj %}

# # 内容渲染 #}
# <ul>
#   {% for article in page_obj %}
#     <li>{{ article.title }}</li>
#   {% endfor %}
# </ul>

# # 条件分页导航 #}
# {% if page_obj|need_pagination %}
#   {% include "widgets/pagination.html" %}
# {% endif %}

def get_status_filter(status, all_status_flag=100, default_status=99):
    """
    通用状态过滤条件生成器

    Args:
        status: 支持以下形式：
            - 等于all_status_flag值时表示所有状态
            - 列表/元组时表示多状态筛选
            - 其他情况视为单个状态
        all_status_flag: 表示所有状态的标志值(默认100)
        default_status: 默认状态值(当status无效时使用)

    Returns:
        dict: 生成的状态过滤条件字典
    """
    if status == all_status_flag:  # 所有状态
        return {}
    if isinstance(status, (list, tuple)):  # 多状态筛选
        return {'status__in': [int(s) for s in status if str(s).isdigit()]}

    # 单个状态处理
    status = int(status) if str(status).isdigit() else default_status
    return {'status': status}


@register.simple_tag
def article_cate_list(
        category=None,
        fields='title,meta_description,update_time,pk',
        status=99,
        order_by='-update_time',
        shownum=0,
        get_children=True):
    """
    分类文章查询模板标签

    Args:
        category: 分类筛选条件，支持以下形式：
            - None: 不限制分类（默认）
            - Category对象: 直接使用该分类
            - 数字ID: 自动查询对应分类
            - ID列表: 批量查询多个分类
        fields: 返回字段白名单（逗号分隔），可选值包括：
            - title: 文章标题
            - meta_description: 摘要描述
            - create_time: 创建时间
            - update_time: 更新时间（默认包含）
            - pk: 主键ID（默认包含）
            - hits: 点击量
            - category: 分类对象
            - author: 作者信息
            - thumb_image: 缩略图
        status: 文章状态筛选：支持多状态选择
            - 99: 已发布（默认）
            - 100: 所有状态
            - 其他所有内容状态
        order_by: 排序字段（支持多字段逗号分隔），例如：
            - '-update_time': 按更新时间倒序（默认）
            - 'hits,-create_time': 先按点击量正序，再按创建时间倒序
        shownum: 结果数量限制：
            - 0: 返回全部结果（默认）
            - >0: 返回指定数量的记录
        get_children: 是否包含子分类文章：
            - True: 包含所有子分类文章（默认）
            - False: 仅查询当前分类

    Returns:
        QuerySet: 包含筛选结果的文章查询集
    """

    # 字段处理
    valid_fields = [f.strip() for f in fields.split(',')
                    if f.strip() in FIELD_WHITELIST]

    # 状态过滤
    status_filter = get_status_filter(status)

    # 分类处理
    category_param = resolve_category_param(category)
    if not category_param:  # 无分类限制
        queryset = Article.objects.filter(**status_filter)
    elif isinstance(category_param, list):  # ID列表查询
        queryset = Article.objects.filter(
            category_id__in=category_param, **status_filter)
    elif not get_children:  # 单节点查询（优先使用article_set）
        queryset = category_param.article_set.filter(**status_filter)
    else:  # 包含子分类查询
        descendants = category_param.get_descendants(include_self=True)
        queryset = Article.objects.filter(
            category__in=descendants, **status_filter)
    if 'category' not in valid_fields:
        valid_fields.append('category')
    # 结果处理
    if valid_fields:
        queryset = queryset.only(*valid_fields)
    queryset = queryset.select_related('category')
    # 优化随机排序性能
    if order_by == '?':
        if shownum > 0:
            # 使用更高效的随机抽样方法
            category_id = category.id if category else 0
            # 根据实际情况构建更复杂的key，包含可能影响数量的参数
            cache_key = f"category_{category_id}_article_count_status_{status}"
            count = cache.get(cache_key)

            if count is None:
                count = queryset.count()
                cache.set(cache_key, count, 3600*2)  # 缓存1小时，过期时间可根据业务调整
            if count > 10000:  # 大数据量时采用分片随机
                # 将获取到的ID列表缓存起来（例如缓存5-10分钟）
                # 这样可以避免在缓存有效期内频繁执行昂贵的全ID查询和传输
                cache_random_ids_key = f"category_{category_id}_article_ids_status_{status}"
                random_ids = cache.get(cache_random_ids_key)
                if random_ids is None:
                    random_ids = list(queryset.values_list(
                        'pk', flat=True))  # 第1次网络传输
                    cache.set(cache_random_ids_key,
                              random_ids, 3600*2)  # 缓存1小时
                random_ids = random.sample(random_ids, min(shownum, count))
                queryset = Article.objects.filter(pk__in=random_ids)
            else:  # 小数据量直接使用order_by
                queryset = queryset.order_by('?')[:shownum]
        else:
            # 无数量限制时不建议使用随机排序
            queryset = queryset.order_by('-update_time')
    elif order_by:
        queryset = queryset.order_by(*[f.strip() for f in order_by.split(',')])

    return queryset[:shownum] if shownum > 0 else queryset


@register.simple_tag
def download_cate_list(
    category=None,
        fields='title,meta_description,update_time,pk',
        status=99,
        order_by='-update_time',
        shownum=0,
        get_children=True):
    """
    分类文章查询模板标签

    Args:
        category: 分类筛选条件，支持以下形式：
            - None: 不限制分类（默认）
            - Category对象: 直接使用该分类
            - 数字ID: 自动查询对应分类
            - ID列表: 批量查询多个分类
        fields: 返回字段白名单（逗号分隔），可选值包括：
            - title: 文章标题
            - meta_description: 摘要描述
            - create_time: 创建时间
            - update_time: 更新时间（默认包含）
            - pk: 主键ID（默认包含）
            - hits: 点击量
            - category: 分类对象
            - author: 作者信息
            - thumb_image: 缩略图
        status: 文章状态筛选：支持多状态选择
            - 99: 已发布（默认）
            - 100: 所有状态
            - 其他所有内容状态
        order_by: 排序字段（支持多字段逗号分隔），例如：
            - '-update_time': 按更新时间倒序（默认）
            - 'hits,-create_time': 先按点击量正序，再按创建时间倒序
        shownum: 结果数量限制：
            - 0: 返回全部结果（默认）
            - >0: 返回指定数量的记录
        get_children: 是否包含子分类文章：
            - True: 包含所有子分类文章（默认）
            - False: 仅查询当前分类

    Returns:
        QuerySet: 包含筛选结果的文章查询集
    """

    # 字段处理
    valid_fields = [f.strip() for f in fields.split(',')
                    if f.strip() in FIELD_WHITELIST]

    # 状态过滤
    status_filter = get_status_filter(status)

    # 分类处理
    category_param = resolve_category_param(category)
    if not category_param:  # 无分类限制
        queryset = Download.objects.filter(**status_filter)
    elif isinstance(category_param, list):  # ID列表查询
        queryset = Download.objects.filter(
            category_id__in=category_param, **status_filter)
    elif not get_children:  # 单节点查询（优先使用article_set）
        queryset = category_param.download_set.filter(**status_filter)
    else:  # 包含子分类查询
        descendants = category_param.get_descendants(include_self=True)
        queryset = Download.objects.filter(
            category__in=descendants, **status_filter)

    # 结果处理
    if valid_fields:
        queryset = queryset.only(*valid_fields)
    if order_by:
        queryset = queryset.order_by(*[f.strip() for f in order_by.split(',')])

    return queryset[:shownum] if shownum > 0 else queryset


@register.simple_tag
def image_cate_list(
        category=None,
        fields='title,meta_description,update_time,pk',
        status=99,
        order_by='-update_time',
        shownum=0,
        get_children=True):
    """
    分类文章查询模板标签

    Args:
        category: 分类筛选条件，支持以下形式：
            - None: 不限制分类（默认）
            - Category对象: 直接使用该分类
            - 数字ID: 自动查询对应分类
            - ID列表: 批量查询多个分类
        fields: 返回字段白名单（逗号分隔），可选值包括：
            - title: 文章标题
            - meta_description: 摘要描述
            - create_time: 创建时间
            - update_time: 更新时间（默认包含）
            - pk: 主键ID（默认包含）
            - hits: 点击量
            - category: 分类对象
            - author: 作者信息
            - thumb_image: 缩略图
        status: 文章状态筛选：支持多状态选择
            - 99: 已发布（默认）
            - 100: 所有状态
            - 其他所有内容状态
        order_by: 排序字段（支持多字段逗号分隔），例如：
            - '-update_time': 按更新时间倒序（默认）
            - 'hits,-create_time': 先按点击量正序，再按创建时间倒序
        shownum: 结果数量限制：
            - 0: 返回全部结果（默认）
            - >0: 返回指定数量的记录
        get_children: 是否包含子分类文章：
            - True: 包含所有子分类文章（默认）
            - False: 仅查询当前分类

    Returns:
        QuerySet: 包含筛选结果的文章查询集
    """

    # 字段处理
    valid_fields = [f.strip() for f in fields.split(',')
                    if f.strip() in FIELD_WHITELIST]

    # 状态过滤
    status_filter = get_status_filter(status)

    # 分类处理
    category_param = resolve_category_param(category)
    if not category_param:  # 无分类限制
        queryset = ImageGallery.objects.filter(**status_filter)
    elif isinstance(category_param, list):  # ID列表查询
        queryset = ImageGallery.objects.filter(
            category_id__in=category_param, **status_filter)
    elif not get_children:  # 单节点查询（优先使用article_set）
        queryset = category_param.imagegallery_set.filter(**status_filter)
    else:  # 包含子分类查询
        descendants = category_param.get_descendants(include_self=True)
        queryset = ImageGallery.objects.filter(
            category__in=descendants, **status_filter)

    # 结果处理
    if valid_fields:
        queryset = queryset.only(*valid_fields)
    if order_by:
        queryset = queryset.order_by(*[f.strip() for f in order_by.split(',')])

    return queryset[:shownum] if shownum > 0 else queryset


@register.simple_tag
def wiki_cate_list(
        category=None,
        fields='title,meta_description,update_time,pk',
        status=99,
        order_by='-update_time',
        shownum=0,
        get_children=True):
    """
    分类文章查询模板标签

    Args:
        category: 分类筛选条件，支持以下形式：
            - None: 不限制分类（默认）
            - Category对象: 直接使用该分类
            - 数字ID: 自动查询对应分类
            - ID列表: 批量查询多个分类
        fields: 返回字段白名单（逗号分隔），可选值包括：
            - title: 文章标题
            - meta_description: 摘要描述
            - create_time: 创建时间
            - update_time: 更新时间（默认包含）
            - pk: 主键ID（默认包含）
            - hits: 点击量
            - category: 分类对象
            - author: 作者信息
            - thumb_image: 缩略图
        status: 文章状态筛选：支持多状态选择
            - 99: 已发布（默认）
            - 100: 所有状态
            - 其他所有内容状态
        order_by: 排序字段（支持多字段逗号分隔），例如：
            - '-update_time': 按更新时间倒序（默认）
            - 'hits,-create_time': 先按点击量正序，再按创建时间倒序
        shownum: 结果数量限制：
            - 0: 返回全部结果（默认）
            - >0: 返回指定数量的记录
        get_children: 是否包含子分类文章：
            - True: 包含所有子分类文章（默认）
            - False: 仅查询当前分类

    Returns:
        QuerySet: 包含筛选结果的文章查询集
    """

    # 字段处理
    valid_fields = [f.strip() for f in fields.split(',')
                    if f.strip() in FIELD_WHITELIST]

    # 状态过滤
    status_filter = get_status_filter(status)

    # 分类处理
    category_param = resolve_category_param(category)
    if not category_param:  # 无分类限制
        queryset = Wiki.objects.filter(**status_filter)
    elif isinstance(category_param, list):  # ID列表查询
        queryset = Wiki.objects.filter(
            category_id__in=category_param, **status_filter)
    elif not get_children:  # 单节点查询（优先使用article_set）
        queryset = category_param.wiki_set.filter(**status_filter)
    else:  # 包含子分类查询
        descendants = category_param.get_descendants(include_self=True)
        queryset = Wiki.objects.filter(
            category__in=descendants, **status_filter)

    # 结果处理
    if valid_fields:
        queryset = queryset.only(*valid_fields)
    if order_by:
        queryset = queryset.order_by(*[f.strip() for f in order_by.split(',')])

    return queryset[:shownum] if shownum > 0 else queryset


@register.simple_tag(takes_context=True)
def smart_paginator(context, object_list, per_page=10):
    """
    智能分页处理器
    :param object_list: 待分页数据集
    :param per_page: 每页条目数 (默认10)
    :return: 分页后的Page对象
    """
    request = context['request']
    paginator = Paginator(object_list, per_page)
    # 智能解析页码参数
    try:
        page_number = _get_page_number(request)
        page_obj = paginator.get_page(page_number)
    except (PageNotAnInteger, EmptyPage) as e:
        raise Http404(f"无效页码: {str(e)}")
    except Exception as e:
        raise Http404(f"分页错误: {str(e)}")

    return page_obj


def _get_page_number(request):
    """统一页码获取逻辑"""
    # 优先从GET参数获取
    if page := request.GET.get('page'):
        return int(page)

    # 备用方案：解析URL路径
    current_url = resolve(request.path_info)
    url_name = current_url.url_name
    # 处理类似 /articles/page_2/ 的SEO友好格式
    if url_name.endswith('_page'):
        return int(current_url.kwargs.get('page', 1))
    if 'pagenum' in current_url.route:
        try:
            current_url.kwargs.get('pagenum', 1)
            if current_url.kwargs.get('pagenum', 1):
                return int(current_url.kwargs.get('pagenum', 1))
        except Exception as e:
            print(e)
            return 1

    return 1


@register.simple_tag(takes_context=True)
def modify_url(context, **kwargs):
    """
    生成带分页参数的URL
    :example {% modify_url page=2 %}
    """
    request = context['request']
    params = request.GET.copy()
    urlconfig = UrlStyle.get_solo()

    for key, value in kwargs.items():
        if value is None:
            if key in params:
                del params[key]
        else:
            params[key] = value
    url_suffix = ''
    url_end = ''
    if not urlconfig.url_suffix == '':
        # 当后缀不空时，只有首页是/结尾的，我们的定义为index.html
        if request.path.endswith(urlconfig.url_suffix):
            if re.match(rf'^(.*)_\d+{urlconfig.url_suffix}$', request.path):
                return re.sub(rf'^(.*)_\d+{urlconfig.url_suffix}$', rf'\1_{params["page"]}{urlconfig.url_suffix}', request.path)
            else:
                return re.sub(rf'^(.*){urlconfig.url_suffix}$', rf'\1_{params["page"]}{urlconfig.url_suffix}', request.path)
        else:
            return f'/index_{params["page"]}{urlconfig.url_suffix}'  
    else:
        if re.match(rf'^(.*)_\d+$', request.path):
            return re.sub(rf'^(.*)_\d+$', rf'\1_{params["page"]}', request.path)
        elif request.path.endswith('/'):
            return f'{request.path}index_{params["page"]}'
        else:
            return f'{request.path}_{params["page"]}'
    return f'{request.path}?{params.urlencode()}'


@register.filter
def visible_pages(page_obj, window=3):
    """生成带省略号的页码范围"""
    num_pages = page_obj.paginator.num_pages
    current = page_obj.number
    return range(max(1, current - window),
                 min(num_pages + 1, current + window + 1))


@register.filter
def need_pagination(page_obj, min_pages=1):
    """判断是否需要显示分页组件"""
    return page_obj.paginator.num_pages > min_pages


@register.simple_tag
def get_tag_list(
    group: str = None,
    status: int = 99,
    order_by: str = '-order,-usage_count',
    limit: int = 10,
    include_empty: bool = True,
    count_cache: int = 43200  # 新增参数，默认12小时（43200秒）。None或False表示不缓存，正数表示缓存秒数，-1可表示永久缓存。
):
    """
    优化版标签获取函数，支持高效随机抽样
    新增特性：
    - 使用数据库原生随机排序(MySQL RAND()/PostgreSQL RANDOM())
    - 添加索引提示优化查询
    - 支持分片抽样提高大表性能
    - count_cache参数，根据其取值来决定是否缓存计数 (count) 以及缓存的超时时间。count_cache = None或 False不启用计数缓存。count_cache = 0: 缓存计数，但立即过期（通常意义不大，可归为不缓存）。count_cache > 0: 缓存计数，超时时间为 count_cache秒。count_cache < 0: 通常表示特殊含义，例如 -1可代表永久缓存（timeout=None），但需谨慎使用。
    """
    # 还有一种逻辑是不需要考虑抽样的阀值，直接使用最大ID进行切片提取
    THRESHOLD = 500000  # 触发分片抽样的数据量阈值
    qs = Tag.objects.all()

    # 构建过滤条件
    filters = Q()
    if not include_empty:
        filters &= Q(usage_count__gt=0)

    # 特殊处理随机排序情况
    if order_by == '?':
        # 使用缓存计数避免频繁COUNT查询
        # 根据 count_cache 参数决定是否使用缓存计数
        count_cache_key = f"tag_count_{group}_{status}_{include_empty}"

        if count_cache is None or count_cache is False:
            # 情况1: 不启用计数缓存，直接查询数据库
            count = qs.filter(filters).count()
        else:
            # 情况2 & 3 & 4: 启用计数缓存（包括永久缓存）
            # 先尝试从缓存获取
            count = cache.get(count_cache_key)
            if count is None:
                count = qs.filter(filters).count()
                # 根据 count_cache 的值设置缓存超时
                if count_cache == -1:
                    # 永久缓存，直到手动清除或缓存服务重启
                    cache.set(count_cache_key, count, timeout=None)
                elif count_cache > 0:
                    # 缓存指定的秒数
                    cache.set(count_cache_key, count, timeout=count_cache)
                # 如果 count_cache == 0, 通常不缓存或立即过期，这里选择不缓存（等同于情况1）
                # 所以不需要额外的else分支

        if count > THRESHOLD:  # 大数据量采用分片抽样
            max_id = Tag.objects.aggregate(max_id=models.Max("id"))['max_id']
            # 为确保我们随机切片的有效ID满足limit，所我们加入了缓冲系数以确保我们最终的有效ID大于limit
            sample_size = min(limit * 2, 100)  # 例如：缓冲系数为 2，但设置一个上限（如100）
            random_ids = random.sample(range(1, max_id + 1), sample_size)
            qs = qs.filter(id__in=random_ids).order_by('?')
            # sample_size = min(1000, max_id//10)  # 动态计算样本池大小
            # random_ids = random.sample(range(1, max_id+1), sample_size)
            # qs = qs.filter(id__in=random_ids).order_by('?')
        else:  # 小数据量直接使用原生随机
            qs = qs.filter(filters).order_by('?')
    else:
        # 常规排序逻辑
        qs = qs.filter(filters)
        if order_by:
            order_fields = [f.strip() for f in order_by.split(',')]
            valid_fields = {'order', 'usage_count',
                            'create_time', 'update_time'}
            qs = qs.order_by(
                *[f for f in order_fields if f.lstrip('-') in valid_fields])

    return qs[:abs(limit)] if limit else qs


# 格式化列表
@register.filter
def format_list(value, str):
    new_val = str.join(eval(value))
    return new_val


# 以字符分割字符串
@register.filter
def split_str(value, str):
    list = value.split(str)
    return list


# 列表字符转列表
@register.filter
def list_str(value):
    list = eval(value)
    return list


# 截中英文字符
@register.filter
def cut_str(str, length=120):
    """
    截取字符串，使得字符串长度等于length，并在字符串后加上省略号
    """
    is_encode = False
    try:
        str_encode = str.encode("gb18030")  # 为了中文和英文的长度一致（中文按长度2计算）
        is_encode = True
    except:
        pass
    if is_encode:
        l = length * 2
        if l < len(str_encode):
            l = l - 3
            str_encode = str_encode[:l]
            try:
                str = str_encode.decode("gb18030") + "..."
            except:
                str_encode = str_encode[:-1]
                try:
                    str = str_encode.decode("gb18030") + "..."
                except:
                    is_encode = False
    if not is_encode:
        if length < len(str):
            length = length - 2
            return str[:length] + "..."
    return str


@register.filter
def cut_str_by_display_length(s, max_width=120):
    """
    按显示宽度截取字符串（中文算2单位，英文算1单位），并添加省略号
    """
    current_width = 0
    result = []
    for char in s:
        char_width = 2 if ord(char) > 255 else 1
        if current_width + char_width > max_width:
            break
        result.append(char)
        current_width += char_width
    return "".join(result) + ("..." if len(result) < len(s) else "")


@register.filter
def truncate_chars(s, max_length=120):
    """
    按字符数截取字符串（中英文均计1个长度），超出部分显示省略号
    示例："好好学习good good study" 截取 length=7 → "好好学习g..."
    """
    if len(s) > max_length:
        return f"{s[:max_length]}..."
    return s


@register.simple_tag
def ancestorsnode(obj, bool):
    nodelist = obj.get_ancestors(
        include_self=bool)
    return nodelist


@register.inclusion_tag('friend_links/links_list.html')
def show_friend_links(group_alias=None):
    groups = LinkGroup.objects.prefetch_related('friendlink_set').filter(
        friendlink__is_active=True
    ).distinct().order_by('sort_order')

    if group_alias:
        groups = groups.filter(alias=group_alias)

    return {'link_groups': groups}
# 广告管理


@register.inclusion_tag('ads/render_ads.html')
def render_ads(identifier):
    now = timezone.now()
    ads = Advertisement.objects.filter(
        space__identifier=identifier,
        start_time__lte=now,
        end_time__gte=now,
        is_active=True,
        audit_status=True
    ).order_by('-priority')

    # 过滤符合投放规则的广告
    valid_ads = []
    for ad in ads:
        try:
            rule = ad.delivery_rule  # 假设有related_name='delivery_rule'
            if rule.is_valid(now):  # 假设is_valid方法检查时间规则等
                valid_ads.append(ad)
        except AdDeliveryRule.DoesNotExist:
            # 没有投放规则的广告默认显示
            valid_ads.append(ad)

    return {'ads': valid_ads}


@register.filter(name='clean_truncate')
@stringfilter
def clean_truncate(value, arg):
    """
    过滤HTML标签并截断文本的模板过滤器
    :param value: 输入内容
    :param arg: 截取长度
    :return: 处理后的纯文本
    """
    try:
        length = int(arg)
    except ValueError:
        return value

    # 先去除HTML标签
    cleaned = strip_tags(str(value))
    # 去除多余空白字符
    cleaned = ' '.join(cleaned.split())
    # 截断文本并添加省略号
    if len(cleaned) > length:
        return cleaned[:length] + '...'
    return cleaned


@register.filter(name='highlight')
def highlight(text, keys=[]):
    if not text or not keys:
        return text
    # 统一处理为去重后的关键词列表
    keys = list(set([k.strip() for k in keys if k.strip()])
                ) if isinstance(keys, (list, tuple)) else [keys]

    # 按长度降序排序，优先匹配长关键词
    keys.sort(key=len, reverse=True)

    highlighted = set()
    text = str(text)

    for key in keys:
        if not key or key in highlighted:
            continue

        # 处理包含关系（如"淘宝"和"淘宝网"）
        if any(k for k in highlighted if key in k or k in key):
            continue

        pattern = re.compile(re.escape(key), re.IGNORECASE)
        text = pattern.sub(
            lambda m: mark_safe(f'<span class="highlight">{m.group()}</span>'),
            text
        )
        highlighted.add(key)

    return mark_safe(f'{text}')


@register.filter(name='sensitive_filter')
def sensitive_filter(value):
    """
    模板过滤器：对文本进行敏感词过滤
    使用示例：{{ some_text|sensitive_filter }}
    """
    if not value:
        return value

    try:
        config = SensitiveConfig.get_solo()
        if not config.filter_switch:
            return value

        sensitive_filter = SensitiveFilter(config)
        return sensitive_filter.process_text(value)
    except Exception:
        return value


@register.simple_tag(takes_context=True)
def wiki_cate_page(
        context,
        category=None,
        fields='title,meta_description,update_time,pk',
        status=99,
        order_by='-update_time',
        per_page=10,
        get_children=True):

    # 获取基础查询集（原wiki_cate_list逻辑）
    valid_fields = [f.strip() for f in fields.split(',')
                    if f.strip() in FIELD_WHITELIST]
    status_filter = get_status_filter(status)
    category_param = resolve_category_param(category)

    if not category_param:
        queryset = Wiki.objects.filter(**status_filter)
    elif isinstance(category_param, list):
        queryset = Wiki.objects.filter(
            category_id__in=category_param, **status_filter)
    elif not get_children:
        queryset = category_param.wiki_set.filter(**status_filter)
    else:
        descendants = category_param.get_descendants(include_self=True)
        queryset = Wiki.objects.filter(
            category__in=descendants, **status_filter)
    if 'category' not in valid_fields:
        valid_fields.append('category')
    if valid_fields:
        queryset = queryset.only(*valid_fields)
    queryset = queryset.select_related('category')
    if order_by:
        queryset = queryset.order_by(*[f.strip() for f in order_by.split(',')])

    # 分页处理（原smart_paginator逻辑）
    request = context['request']
    paginator = Paginator(queryset, per_page)

    try:
        page_number = _get_page_number(request)
        return paginator.get_page(page_number)
    except (PageNotAnInteger, EmptyPage) as e:
        raise Http404(f"无效页码: {str(e)}")
    except Exception as e:
        raise Http404(f"分页错误: {str(e)}")


@register.simple_tag(takes_context=True)
def article_cate_page(
        context,
        category=None,
        fields='title,meta_description,update_time,pk',
        status=99,
        order_by='-update_time',
        per_page=10,
        get_children=True):

    # 获取基础查询集（原wiki_cate_list逻辑）
    valid_fields = [f.strip() for f in fields.split(',')
                    if f.strip() in FIELD_WHITELIST]
    status_filter = get_status_filter(status)
    category_param = resolve_category_param(category)

    if not category_param:
        queryset = Article.objects.filter(**status_filter)
    elif isinstance(category_param, list):
        queryset = Article.objects.filter(
            category_id__in=category_param, **status_filter)
    elif not get_children:
        queryset = category_param.wiki_set.filter(**status_filter)
    else:
        descendants = category_param.get_descendants(include_self=True)
        queryset = Article.objects.filter(
            category__in=descendants, **status_filter)
    if 'category' not in valid_fields:
        # 结合后面的select_related('category')，将相关的关联字段加进来避免N+1
        valid_fields.append('category')
    if valid_fields:
        queryset = queryset.only(*valid_fields)
    if order_by:
        queryset = queryset.order_by(*[f.strip() for f in order_by.split(',')])
    queryset = queryset.select_related('category')
    # 分页处理（原smart_paginator逻辑）
    request = context['request']
    paginator = Paginator(queryset, per_page)

    try:
        page_number = _get_page_number(request)
        return paginator.get_page(page_number)
    except (PageNotAnInteger, EmptyPage) as e:
        raise Http404(f"无效页码: {str(e)}")
    except Exception as e:
        raise Http404(f"分页错误: {str(e)}")


@register.simple_tag(takes_context=True)
def download_cate_page(
        context,
        category=None,
        fields='title,meta_description,update_time,pk',
        status=99,
        order_by='-update_time',
        per_page=10,
        get_children=True):

    # 获取基础查询集（原wiki_cate_list逻辑）
    valid_fields = [f.strip() for f in fields.split(',')
                    if f.strip() in FIELD_WHITELIST]
    status_filter = get_status_filter(status)
    category_param = resolve_category_param(category)

    if not category_param:
        queryset = Download.objects.filter(**status_filter)
    elif isinstance(category_param, list):
        queryset = Download.objects.filter(
            category_id__in=category_param, **status_filter)
    elif not get_children:
        queryset = category_param.wiki_set.filter(**status_filter)
    else:
        descendants = category_param.get_descendants(include_self=True)
        queryset = Download.objects.filter(
            category__in=descendants, **status_filter)
    if 'category' not in valid_fields:
        valid_fields.append('category')
    if valid_fields:
        queryset = queryset.only(*valid_fields)
    queryset = queryset.select_related('category')
    if order_by:
        queryset = queryset.order_by(*[f.strip() for f in order_by.split(',')])

    # 分页处理（原smart_paginator逻辑）
    request = context['request']
    paginator = Paginator(queryset, per_page)

    try:
        page_number = _get_page_number(request)
        return paginator.get_page(page_number)
    except (PageNotAnInteger, EmptyPage) as e:
        raise Http404(f"无效页码: {str(e)}")
    except Exception as e:
        raise Http404(f"分页错误: {str(e)}")


@register.simple_tag(takes_context=True)
def image_cate_page(
        context,
        category=None,
        fields='title,meta_description,update_time,pk',
        status=99,
        order_by='-update_time',
        per_page=10,
        get_children=True):

    # 获取基础查询集（原wiki_cate_list逻辑）
    valid_fields = [f.strip() for f in fields.split(',')
                    if f.strip() in FIELD_WHITELIST]
    status_filter = get_status_filter(status)
    category_param = resolve_category_param(category)

    if not category_param:
        queryset = ImageGallery.objects.filter(**status_filter)
    elif isinstance(category_param, list):
        queryset = ImageGallery.objects.filter(
            category_id__in=category_param, **status_filter)
    elif not get_children:
        queryset = category_param.wiki_set.filter(**status_filter)
    else:
        descendants = category_param.get_descendants(include_self=True)
        queryset = ImageGallery.objects.filter(
            category__in=descendants, **status_filter)
    if 'category' not in valid_fields:
        valid_fields.append('category')
    if valid_fields:
        queryset = queryset.only(*valid_fields)
    queryset = queryset.select_related('category')
    if order_by:
        queryset = queryset.order_by(*[f.strip() for f in order_by.split(',')])

    # 分页处理（原smart_paginator逻辑）
    request = context['request']
    paginator = Paginator(queryset, per_page)

    try:
        page_number = _get_page_number(request)
        return paginator.get_page(page_number)
    except (PageNotAnInteger, EmptyPage) as e:
        raise Http404(f"无效页码: {str(e)}")
    except Exception as e:
        raise Http404(f"分页错误: {str(e)}")


@register.simple_tag(takes_context=True)
def cate_list_page(
        context,
        category=None,
        value='title,meta_description,update_time,pk',
        status=99,
        order_by='-update_time',
        per_page=10,
        include_children=True):

    # 基础查询逻辑（原cate_list核心功能）
    if not category or not hasattr(category, 'content_type'):
        return []

    target_categories = [category]
    if include_children:
        target_categories.extend(category.get_descendants())

    querysets = []
    for cat in target_categories:
        model_class = cat.content_type.model_class()

        valid_fields = [f.strip() for f in value.split(',')
                        if f.strip() in FIELD_WHITELIST]
        valid_fields += ['category__name', 'category__slug', 'category__id']

        # 状态处理逻辑
        status_choices = {choice.value for choice in getattr(
            model_class, 'StatusChoices', [])}
        status_list = []
        for s in str(status).split(','):
            if s == '99' and hasattr(model_class.StatusChoices, 'REVIEW'):
                status_list.append(model_class.StatusChoices.REVIEW.value)
            elif s.isdigit():
                status_list.append(int(s))

        valid_status = [s for s in status_list if s in status_choices] or \
            [model_class.StatusChoices.REVIEW.value]

        queryset = model_class.objects.filter(
            status__in=valid_status,
            category=cat
        ).select_related('category')

        if valid_fields:
            queryset = queryset.only(*valid_fields)
        querysets.append(queryset)

    # 合并结果集
    combined = list(chain(*querysets))
    if order_by:
        order_fields = [f.strip() for f in order_by.split(',')
                        if f.strip().lstrip('-') in FIELD_WHITELIST]
        if order_fields:
            reverse = order_fields[0].startswith('-')
            order_attr = order_fields[0].lstrip('-')
            combined.sort(key=lambda x: getattr(x, order_attr, 0),
                          reverse=reverse)

    # 分页处理（原smart_paginator核心功能）
    request = context['request']
    paginator = Paginator(combined, per_page)

    try:
        page_number = _get_page_number(request)
        return paginator.get_page(page_number)
    except (PageNotAnInteger, EmptyPage) as e:
        raise Http404(f"无效页码: {str(e)}")
    except Exception as e:
        raise Http404(f"分页错误: {str(e)}")


@register.simple_tag
def get_home_url():
    dns_config = GlobalDNSConfig.get_solo()
    if dns_config.status == 'enabled' and dns_config.primary_domain and dns_config.dns_type == 'content_to_home':
        return f'//{dns_config.primary_domain}'
    else:
        return '/'
@register.filter
@stringfilter
def strip_whitespace(value):
    """
    移除字符串中所有的换行符、制表符和连续的空白符，替换为单个空格。
    """
    # 首先，使用striptags确保去除HTML标签（如果尚未处理）
    # 但通常在模板链中，这个过滤器会在striptags之后使用
    # 移除所有换行符、制表符等，并替换为单个空格
    value = re.sub(r'\s+', ' ', value)
    # 去除字符串首尾可能出现的空格
    return value.strip()