import datetime
import os
import random
import threading
import time

from apps.content.models import Category
from apps.content.models.article import Article
from apps.core.cache.conditional_cache_page import (
    conditional_cache_page,
    dynamic_ratelimit,
)
from apps.core.cache.static_cache import StaticCacheManager
from apps.core.decorators import require_security_code
from apps.core.models.post_set import PostSet
from apps.core.models.sensitive_config import SensitiveConfig
from apps.core.models.site_settings import SiteSettings
from apps.core.models.static_settings import StaticSettings
from apps.core.utils.about_img import nopic
from apps.core.utils.hash import unhash_id
from apps.core.utils.sensitive_filter import SensitiveFilter
from apps.core.views.ai_create import AIcreate
from django.conf import settings
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.db import models, transaction
from django.db.models import F, Q
from django.http import Http404, HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404, render
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _
from django.views import View
from django.views.generic import DetailView
from django.views.static import serve
from rest_framework import serializers, status
from rest_framework.generics import CreateAPIView
from rest_framework.response import Response
from rest_framework.views import APIView

from ..models.article import Article


@method_decorator(dynamic_ratelimit(settings.RATE_LIMIT_ITEM), name="dispatch")
@method_decorator(conditional_cache_page(settings.ITEM_TIMEOUT), name="dispatch")
class ArticleDetailView(DetailView):
    model = Article
    context_object_name = "article"
    slug_field = "slug"
    slug_url_kwarg = "article_slug"

    def get_template_names(self):
        # theme = getattr(self.request, 'current_theme', 'default') template
        theme = settings.THEME_NAME
        # 文章个性模板>栏目内容模板>主题模型内容页templates/articles/article_item.html
        # themes/default/templates/articles/article_item.html是最后的容灾收段
        templates = []
        if self.object.template:
            templates.append(
                self.object.template.replace(f"{settings.BASE_DIR}/templates/themes/", "")
            )
        if self.object.category.content_template:
            templates.append(
                self.object.category.content_template.replace(
                    f"{settings.BASE_DIR}/templates/themes/", ""
                )
            )
        model_name = self.object.category.content_type.model
        templates.extend(
            [
                f"themes/{theme}/templates/item/{model_name}_item.html",
                "themes/default/templates/item/{model_name}_item.html",
            ]
        )

        return templates

    def dispatch(self, request, *args, **kwargs):
        # 获取当前主题和备用模板路径
        theme = getattr(settings, "THEME_NAME", "default")
        templates = [
            f"themes/{theme}/templates/utils/403_restricted.html",
            "themes/default/templates/utils/403_restricted.html",
        ]
        # 先获取对象再校验权限
        self.object = self.get_object()
        if (
            self.object.staff_only or self.object.category.staff_only
        ) and not request.user.is_staff:
            return render(request, templates, {"message": "该内容仅限工作人员查看"}, status=403)
        if (
            self.object.allowed_groups.exists()
            and not self.object.allowed_groups.filter(id__in=request.user.groups.all()).exists()
        ) or (
            self.object.category.allowed_groups.exists()
            and not self.object.category.allowed_groups.filter(
                id__in=request.user.groups.all()
            ).exists()
        ):
            return render(
                request, templates, {"message": "您所在的用户组无权查看此内容"}, status=403
            )
        return super().dispatch(request, *args, **kwargs)

    def get_object(self, **kwargs):
        # 判断路径模式 ?P<slug>
        if "pk" in self.kwargs and "slug" in self.kwargs:
            try:
                int(self.kwargs["pk"])
                return self._slug_pk()
            except:
                return self._slug_handle_hash()

        if "pk" in self.kwargs:
            try:
                int(self.kwargs["pk"])
                return self._pk()
            except:
                return self._handle_hash()

        elif "year" in self.kwargs:
            return self._validate_date_path()
        return super().get_object()

    def _slug_pk(self):
        """pk查询逻辑"""
        obj_id = self.kwargs["pk"]
        return get_object_or_404(
            Article, pk=int(obj_id), status=99, category__slug=self.kwargs["slug"]
        )

    def _slug_handle_hash(self):
        """哈希值查询逻辑"""
        return get_object_or_404(
            Article,
            pk=int(unhash_id(self.kwargs["pk"])),
            status=99,
            category__slug=self.kwargs["slug"],
        )

    def _pk(self):
        """pk查询逻辑"""
        obj_id = self.kwargs["pk"]
        return get_object_or_404(Article, pk=int(obj_id), status=99)

    def _handle_hash(self):
        """哈希值查询逻辑"""
        return get_object_or_404(Article, pk=int(unhash_id(self.kwargs["pk"])), status=99)

    def get(self, request, *args, **kwargs):

        filename = request.path_info
        self.static_config = StaticSettings.get_solo()
        manager = StaticCacheManager(self.static_config.high_safe, self.static_config.static_item)
        self.object = self.get_object()
        # 获取静态内容

        if self.static_config.static_key:

            static_content = manager.get(filename)

            if static_content:
                return HttpResponse(static_content)

            # 执行父类get方法完成常规处理
        response = super().get(request, *args, **kwargs)
        if self.static_config.static_key:
            manager.create(filename, response.render().content.decode("utf-8"))

        return response

    def serve_static(self, path):
        """静态文件服务方法（生产环境需用Nginx处理）"""
        # serve(self.request,文件名,目录路径)
        return serve(self.request, os.path.basename(path), os.path.dirname(path))

    def _validate_date_path(self):
        """日期路径验证"""
        year = self.kwargs["year"]
        month = self.kwargs["month"]
        try:
            datetime(year, month, 1)
        except ValueError:
            raise Http404
        return get_object_or_404(Article, pk=self.kwargs["pk"])

    def get_queryset(self):
        return (
            super()
            .get_queryset()
            .select_related("category", "editor")
            .prefetch_related("related_tags", "related_wikis")
        )

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        obj = self.object
        from apps.core.models import TagPageConfig

        tag_config = TagPageConfig.get_solo()
        if not obj.cut_keywords.exists() and tag_config.word_split_enabled:

            if tag_config.concurrent_update_enabled:

                def update_task():
                    # 在线程内重新获取对象
                    try:
                        obj.update_cut_keywords()
                    except Exception as e:
                        print(f"Error updating cut keywords: {e}")

                thread = threading.Thread(target=update_task)
                thread.daemon = True
                thread.start()
            else:
                obj.update_cut_keywords()
        obj_update_fields = []
        if not obj.previous_obj:
            previous_article = (
                Article.objects.filter(status=99, create_time__lt=obj.create_time)
                .order_by("-create_time")
                .first()
            )
            if previous_article:
                obj.previous_obj = previous_article
                obj_update_fields.append("previous_obj")
                # 同步更新相关文章
                previous_article.next_obj = obj
                previous_article.save(update_fields=["next_obj"])
        if not obj.next_obj:
            next_article = (
                Article.objects.filter(status=99, create_time__gt=obj.create_time)
                .order_by("create_time")
                .first()
            )
            if next_article:
                obj.next_obj = next_article
                obj_update_fields.append("next_obj")
                # 同步更新相关文章
                next_article.previous_obj = obj
                next_article.save(update_fields=["previous_obj"])

            # 更新当前文章关联字段‌:ml-citation{ref="2" data="citationList"}
        if obj_update_fields:
            obj.save(update_fields=obj_update_fields)
        # 开始处理敏感词
        config = SensitiveConfig.get_solo()
        sensitive_filter = SensitiveFilter(config)
        sensitive_fields = ["title", "meta_description", "content"]
        if config.filter_switch:
            for field in sensitive_fields:
                # 1. 检查字段是否存在
                if not hasattr(obj, field):
                    continue

                # 2. 获取原始值并处理
                original_value = getattr(obj, field)
                processed_value = sensitive_filter.process_text(original_value)

                setattr(obj, field, processed_value)

        context["obj"] = obj
        # 相关标签
        context["tags"] = obj.related_tags.all()
        # 相关文章逻辑
        # 通过标签获取相关文章
        related_by_tags = (
            Article.objects.filter(related_tags__in=obj.related_tags.all(), status=99)
            .select_related("category")
            .exclude(pk=obj.pk, status=99)
            .annotate(tag_count=models.Count("related_tags"))
            .order_by("-tag_count", "-create_time")
            .distinct()
            .only(
                "title",
                "meta_description",
                "pk",
                "update_time",
                "thumb_image",
                "category__id",
                "category__name",
                "category__slug",
            )[:10]
        )
        context["related_articles"] = related_by_tags
        # 补足逻辑：当标签关联不足时用同分类文章随机补足
        # remaining = 10 - len(related_by_tags)
        # if remaining > 0:
        #     same_category = Article.objects.filter(
        #         category=obj.category,
        #         status=99).select_related('category').only(
        #             'title', 'meta_description', 'pk', 'update_time',
        #             'thumb_image', 'category__id', 'category__name',
        #             'category__slug').exclude(pk=obj.pk).order_by('?')
        #     combined = related_by_tags | same_category
        #     context['related_articles'] = combined[:10]
        # else:
        #     context['related_articles'] = related_by_tags

        # # 处理缩略图路径
        # if obj.thumb_image:
        #     context['thumbnail_url'] = self._generate_thumbnail_url(
        #         obj.thumb_image.url)

        return context

    def process_fields(instance, field_names):
        """
        通用字段处理函数
        :param instance: 模型实例
        :param field_names: 需要处理的字段列表
        """
        config = SensitiveConfig.get_solo()
        if not config.filter_switch:
            return

        filter = SensitiveFilter(config)
        for field in field_names:
            value = getattr(instance, field, "")
            if value:
                setattr(instance, field, filter.process_text(str(value)))

    def _generate_thumbnail_url(self, original_url):
        return original_url.replace("/origin/", "/thumbnail/").replace(".", "_336x208.")


