import html
import re
from datetime import datetime, timedelta

from apps.core.models.url_style import UrlStyle
from apps.core.utils.hash import hash_id
from django.core.cache import cache
from django.core.validators import MaxValueValidator
from django.db import models
from django.utils import timezone
from django.utils.html import strip_tags
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
from django_ckeditor_5.fields import CKEditor5Field
from pypinyin import lazy_pinyin
from slugify import slugify

# 方案1：直接从django.utils.timezone导入utc（推荐）
try:
    from django.utils.timezone import utc
except ImportError:
    # 方案2：兼容旧版本Django，使用pytz.UTC（如果项目已安装pytz）
    try:
        from pytz import UTC as utc
    except ImportError:
        # 方案3：使用Python标准库手动创建UTC时区（最保险的回退方案）
        utc = timezone(timedelta(hours=0))


class Tag(models.Model):
    """内容标签管理系统"""

    class StatusChoices(models.IntegerChoices):
        DRAFT = 1, _("草稿")
        PENDING = 0, _("待审")
        REVIEW = 99, _("审核通过")
        ARCHIVED = 3, _("回收站")

    class GroupType(models.TextChoices):
        DEFAULT = "default", _("默认分组")
        TOPIC = "topic", _("主题标签")
        CONTENT = "content", _("内容标签")

    # 基础信息
    title = models.CharField(max_length=200, verbose_name=_("标签名称"), db_index=True, unique=True)
    segmented_words = models.JSONField(default=list, blank=True, null=True, verbose_name="分词结果")
    slug = models.SlugField(
        max_length=200,
        verbose_name=_("标签别名"),
        blank=True,  # unique=True
    )
    order = models.PositiveSmallIntegerField(
        default=0, verbose_name=_("排序权重"), validators=[MaxValueValidator(999)]
    )

    # 分类信息
    group = models.CharField(
        max_length=20,
        choices=GroupType.choices,
        default=GroupType.DEFAULT,
        verbose_name=_("标签分组"),
        db_index=True,
    )

    # 内容统计
    usage_count = models.PositiveIntegerField(
        default=0, verbose_name=_("使用次数"), editable=False  # 自动维护字段
    )

    # 富文本内容
    intro = CKEditor5Field(
        verbose_name="标签介绍", blank=True, config_name="basic"
    )  # 使用简化版CKEditor配置

    # 状态控制
    status = models.PositiveSmallIntegerField(
        choices=StatusChoices.choices,
        default=StatusChoices.REVIEW,
        verbose_name=_("审核状态"),
        db_index=True,
    )

    # 时间戳
    create_time = models.DateTimeField(auto_now_add=True, verbose_name=_("创建时间"), db_index=True)
    update_time = models.DateTimeField(auto_now=True, verbose_name=_("更新时间"), db_index=True)
    cut_keywords = models.ManyToManyField(
        "content.cutword", related_name="tags", blank=True, verbose_name="标题分词"
    )
    filtered_times = models.JSONField(
        default=dict,
        blank=True,
        verbose_name=_("动态匹配时间记录"),
        help_text=mark_safe(
            _(
                """
存储各资源类型的最后匹配时间。<pre><code>  
<strong>**格式示例**:</strong> 
{
    "article": "2023-10-25T10:30:00.123Z",  # 最后匹配文章的时间
    "download": "2023-10-24T15:45:00.456Z", # 最后匹配下载资源的时间
    "image": "2023-10-23T09:15:00.789Z",    # 最后匹配图库的时间
    "wiki": "2023-10-22T14:00:00.000Z"     # 最后匹配百科的时间
}
            
<strong>**使用说明**:</strong> 
- 键名 (如 'article') 表示资源类型。
- 值为 ISO 8601 格式的 UTC 时间字符串。
- 当某种资源类型有新的匹配时，更新对应的时间戳即可。

在开发中可如下使用
<strong>tag.update_filtered_time('article')</strong> 
tag 为模型Tag实例
</code></pre>  
"""
            )
        ),
    )
    generated_title = models.TextField(
        verbose_name="固化后的页面标题",
        blank=True,
        help_text="由系统根据多样性模板自动生成并固化，用于SEO",
    )
    generated_intro = models.TextField(
        verbose_name="固化后的页面简介",
        blank=True,
        help_text="由系统根据多样性模板自动生成并固化，支持HTML",
    )

    class Meta:
        db_table = "tags"
        verbose_name = "内容标签"  # 修改为通用名称
        verbose_name_plural = "标签列表"
        # unique_together = [('group', 'slug')]  # 添加唯一性约束
        ordering = ["-update_time", "-id"]
        indexes = [
            # 核心查询索引
            models.Index(fields=["status", "-update_time"], name="idx_status_utime"),
            # 高频状态条件索引
            models.Index(
                fields=["status"], condition=models.Q(status=99), name="idx_status_review"
            ),
            # Admin全覆盖索引
            models.Index(fields=["group", "title", "id"], name="idx_admin_full_cover"),
        ]

    # constraints = [
    #     # 保留唯一约束（自动生成复合索引）
    #     models.UniqueConstraint(fields=['title', 'group'],
    #                             name='uniq_title_group')
    # ]

    def __str__(self):
        return f"{self.title} ({self.get_group_display()})"

    def get_absolute_url(self):
        """
        变量为objects
        url_front=[('model', '模型路径（/model/123.html）'),
        ('slug', '单级Slug（/news/123.html）'),
        ('moreslug', '多级Slug（/news/sports/123.html）'),
        ('random', '随机字符（/jklfdsa/1.html）'),
        ('custom', '自定义路径（/mycategory/myarticle/1.shtml）')]
        """
        urlconfig = UrlStyle.get_solo()

        # 使用自定义标识（如果存在且非空），否则使用默认值'tag'
        if urlconfig.tag_identifier and urlconfig.tag_identifier.strip():
            prefix = urlconfig.tag_identifier
        else:
            prefix = "tag"

        if urlconfig.url_mid == "hash":
            url_mid = hash_id(self.id)  # 确保hash_id函数已定义
        else:
            url_mid = self.id

        return f"/{prefix}/{url_mid}{urlconfig.url_suffix}"

    @property
    def total_usage(self):
        return (
            self.articles.count()
            + self.novels.count()
            + self.videos.count()
            + self.pics.count()
            + self.downloads.count()
            + self.wikis.count()
        )

    @property
    def is_active(self):
        return self.status == self.StatusChoices.REVIEW

    def increment_usage(self):
        """原子操作更新使用次数"""
        self.__class__.objects.filter(pk=self.pk).update(usage_count=models.F("usage_count") + 1)

    class AdminMeta:
        list_display = ["title", "status", "usage_count"]
        search_fields = ["title", "slug"]
        list_filter = ["group", "status"]

    # def clean(self):
    #     pass

    # 校验分组与模型关联的合法性
    # if self.group == GroupType.TOPIC:  # and self.novels.exists():
    #     raise ValidationError(_('主题标签不能关联小说内容'))

    def generate_slug(self):
        """独立slug生成方法，可被外部调用"""
        if not self.slug:
            pinyin_list = lazy_pinyin(self.title, style=3)
            base_slug = "".join(pinyin_list)
            base_slug = slugify(base_slug)

            if base_slug.isdigit():
                base_slug = f"tag-{base_slug}"

            unique_slug = base_slug
            counter = 1
            while Tag.objects.filter(group=self.group, slug=unique_slug).exists():
                unique_slug = f"{base_slug}-{counter}"
                counter += 1
            return unique_slug
        return self.slug

    def clean_fields(self, exclude=None):
        """字段清理逻辑"""
        if self.intro and (not exclude or "intro" not in exclude):
            cleaned_intro = html.unescape(self.intro)
            cleaned_intro = strip_tags(cleaned_intro)
            self.intro = re.sub(r"\s+", " ", cleaned_intro).strip()
        super().clean_fields(exclude=exclude)

    def save(self, *args, **kwargs):
        """重写save方法"""
        self.clean_fields()
        # 模型save方法中停用 slug自动生成，防止在批量加入时计算过多，导入速度太慢，该方法加入到admin save中
        # if not self.slug:
        #     self.slug = self.generate_slug()
        if self.title:
            self.title = self.title.strip()
        if self.pk:
            old_instance = Tag.objects.get(pk=self.pk)
            title_changed = old_instance.title != self.title
        else:
            title_changed = True

        from apps.core.models import TagPageConfig  # 注意避免循环导入

        tag_config = TagPageConfig.get_solo()
        # 1. 如果 generated_title 为空，则尝试生成
        if not self.generated_title:
            # 调用您TagPageConfig中的方法，假设您能获取到tag_config实例
            self.generated_title = tag_config.get_complete_title(self.title) or ""

        # 2. 如果 generated_intro 为空，则尝试生成
        if not self.generated_intro:
            # 注意：这里传入的 obj_intro 是标签原有的 intro 字段内容
            self.generated_intro = tag_config.generate_diverse_intro(self.title, self.intro) or ""
        # 3. 调用父类的save方法，将数据（包括新生成的标题和简介）保存到数据库
        super().save(*args, **kwargs)
        # 如果title发生变化或是没有相关分词的时候更新分词
        if title_changed or not self.cut_keywords.exists():
            self.update_cut_keywords()

    def update_cut_keywords(self):
        """
        更新标签的分词关联
        """
        from apps.content.utils.extract_keywords import extract_keywords

        from .cut_words import CutWord

        # 清空旧的分词关联
        self.cut_keywords.clear()

        # 提取标题分词并保存到segmented_words
        keywords = extract_keywords(self.title)
        self.segmented_words = keywords
        super().save(update_fields=["segmented_words"])

        if not keywords:
            return

        # 创建或获取CutWord记录，并建立关联
        for keyword in keywords:
            cut_word, created = CutWord.objects.get_or_create(title=keyword)
            self.cut_keywords.add(cut_word)

    @classmethod
    def bulk_create_with_slug(cls, objs, batch_size=None):
        """支持批量创建时自动生成slug的方法"""
        for obj in objs:
            if not obj.slug:
                obj.slug = obj.generate_slug()
        return cls.objects.bulk_create(objs, batch_size=batch_size)

    CACHE_KEY_TOTAL_COUNT = "tag_model_total_count"

    @classmethod
    def get_total_count(cls):
        """
        获取 Tag 模型的对象总数，使用缓存。

        返回:
            int: Tag 对象的总数。
        """
        # 尝试从缓存中获取总数
        total_count = cache.get(cls.CACHE_KEY_TOTAL_COUNT)
        if total_count is None:
            # 缓存中没有，则从数据库中计算
            total_count = cls.objects.count()
            # 将计算结果设置到缓存中，默认超时时间（300秒）可根据需要调整
            cache.set(cls.CACHE_KEY_TOTAL_COUNT, total_count, 300)
        return total_count

    @classmethod
    def _clear_total_count_cache(cls):
        """
        内部方法，用于清除总数缓存。
        通常在创建新 Tag 对象后调用。
        """
        cache.delete(cls.CACHE_KEY_TOTAL_COUNT)

    def update_filtered_time(self, resource_type):
        """更新指定资源类型的动态匹配时间为当前时间（UTC时区）。"""
        # 导入已在函数外处理，无需再次导入
        # 获取当前时间并明确转换为UTC时区
        now_utc = timezone.now().astimezone(utc)  # 使用上面定义的utc

        if not self.filtered_times:
            self.filtered_times = {}

        # 存储UTC时区的ISO格式字符串
        self.filtered_times[resource_type] = now_utc.isoformat()

        # 只保存 filtered_times 字段，提高效率
        self.save(update_fields=["filtered_times"])

    def get_filtered_time(self, resource_type):
        """获取指定资源类型的动态匹配时间，不存在则返回一个极早的UTC时间。"""
        try:
            from django.utils.dateparse import parse_datetime

            time_str = self.filtered_times.get(resource_type)

            if time_str:
                parsed_time = parse_datetime(time_str)
                if parsed_time:
                    # 关键步骤：确保返回的时间一定是时区感知的，并且是UTC时区
                    if timezone.is_naive(parsed_time):
                        # 使用上面定义的utc常量
                        parsed_time = timezone.make_aware(parsed_time, timezone=utc)
                    else:
                        # 如果已有时区信息，统一转换为UTC时区
                        parsed_time = parsed_time.astimezone(utc)  # 使用utc常量
                    return parsed_time

            # 默认返回一个极早的UTC时区感知时间（Unix纪元起点）
            # 使用方法三：手动创建UTC时区对象
            return datetime(1970, 1, 1, tzinfo=utc)  # 直接指定时区信息
        except Exception as e:
            print(f"get_filtered_time Error{e}")
            return datetime(1970, 1, 1, tzinfo=utc)

    def is_recently_matched(self, resource_type, days=30):
        """
        检查指定资源类型是否在最近N天内有匹配记录
        """
        try:
            # 获取该资源类型的最后匹配时间
            last_matched_time = self.get_filtered_time(resource_type)

            # 计算N天前的时间点（使用时区感知的时间）
            cutoff_time = timezone.now() - timezone.timedelta(days=days)

            # 确保cutoff_time是时区感知的（与last_matched_time保持一致）
            if timezone.is_naive(cutoff_time):
                cutoff_time = timezone.make_aware(cutoff_time, timezone=utc)

            # 比较时间
            return last_matched_time > cutoff_time
        except Exception as e:
            print(f"is_recently_matched Error:{e}")
            return False

    def refresh_diverse_content(self, site_name, force_update=False):
        """
        为标签生成并固化新的多样性标题和简介。
        通常只在管理员手动触发或标签信息更新时执行。

        :param site_name: 网站名称
        :param force_update: 是否强制更新，即使已有内容也重新生成
        """
        from apps.core.models import TagPageConfig  # 避免循环导入

        # 获取标签页面配置
        tag_config = TagPageConfig.get_solo()

        # 1. 生成并固化标题
        if not self.generated_title or force_update:
            new_title = tag_config.generate_diverse_title(self.title, site_name)
            self.generated_title = new_title

        # 2. 生成并固化简介
        if not self.generated_intro or force_update:
            # 注意：这里传入的 obj_intro 是标签原有的 intro 字段
            new_intro = tag_config.generate_diverse_intro(self.title, site_name, self.intro)
            self.generated_intro = new_intro

        # 只更新发生变化的字段
        update_fields = []
        if not self.generated_title or force_update:
            update_fields.append("generated_title")
        if not self.generated_intro or force_update:
            update_fields.append("generated_intro")

        if update_fields:
            self.save(update_fields=update_fields)

    def get_seo_title(self, pagenum=None):
        """
        在模板中调用此方法获取最终用于SEO的标题。
        它返回固化的标题，并智能添加分页信息。
        """
        base_title = (
            self.generated_title or f'"{self.title}"相关内容介绍、详解，"{self.title}"是什么意思'
        )

        if pagenum:
            return f"{base_title}-第{pagenum}页-"
        return f"{base_title}-"

    def get_seo_intro(self):
        """
        在模板中调用此方法获取最终用于SEO的简介。
        如果尚未生成固化简介，则返回一个合理的默认值。
        """
        if self.generated_intro:
            return self.generated_intro
        # 提供一个基本的、与标题相关的默认描述，这比空着要好
        return f"本页面提供关于“{self.title}”的详细介绍和相关内容。"
