# 导入必要模块
import _thread  # 用于创建多线程，处理异步任务（如发送邮件）
import logging  # 日志记录模块，记录信号处理过程中的关键信息和错误

import django.dispatch  # Django信号系统，用于定义和发送自定义信号
from django.conf import settings  # 导入Django项目配置
from django.contrib.admin.models import LogEntry  # 管理员操作日志模型
from django.contrib.auth.signals import user_logged_in, user_logged_out  # Django内置的用户登录/登出信号
from django.core.mail import EmailMultiAlternatives  # 用于发送HTML格式的邮件
from django.db.models.signals import post_save  # Django模型保存后的信号
from django.dispatch import receiver  # 用于注册信号接收器

# 导入项目内部模块
from comments.models import Comment  # 评论模型
from comments.utils import send_comment_email  # 发送评论通知邮件的工具函数
from djangoblog.spider_notify import SpiderNotify  # 搜索引擎推送工具类
from djangoblog.utils import (  # 项目工具函数
    cache,  # 缓存操作对象
    expire_view_cache,  # 清除视图缓存
    delete_sidebar_cache,  # 清除侧边栏缓存
    delete_view_cache  # 清除指定视图缓存
)
from djangoblog.utils import get_current_site  # 获取当前站点信息
from oauth.models import OAuthUser  # OAuth用户模型

# 创建当前模块的日志记录器，用于记录信号处理相关日志
logger = logging.getLogger(__name__)

# 定义自定义信号：OAuth用户登录信号
# 触发时机：当用户通过OAuth（第三方登录）成功登录时
# 参数：['id'] 表示信号会携带OAuthUser的id
oauth_user_login_signal = django.dispatch.Signal(['id'])

# 定义自定义信号：发送邮件信号
# 触发时机：需要发送邮件时（解耦邮件发送逻辑，便于多处调用）
# 参数：['emailto', 'title', 'content'] 分别表示收件人、邮件标题、邮件内容
send_email_signal = django.dispatch.Signal(['emailto', 'title', 'content'])


@receiver(send_email_signal)
def send_email_signal_handler(sender, **kwargs):
    """
    发送邮件信号的接收器，处理实际的邮件发送逻辑
    
    :param sender: 信号发送者（通常无需关注）
    :param kwargs: 信号携带的参数（emailto, title, content）
    """
    # 从信号参数中提取邮件信息
    emailto = kwargs['emailto']  # 收件人列表
    title = kwargs['title']      # 邮件标题
    content = kwargs['content']  # 邮件内容（HTML格式）

    # 创建HTML格式邮件对象
    # from_email：发件人（从项目配置中获取）
    # to：收件人列表
    msg = EmailMultiAlternatives(
        title,
        content,
        from_email=settings.DEFAULT_FROM_EMAIL,
        to=emailto
    )
    msg.content_subtype = "html"  # 声明邮件内容为HTML格式

    # 记录邮件发送日志（存入数据库）
    from servermanager.models import EmailSendLog  # 导入邮件发送日志模型
    log = EmailSendLog()
    log.title = title
    log.content = content
    log.emailto = ','.join(emailto)  # 将收件人列表转为字符串存储

    try:
        # 发送邮件，返回成功发送的数量
        result = msg.send()
        log.send_result = result > 0  # 若成功发送数量>0，标记为发送成功
    except Exception as e:
        # 发送失败时记录错误日志
        logger.error(f"失败邮箱号: {emailto}, {e}")
        log.send_result = False  # 标记为发送失败
    log.save()  # 保存日志记录到数据库


@receiver(oauth_user_login_signal)
def oauth_user_login_signal_handler(sender, **kwargs):
    """
    OAuth用户登录信号的接收器，处理第三方登录后的后续操作
    
    :param sender: 信号发送者
    :param kwargs: 信号携带的参数（id：OAuthUser的id）
    """
    id = kwargs['id']  # 获取OAuth用户ID
    oauthuser = OAuthUser.objects.get(id=id)  # 查询对应的OAuth用户对象

    # 获取当前站点域名（用于判断头像是否为本站资源）
    site = get_current_site().domain

    # 若用户头像存在且不是本站资源（如第三方平台的头像URL），则下载并保存到本地
    if oauthuser.picture and not oauthuser.picture.find(site) >= 0:
        from djangoblog.utils import save_user_avatar  # 导入保存用户头像的工具函数
        oauthuser.picture = save_user_avatar(oauthuser.picture)  # 下载并替换头像URL为本地路径
        oauthuser.save()  # 保存更新后的用户信息

    # 清除侧边栏缓存（用户登录状态可能影响侧边栏展示内容）
    delete_sidebar_cache()