class ArticleHitAPIView(APIView):

    def get(self, request, pk):
        # 使用F表达式原子性更新hits字段
        updated = Article.objects.filter(pk=pk, status=99).update(hits=F("hits") + 1)
        if not updated:
            # 状态不符或对象不存在时返回404
            return Response({"detail": "指定文章不存在或未发布"}, status=status.HTTP_404_NOT_FOUND)
        # 获取更新后的值（需重新查询）
        updated_hits = Article.objects.get(pk=pk).hits
        return Response({"hits": updated_hits})


# serializers.py
class ArticleSerializer(serializers.ModelSerializer):
    # 声明字段级验证规则
    title = serializers.CharField(
        max_length=200,
        error_messages={"blank": "标题不能为空", "max_length": "标题不能超过200字符"},
    )
    category_id = serializers.IntegerField(
        required=True, error_messages={"required": "必须提供栏目ID"}
    )
    editor_id = serializers.IntegerField(
        required=True, error_messages={"required": "必须提供网站编辑id"}
    )
    content = serializers.CharField(allow_blank=False)  # 显式声明并设置参数

    class Meta:
        model = Article
        fields = [
            "title",
            "content",
            "meta_description",
            "category_id",
            "status",
            "meta_keywords",
            "editor_id",
        ]
        extra_kwargs = {
            "title": {
                "error_messages": {"blank": "标题不能为空", "max_length": "标题长度不能超过200字符"}
            }
        }

    # def validate_status(self, value):
    #     if value:

    #     return 0  # 强制覆盖为入库状态

    def validate(self, attrs):
        config = SensitiveConfig.get_solo()
        sensitive_filter = SensitiveFilter(config)
        sensitive_fields = ["title", "meta_description", "content"]

        # 严格模式：发现敏感词直接拒绝
        if config.upload_switch:
            sensitive_found = False
            error_details = {}

            for field in sensitive_fields:
                if field_value := attrs.get(field):
                    if sensitive_filter.has_sensitive_words(field_value):
                        error_details[field] = f"包含敏感内容"
                        sensitive_found = True

            if sensitive_found:
                raise serializers.ValidationError(
                    {"detail": "内容包含敏感词", "fields": error_details}
                )

        # 过滤模式：执行内容处理
        if config.filter_switch:
            for field in sensitive_fields:
                if field_value := attrs.get(field):
                    attrs[field] = sensitive_filter.process_text(field_value)
        # 标题唯一性校验
        if Article.objects.filter(title=attrs["title"]).only("id").exists():
            raise serializers.ValidationError({"title": "标题已存在"})  # type: ignore
        return attrs


