#!/usr/bin/env python
# encoding: utf-8

"""
djangoblog 工具函数模块（utils.py）

提供博客系统所需的通用工具函数，包括缓存、安全、Markdown解析、邮件发送、文件处理等功能。
"""

# 导入标准库和第三方库
import logging
import os
import random
import string
import uuid
from hashlib import sha256

import bleach  # 用于清理 HTML 标签，防止 XSS 攻击
import markdown  # 用于将 Markdown 文本转换为 HTML
import requests  # 用于发起 HTTP 请求
from django.conf import settings
from django.contrib.sites.models import Site  # Django 的站点框架
from django.core.cache import cache  # 使用 Django 缓存后端
from django.templatetags.static import static  # 用于生成静态文件 URL

# 获取当前模块的日志记录器
logger = logging.getLogger(__name__)


def get_max_articleid_commentid():
    """
    获取当前数据库中最新（主键最大）的文章和评论的 ID。

    返回:
        tuple: (最新文章的主键, 最新评论的主键)
    """
    from ..blog.models import Article
    from ..comments.models import Comment
    return (Article.objects.latest().pk, Comment.objects.latest().pk)

#wwc
def get_sha256(str):
    """
    计算输入字符串的 SHA256 哈希值。

    参数:
        str (str): 要哈希的字符串

    返回:
        str: SHA256 哈希值的十六进制字符串表示
    """
    m = sha256(str.encode('utf-8'))
    return m.hexdigest()

#wwc
def cache_decorator(expiration=3 * 60):
    """
    缓存装饰器，用于为函数添加缓存功能，避免重复计算或数据库查询。

    参数:
        expiration (int): 缓存过期时间（秒），默认 3 分钟

    返回:
        decorator: 可用于装饰函数的装饰器

    工作原理:
        1. 尝试从视图对象获取缓存键（通过 get_cache_key 方法）
        2. 如果没有，则基于函数名和参数生成唯一哈希作为键
        3. 查询缓存，命中则返回缓存值
        4. 未命中则执行原函数，将结果存入缓存并返回
        5. 特殊处理返回值为 None 的情况，避免缓存穿透
    """
#wwc
    def wrapper(func):
        def news(*args, **kwargs):
            try:
                view = args[0]
                key = view.get_cache_key()
            except:
                key = None
            if not key:
                unique_str = repr((func, args, kwargs))
                m = sha256(unique_str.encode('utf-8'))
                key = m.hexdigest()
            value = cache.get(key)
            if value is not None:
                if str(value) == '__default_cache_value__':
                    return None
                else:
                    return value
            else:
                logger.debug(
                    'cache_decorator set cache:%s key:%s' %
                    (func.__name__, key))
                value = func(*args, **kwargs)
                if value is None:
                    cache.set(key, '__default_cache_value__', expiration)
                else:
                    cache.set(key, value, expiration)
                return value

        return news

    return wrapper

#wwc
def expire_view_cache(path, servername, serverport, key_prefix=None):
    """
    手动清除特定视图的缓存（用于内容更新后刷新缓存）。

    参数:
        path (str): URL 路径（如 '/article/1/'）
        servername (str): 服务器域名
        serverport (str): 服务器端口
        key_prefix (str, optional): 缓存键前缀

    返回:
        bool: 缓存是否成功删除

    实现:
        1. 构造一个模拟的 HttpRequest 对象
        2. 使用 Django 的 get_cache_key 工具生成与视图缓存对应的键
        3. 如果键存在且缓存中有值，则删除该缓存
    """
    from django.http import HttpRequest
    from django.utils.cache import get_cache_key

    request = HttpRequest()
    request.META = {'SERVER_NAME': servername, 'SERVER_PORT': serverport}
    request.path = path

    key = get_cache_key(request, key_prefix=key_prefix, cache=cache)
    if key:
        logger.info('expire_view_cache:get key:{path}'.format(path=path))
        if cache.get(key):
            cache.delete(key)
        return True
    return False