@receiver(post_save)
def model_post_save_callback(
        sender,
        instance,
        created,
        raw,
        using,
        update_fields,
        **kwargs):
    """
    Django模型保存后（post_save）信号的接收器，处理模型保存后的联动操作
    
    :param sender: 发送信号的模型类
    :param instance: 被保存的模型实例
    :param created: 是否为新创建的记录（True表示新建，False表示更新）
    :param raw: 是否为原始保存（如通过loaddata导入数据时为True）
    :param using: 使用的数据库别名
    :param update_fields: 被更新的字段列表（None表示全量更新）
    :param kwargs: 其他参数
    """
    clearcache = False  # 标记是否需要清除缓存

    # 若保存的是管理员操作日志（LogEntry），直接返回（无需处理）
    if isinstance(instance, LogEntry):
        return

    # 若模型实例有get_full_url方法（通常表示是可被搜索引擎收录的内容，如文章）
    if 'get_full_url' in dir(instance):
        # 判断是否仅更新了浏览量字段（views）
        is_update_views = update_fields == {'views'}

        # 非测试环境且不是仅更新浏览量时，通知搜索引擎更新内容
        if not settings.TESTING and not is_update_views:
            try:
                notify_url = instance.get_full_url()  # 获取内容的完整URL
                SpiderNotify.baidu_notify([notify_url])  # 向百度搜索引擎推送更新
            except Exception as ex:
                logger.error("notify spider", ex)  # 推送失败时记录错误日志

        # 若不是仅更新浏览量，标记需要清除缓存
        if not is_update_views:
            clearcache = True

    # 若保存的是评论（Comment）实例
    if isinstance(instance, Comment):
        # 仅处理已启用的评论（is_enable=True）
        if instance.is_enable:
            # 获取评论所属文章的URL路径
            path = instance.article.get_absolute_url()
            # 获取当前站点域名
            site = get_current_site().domain

            # 处理域名中的端口（若有），仅保留主域名
            if site.find(':') > 0:
                site = site[0:site.find(':')]

            # 清除文章详情页的视图缓存（评论更新后页面内容需同步更新）
            expire_view_cache(
                path,
                servername=site,
                serverport=80,
                key_prefix='blogdetail'
            )

            # 清除SEO处理器缓存（评论可能影响页面SEO信息）
            if cache.get('seo_processor'):
                cache.delete('seo_processor')

            # 清除该文章的评论缓存
            comment_cache_key = 'article_comments_{id}'.format(id=instance.article.id)
            cache.delete(comment_cache_key)

            # 清除侧边栏缓存（侧边栏可能包含最新评论等动态内容）
            delete_sidebar_cache()

            # 清除评论列表视图的缓存
            delete_view_cache('article_comments', [str(instance.article.pk)])

            # 启动新线程异步发送评论通知邮件（避免阻塞主请求）
            _thread.start_new_thread(send_comment_email, (instance,))

    # 若标记需要清除缓存，则执行缓存清除
    if clearcache:
        cache.clear()


@receiver(user_logged_in)  # 注册为用户登录信号的接收器
@receiver(user_logged_out)  # 同时注册为用户登出信号的接收器
def user_auth_callback(sender, request, user, **kwargs):
    """
    用户登录/登出信号的接收器，处理认证状态变化后的操作
    
    :param sender: 信号发送者
    :param request: HTTP请求对象
    :param user: 当前用户对象
    :param kwargs: 其他参数
    """
    # 若用户存在且用户名有效
    if user and user.username:
        logger.info(user)  # 记录用户登录/登出日志
        delete_sidebar_cache()  # 清除侧边栏缓存（登录状态可能影响侧边栏内容，如显示用户名）
        # cache.clear()  # 注释：可根据需求开启全量缓存清除（通常侧边栏缓存足够）