@method_decorator(require_security_code, name="post")
class ArticleCreateAPIView(CreateAPIView):
    authentication_classes = []  # 禁用所有认证
    permission_classes = []  # 禁用权限校验
    serializer_class = ArticleSerializer

    def perform_create(self, serializer):
        serializer.save()
        # serializer.save(status=0, )  # 强制设置入库状态‌

    def create(self, request, *args, **kwargs):

        try:
            response = super().create(request, *args, **kwargs)
            # 标准化响应格式
            return Response(
                {
                    "code": status.HTTP_201_CREATED,
                    "message": "Article uploaded successfully",
                    "data": "",
                },
                status=status.HTTP_201_CREATED,
                content_type="application/json; charset=utf-8",
            )
        except serializers.ValidationError as e:
            # 捕获并格式化验证错误
            return Response(
                {
                    "code": status.HTTP_400_BAD_REQUEST,
                    "message": "Article upload failed",
                    "errors": e.detail,
                },
                status=status.HTTP_400_BAD_REQUEST,
                content_type="application/json; charset=utf-8",
            )


@method_decorator(require_security_code, name="get")
class ArticleStatusUpdateAPI(View):
    """文章状态更新API，支持批量更新和URL推送"""

    def __init__(self):
        self.updated_ids = []
        self.push_messages = []
        self.post_set_config = None
        self.url_push_config = None
        self.execution_stats = {"total_time": 0, "steps": {}}
        self.start_time = ""

    @method_decorator(require_security_code)
    def get(self, request):
        try:
            self.start_time = time.time()

            # 记录配置加载时间
            step_start = time.time()
            self._load_configurations()
            self.execution_stats["steps"]["load_config"] = time.time() - step_start

            # 记录参数验证时间
            step_start = time.time()
            num = self._validate_parameters()
            self.execution_stats["steps"]["validate_params"] = time.time() - step_start

            # 根据内容类型选择处理方式
            content_type = getattr(self.post_set_config, "content_type", "db")

            if content_type == "db":
                return self._process_database_content(request, num)
            else:
                return self._process_text_content(request, num)

        except Exception as e:
            self.execution_stats["total_time"] = time.time() - self.start_time
            return self._build_error_response(str(e))

    def _process_database_content(self, request, num):
        """处理数据库内容"""
        step_start = time.time()

        # 获取栏目ID和编辑ID
        category_ids = getattr(self.post_set_config, "nodekey", "0").strip()
        selected_categories = self._get_categories(category_ids)

        if not selected_categories:
            return self._build_error_response(_("所有栏目已经没有文章了。"))

        # 获取随机编辑ID
        editor_id = self.post_set_config.get_random_editor_id()

        # 处理文章
        for i in range(num):
            cate_id = random.choice(selected_categories)
            article = self._get_next_article(cate_id)

            if not article:
                return self._build_error_response(_("所选栏目没有可用的文章。"))

            self._update_article_status(article, editor_id)
            self._push_article_url(request, article)

        self.execution_stats["steps"]["process_articles"] = time.time() - step_start
        self._log_operation(editor_id)
        self.execution_stats["total_time"] = time.time() - self.start_time

        return self._build_success_response()

    def _process_text_content(self, request, num):
        """处理文本文件内容"""
        step_start = time.time()

        # 获取栏目ID和编辑ID
        category_ids = getattr(self.post_set_config, "nodekey", "0").strip()
        selected_categories = self._get_categories(category_ids, for_txt=True)

        if not selected_categories:
            return self._build_error_response(_("没有可用的文本文件目录。"))

        # 获取随机编辑ID
        editor_id = self.post_set_config.get_random_editor_id()

        # 处理文本文件
        for i in range(num):
            cate_id = random.choice(selected_categories)
            txt_file_info = self._get_next_text_file(cate_id)

            if not txt_file_info:
                return self._build_error_response(_("所选目录没有可用的文本文件。"))

            article = self._create_article_from_txt(txt_file_info, cate_id, editor_id)
            if article:
                self.updated_ids.append(article.id)
                self._push_article_url(request, article)

        self.execution_stats["steps"]["process_articles"] = time.time() - step_start
        self._log_operation(editor_id)
        self.execution_stats["total_time"] = time.time() - self.start_time

        return self._build_success_response()

    def _get_categories(self, category_ids, for_txt=False):
        """获取可用的栏目列表"""
        if category_ids != "0":
            try:
                category_list = [int(cid) for cid in category_ids.split(",") if cid]
                return [
                    cid for cid in category_list if self._check_category_availability(cid, for_txt)
                ]
            except (ValueError, IndexError):
                return self._get_all_available_categories(for_txt)
        else:
            return self._get_all_available_categories(for_txt)

    def _check_category_availability(self, category_id, for_txt=False):
        """检查栏目是否有可用内容"""
        if for_txt:
            # 检查文本文件目录是否存在且有文件
            txt_dir = os.path.join("static", "txt_files", str(category_id))
            return os.path.exists(txt_dir) and any(f.endswith(".txt") for f in os.listdir(txt_dir))
        else:
            # 检查数据库中有无文章
            return Article.objects.filter(
                category_id=category_id, status=int(self.post_set_config.publish_status)
            ).exists()

    def _get_all_available_categories(self, for_txt=False):
        """获取所有可用的栏目"""
        if for_txt:
            # 获取所有有文本文件的目录
            base_dir = os.path.join("static", "txt_files")
            if not os.path.exists(base_dir):
                return []

            available_categories = []
            for item in os.listdir(base_dir):
                item_path = os.path.join(base_dir, item)
                if os.path.isdir(item_path) and any(
                    f.endswith(".txt") for f in os.listdir(item_path)
                ):
                    try:
                        available_categories.append(int(item))
                    except ValueError:
                        continue
            return available_categories
        else:
            # 从数据库获取有文章的栏目
            from django.db.models import Count

            return list(
                Article.objects.filter(status=int(self.post_set_config.publish_status))
                .values("category_id")
                .annotate(count=Count("id"))
                .filter(count__gt=0)
                .values_list("category_id", flat=True)
            )

    def _get_next_text_file(self, category_id):
        """获取下一个文本文件"""
        txt_dir = os.path.join("static", "txt_files", str(category_id))
        if not os.path.exists(txt_dir):
            return None

        txt_files = [f for f in os.listdir(txt_dir) if f.endswith(".txt")]
        if not txt_files:
            return None

        selected_file = random.choice(txt_files)
        file_path = os.path.join(txt_dir, selected_file)

        try:
            with open(file_path, "r", encoding="utf-8") as f:
                content = f.read()

            # 文件名作为标题（去掉扩展名）
            title = os.path.splitext(selected_file)[0]

            return {"title": title, "content": content, "file_path": file_path}
        except Exception as e:
            print(f"读取文本文件失败: {e}")
            return None

    def _create_article_from_txt(self, txt_file_info, category_id, editor_id):
        """从文本文件创建文章并设置编辑"""
        try:
            with transaction.atomic():
                article = Article(
                    title=txt_file_info["title"],
                    content=txt_file_info["content"],
                    category_id=category_id,
                    editor_id=editor_id,  # 设置编辑ID
                    status=99,  # 直接设置为发布状态
                    create_time=datetime.datetime.now(),
                    update_time=datetime.datetime.now(),
                )
                article.save()
                # 可选：处理成功后删除或移动文本文件
                os.remove(txt_file_info["file_path"])
                return article
        except Exception as e:
            print(f"创建文章失败: {e}")
            return None

    # 保留原有的辅助方法（稍作调整）
    def _load_configurations(self):
        """加载系统配置"""
        self.post_set_config = PostSet.get_solo()
        if not self.post_set_config:
            raise ValueError("发布配置没有设置")
        from apps.core.models.urlpush import UrlPush

        if hasattr(UrlPush, "get_solo"):
            self.url_push_config = UrlPush.get_solo()

    def _validate_parameters(self):
        """验证请求参数"""
        num_param = getattr(self.post_set_config, "num", 1)
        try:
            num = int(num_param)
            if num < 1:
                raise ValueError("num参数必须大于0")
            return num
        except ValueError:
            raise ValueError("无效的num参数")

    def _get_next_article(self, cate_id):
        """获取待处理文章（数据库模式）"""
        return (
            Article.objects.filter(
                category_id=cate_id, status=int(self.post_set_config.publish_status)
            )
            .order_by("create_time")
            .first()
        )

    def _update_article_status(self, article, editor_id):
        """更新文章状态（数据库模式）并设置编辑"""
        article.status = 99
        article.editor_id = editor_id  # 设置编辑ID
        article.update_time = datetime.datetime.now()
        article.save()
        self.updated_ids.append(article.id)

    # 保留其他原有方法不变
    def _push_article_url(self, request, article):
        """推送文章URL"""
        if not (self.url_push_config and getattr(self.url_push_config, "article_status", False)):
            return

        try:
            url = article.get_absolute_url()
            site_config = SiteSettings.get_solo()
            if site_config.site_url:
                base_url = site_config.site_url
            else:
                base_url = f"http://{request.get_host()}"

            push_status = self.url_push_config.push_url([f"{base_url}{url}"])
            msg = f"{base_url}{url}:{self.url_push_config.check_baidu_push_status(push_status)}"
            self.push_messages.append(msg)

        except Exception as e:
            error_msg = f"推送{article.id}的url失败:{e}"
            self.push_messages.append(error_msg)
            print(error_msg)

    def _log_operation(self, editor_id=None):
        """记录操作日志，包含编辑信息"""
        editor_info = f"，分配编辑ID: {editor_id}" if editor_id else ""
        mes = (
            f"API成功处理{len(self.updated_ids)}条内容{editor_info}，id为:{self.updated_ids}"
            + "".join(self.push_messages)
        )
        from apps.log_service.services import log_service

        log_service.write_log("article_update_status", mes, log_type="persistent")

    def _build_success_response(self):
        """构建成功响应"""
        response_data = {
            "status": "success",
            "content_type": getattr(self.post_set_config, "content_type", "db"),
            "updated_count": len(self.updated_ids),
            "affected_ids": self.updated_ids,
            "push_messages": self.push_messages,
            "execution_stats": {
                "total_ms": round(self.execution_stats["total_time"] * 1000, 2),
                "steps": {
                    name: f"{round(time * 1000, 2)}ms"
                    for name, time in self.execution_stats["steps"].items()
                },
            },
        }
        return JsonResponse(response_data, json_dumps_params={"ensure_ascii": False})

    def _build_error_response(self, error_msg):
        """构建错误响应"""
        response_data = {
            "error": error_msg,
            "content_type": getattr(self.post_set_config, "content_type", "db"),
            "execution_time_ms": round(self.execution_stats["total_time"] * 1000, 2),
        }
        return JsonResponse(response_data, status=400, json_dumps_params={"ensure_ascii": False})


@method_decorator(require_security_code, name="get")
class CreateAIArticle(View):

    def get(self, request, *args, **kwargs):
        # 创建并启动后台线程

        try:
            num = int(request.GET.get("num", 1))
            if num < 1:
                num = 1
        except Exception as e:
            print("CreateAIArticle get num Error,我们按1处理:==>", e)
            num = 1
        for i in range(num):
            print(f"我们开始执行AI生成第{i+1}次")
            article_create = AIcreate()
            threading.Thread(target=article_create.ai_article, daemon=True).start()

        # 返回即时响应
        return JsonResponse(
            {"mes": "我们开始后台生成文章"},
            json_dumps_params={"ensure_ascii": False},
            content_type="application/json; charset=utf-8",
        )


class ArticleValidator:

    def __init__(self, data, instance=None):
        """
        :param data: 字典数据，包含文章字段
        :param instance: 可选，Article实例（更新时传入）
        """
        self.data = data
        self.instance = instance
        self.errors = {}

    def validate_required_fields(self):
        """验证必须字段"""
        required_fields = ["title", "content", "category_id", "editor_id"]
        for field in required_fields:
            if not self.data.get(field):
                self.errors[field] = "该字段为必填项"

    def validate_field_types(self):
        """验证字段类型"""
        type_checks = {
            "title": str,
            "content": str,
            "category_id": int,
            "editor_id": int,
            "status": int,
        }
        for field, expected_type in type_checks.items():
            value = self.data.get(field)
            if value and not isinstance(value, expected_type):
                self.errors[field] = f"必须为{expected_type.__name__}类型"

    def validate_foreign_keys(self):
        """验证外键存在性"""
        try:
            Category.objects.get(id=self.data["category_id"])
        except Category.DoesNotExist:
            self.errors["category_id"] = "对应分类不存在"
        except KeyError:
            pass
        try:
            User.objects.get(id=self.data["editor_id"])
        except User.DoesNotExist:
            self.errors["editor_id"] = "对应编辑不存在"
        except KeyError:
            pass

    def validate_unique_title(self):
        """标题唯一性校验（排除自身）"""
        title = self.data.get("title")
        if title and "title" not in self.errors:  # 仅在无其他错误时校验
            qs = Article.objects.filter(title=title)
            if self.instance:
                qs = qs.exclude(pk=self.instance.pk)
            if qs.exists():
                self.errors["title"] = "标题已存在"

    def is_valid(self):
        """执行完整验证"""
        self.validate_required_fields()
        self.validate_field_types()
        self.validate_foreign_keys()
        self.validate_unique_title()  # 新增唯一性校验

        # 额外状态值验证
        if self.data.get("status", 99) not in [99, 1]:
            self.errors["status"] = "无效的状态值"

        return not bool(self.errors)

    def save(self):
        """验证通过后保存到数据库"""
        if not self.is_valid():
            raise ValidationError("数据未通过验证")

        # 创建或更新逻辑
        if self.instance:
            # 更新现有实例
            for attr, value in self.data.items():
                setattr(self.instance, attr, value)
            instance = self.instance
        else:
            # 创建新实例
            instance = Article(**self.data)

        instance.full_clean()  # 执行模型层验证
        instance.save()
        return f"{instance}保存成功,id:{instance.pk}"


class ArticleStaricRemove(APIView):

    @method_decorator(require_security_code, name="get")  # 应用装饰器到dispatch方法
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)

    def get(self, request):

        action = request.query_params.get("url")  # 应校验参数名是否为 "action"
        if "url" in request.query_params:
            url = request.query_params.get("url")  # 提取实际 URL 参数
            if not url:
                return JsonResponse(
                    {"status": 400, "message": "缺少 url 参数"},
                    content_type="application/json; charset=utf-8",
                    json_dumps_params={"ensure_ascii": False},
                )
            return JsonResponse(
                {"status": 400, "message": self._remove_url(url)},
                content_type="application/json; charset=utf-8",
                json_dumps_params={"ensure_ascii": False},
            )
        else:
            # 执行全量移除逻辑
            return JsonResponse(
                {"status": 200, "message": self._remove_static_dir()},
                content_type="application/json; charset=utf-8",
                json_dumps_params={"ensure_ascii": False},
            )

    def _remove_url(self, url):  # 添加 self 参数
        from apps.core.cache.static_cache import StaticCacheManager

        manager = StaticCacheManager()

        return manager._remove_file(url)

    def _remove_static_dir(self):  # 添加 self 参数
        from apps.core.cache.static_cache import StaticCacheManager

        manager = StaticCacheManager()

        return manager._remove_static_dir()