#wwc
@cache_decorator()
def get_current_site():
    """
    获取当前 Django 站点对象，并使用缓存优化性能。

    返回:
        Site: 当前站点实例（包含域名、名称等信息）
    """
    site = Site.objects.get_current()
    return site

#wwc
class CommonMarkdown:
    """
    提供统一的 Markdown 解析功能，支持代码高亮、目录生成等。
    """
#wwc
    @staticmethod
    def _convert_markdown(value):
        """
        内部方法：将 Markdown 字符串转换为 HTML，并提取目录（TOC）。

        参数:
            value (str): Markdown 格式的文本

        返回:
            tuple: (HTML 内容字符串, 目录 HTML 字符串)
        """
        md = markdown.Markdown(
            extensions=[
                'extra',  # 标准扩展（表格、脚注等）
                'codehilite',  # 代码高亮
                'toc',  # 自动生成目录
                'tables',  # 表格支持
            ]
        )
        body = md.convert(value)
        toc = md.toc
        return body, toc
#wwc
    @staticmethod
    def get_markdown_with_toc(value):
        """
        解析 Markdown 文本，同时返回 HTML 内容和目录。

        参数:
            value (str): Markdown 文本

        返回:
            tuple: (HTML 内容, TOC 目录)
        """
        body, toc = CommonMarkdown._convert_markdown(value)
        return body, toc
#wwc
    @staticmethod
    def get_markdown(value):
        """
        仅解析 Markdown 文本为 HTML 内容，不返回目录。

        参数:
            value (str): Markdown 文本

        返回:
            str: 转换后的 HTML 字符串
        """
        body, toc = CommonMarkdown._convert_markdown(value)
        return body

#wwc
def send_email(emailto, title, content):
    """
    发送邮件的快捷方法，通过 Django 信号机制解耦。

    参数:
        emailto (str): 收件人邮箱
        title (str): 邮件标题
        content (str): 邮件正文

    实现:
        触发自定义的 send_email_signal 信号，由信号处理器完成实际的邮件发送逻辑。
    """
    from ..djangoblog.blog_signals import send_email_signal
    send_email_signal.send(
        send_email.__class__,
        emailto=emailto,
        title=title,
        content=content)

#wwc
def generate_code() -> str:
    """
    生成一个 6 位的随机数字验证码。

    返回:
        str: 6 位数字组成的字符串（如 '123456'）
    """
    return ''.join(random.sample(string.digits, 6))

#wwc
def parse_dict_to_url(dict):
    """
    将字典转换为 URL 查询参数字符串（键值对用 & 连接）。

    参数:
        dict (dict): 要转换的字典

    返回:
        str: URL 编码后的查询字符串（如 'key1=value1&key2=value2'）

    注意:
        使用 urllib.parse.quote 对键和值进行 URL 编码，safe='/' 表示斜杠不编码。
    """
    from urllib.parse import quote
    url = '&'.join(['{}={}'.format(quote(k, safe='/'), quote(v, safe='/'))
                    for k, v in dict.items()])
    return url

#wwc
def get_blog_setting():
    """
    获取博客系统设置，优先从缓存读取，未命中则从数据库获取并缓存。

    如果数据库中没有设置记录，则创建一个默认设置并保存。

    返回:
        BlogSettings: 博客设置模型实例
    """
    value = cache.get('get_blog_setting')
    if value:
        return value
    else:
        from ..blog.models import BlogSettings
        if not BlogSettings.objects.count():
            setting = BlogSettings()
            setting.site_name = 'djangoblog'
            setting.site_description = '基于Django的博客系统'
            setting.site_seo_description = '基于Django的博客系统'
            setting.site_keywords = 'Django,Python'
            setting.article_sub_length = 300
            setting.sidebar_article_count = 10
            setting.sidebar_comment_count = 5
            setting.show_google_adsense = False
            setting.open_site_comment = True
            setting.analytics_code = ''
            setting.beian_code = ''
            setting.show_gongan_code = False
            setting.comment_need_review = False
            setting.save()
        value = BlogSettings.objects.first()
        logger.info('set cache get_blog_setting')
        cache.set('get_blog_setting', value)
        return value