class ArticleListAPI(APIView):

    def get(self, request):
        # 获取请求参数
        category = request.GET.get("category")
        include_children = request.GET.get("include_children", "false").lower() == "true"
        join_category = True
        order_by = request.GET.get("order_by", "-update_time")
        page_size = int(request.GET.get("page_size", 10))
        cursor = request.GET.get("cursor")

        # 处理分类查询
        category_filter = Q()
        if category:
            try:
                category_obj = Category.objects.get(pk=category)
                if include_children:
                    descendants = category_obj.get_descendants(include_self=True)
                    category_filter = Q(category__in=descendants)
                else:
                    category_filter = Q(category=category_obj)
            except Category.DoesNotExist:
                pass

        # 处理查询字段
        article_fields = ["title", "meta_description", "update_time", "pk", "thumb_image"]
        select_related = []
        if join_category:
            category_fields = ["category__name", "category__slug", "category__id"]
            article_fields += category_fields
            select_related = ["category"]
        if "category" not in article_fields:
            article_fields.append("category")
            if "category" not in select_related:
                select_related.append("category")
        # 构建查询
        queryset = Article.objects.filter(category_filter, status=99).only(*article_fields)

        if select_related:
            queryset = queryset.select_related(*select_related)

        # 游标分页
        if cursor:
            try:
                last_obj = Article.objects.get(pk=cursor)
                queryset = queryset.filter(update_time__lt=last_obj.update_time) | queryset.filter(
                    update_time=last_obj.update_time, pk__lt=last_obj.pk
                )
            except Article.DoesNotExist:
                pass

        articles = queryset.order_by(order_by)[: page_size + 1]
        has_next = len(articles) > page_size
        articles = articles[:page_size]
        next_cursor = articles[-1].pk if has_next else None

        # 序列化结果

        data = {
            "results": [
                {
                    "title": a.title,
                    "absolute_url": a.get_absolute_url(),
                    "thumb_image": a.thumb_image.url if a.thumb_image else nopic(),
                    "meta_description": a.meta_description,
                    "update_time": a.update_time,
                    "category": {"name": a.category.name, "url": a.category.get_absolute_url()},
                }
                for a in articles
            ],
            "has_next": has_next,
            "next_cursor": next_cursor,
        }
        return Response(data)