#wwc
def save_user_avatar(url):
    """
    从指定 URL 下载用户头像并保存到本地静态文件目录。

    参数:
        url (str): 头像图片的远程 URL

    返回:
        str: 保存后的本地静态文件 URL（如 '/static/avatar/abc123.jpg'）
              下载失败时返回默认头像路径。

    流程:
        1. 创建本地头像存储目录
        2. 下载图片内容
        3. 根据原始 URL 判断文件类型，生成唯一文件名
        4. 保存文件
        5. 返回静态 URL
    """
    logger.info(url)
    try:
        basedir = os.path.join(settings.STATICFILES, 'avatar')
        rsp = requests.get(url, timeout=2)
        if rsp.status_code == 200:
            if not os.path.exists(basedir):
                os.makedirs(basedir)
            image_extensions = ['.jpg', '.png', 'jpeg', '.gif']
            isimage = len([i for i in image_extensions if url.endswith(i)]) > 0
            ext = os.path.splitext(url)[1] if isimage else '.jpg'
            save_filename = str(uuid.uuid4().hex) + ext
            logger.info('保存用户头像:' + basedir + save_filename)
            with open(os.path.join(basedir, save_filename), 'wb+') as file:
                file.write(rsp.content)
            return static('avatar/' + save_filename)
    except Exception as e:
        logger.error(e)
        return static('blog/img/avatar.png')

#wwc
def delete_sidebar_cache():
    """
    清除侧边栏所有缓存。

    侧边栏内容（如最新文章、热门评论）通常会被缓存以提高性能。
    当内容更新时，需调用此函数清除相关缓存。

    实现:
        遍历 LinkShowType 的所有值，删除以 'sidebar' 为前缀的缓存键。
    """
    from ..blog.models import LinkShowType
    keys = ["sidebar" + x for x in LinkShowType.values]
    for k in keys:
        logger.info('delete sidebar key:' + k)
        cache.delete(k)

#wwc
def delete_view_cache(prefix, keys):
    """
    删除基于模板片段缓存（@cache）的缓存。

    参数:
        prefix (str): 缓存片段的名称（与模板中 cache 标签的第一个参数对应）
        keys (list): 缓存的变量列表（用于生成唯一键）

    实现:
        使用 make_template_fragment_key 生成正确的缓存键，然后删除。
    """
    from django.core.cache.utils import make_template_fragment_key
    key = make_template_fragment_key(prefix, keys)
    cache.delete(key)

#wwc
def get_resource_url():
    """
    获取静态资源的基础 URL。

    如果设置了 STATIC_URL，则直接返回。
    否则，构建一个完整的 URL（http://domain/static/）。

    返回:
        str: 静态资源基础 URL
    """
    if settings.STATIC_URL:
        return settings.STATIC_URL
    else:
        site = get_current_site()
        return 'http://' + site.domain + '/static/'


# 定义允许在用户内容中使用的 HTML 标签和属性
# 用于 sanitize_html 函数，防止 XSS 攻击
ALLOWED_TAGS = ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'li', 'ol', 'pre', 'strong', 'ul', 'h1',
                'h2', 'p']
ALLOWED_ATTRIBUTES = {'a': ['href', 'title'], 'abbr': ['title'], 'acronym': ['title']}

#wwc
def sanitize_html(html):
    """
    清理 HTML 内容，移除不安全的标签和属性，防止跨站脚本（XSS）攻击。

    参数:
        html (str): 待清理的 HTML 字符串

    返回:
        str: 清理后的 HTML 字符串，仅包含 ALLOWED_TAGS 和 ALLOWED_ATTRIBUTES 中定义的内容
    """
    return bleach.clean(html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES)