import os
import re

from apps.core.models.site_settings import SiteSettings
from apps.core.models.url_style import UrlStyle
from apps.core.utils.get_models import get_contenttype_choices
from apps.core.utils.hash import hash_id
from apps.core.utils.restricted_term_manager import RestrictedTermManager
from django.conf import settings
from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.db import IntegrityError, models, transaction
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
from mptt.models import MPTTModel, TreeForeignKey
from pypinyin import lazy_pinyin


def generate_slug(name, existing_slugs=None):
    """生成拼音首字母slug并处理冲突"""
    # 提取拼音首字母并过滤特殊字符
    slug = ''.join([s[0] for s in lazy_pinyin(name) if s])
    slug = re.sub(r'[^\w-]', '', slug).lower()

    # 处理冲突
    if existing_slugs and slug in existing_slugs:
        from random import choices
        slug += f"-{''.join(choices('abcdefghjkmnpqrstuvwxyz23456789', k=4))}"
    return slug


def extract_app_labels(app_paths):
    """从完整应用路径中提取最后一级应用名"""
    return [path.split('.')[-1] for path in app_paths]


class Category(MPTTModel):

    class CategoryType(models.IntegerChoices):
        NORMAL = 1, _("普通栏目")  # 基础内容栏目
        ENCYCLOPEDIA = 2, _("百科栏目")  # 知识库类栏目
        DOWNLOAD = 3, _("下载栏目")  # 资源下载类栏目
        SPECIAL = 4, _("专题栏目")  # 聚合型内容栏目

    class OpenType(models.TextChoices):
        SELF = '_self', _("原窗口打开")
        BLANK = '_blank', _("新窗口打开")

    class NavStatus(models.IntegerChoices):
        HIDDEN = 0, _("不加入导航")
        VISIBLE = 1, _("加入导航")

    class ReviewStatus(models.IntegerChoices):
        ACTIVE = 1, _("激活")  # 正常可用的分类‌
        DISABLED = 2, _("禁用")  # 前端隐藏但保留历史数据
        PENDING = 3, _("待审核")  # 新建/修改待管理员审核
        ARCHIVED = 4, _("归档")  # 历史数据存档不可编辑

    class BooleanFlags(models.IntegerChoices):
        NO = 0, _('否')
        YES = 1, _('是')

    name = models.CharField(
        _("栏目名称"),
        max_length=200,
    )

    cate_type = models.PositiveSmallIntegerField(_("栏目类型"),
                                                 choices=CategoryType.choices,
                                                 default=CategoryType.NORMAL,
                                                 db_index=True,
                                                 help_text=_("用于区分栏目的业务类型"))
    content_type = models.ForeignKey(
        ContentType,
        on_delete=models.CASCADE,
        verbose_name="关联模型",
        limit_choices_to=get_contenttype_choices())

    slug = models.SlugField(
        _("栏目标识"),
        max_length=200,
        unique=True,
    )

    parent = TreeForeignKey("self",
                            on_delete=models.SET_NULL,
                            null=True,
                            blank=True,
                            verbose_name=_("父栏目"),
                            related_name="children")

    image = models.ImageField(_("栏目图片"),
                              upload_to='category_images/',
                              blank=True,
                              null=True)

    seo_title = models.CharField(_("SEO标题"),
                                 max_length=200,
                                 blank=True,
                                 default="")

    description = models.TextField(_("栏目说明"),
                                   max_length=500,
                                   blank=True,
                                   default="")

    keywords = models.CharField(_("栏目关键词"),
                                max_length=200,
                                blank=True,
                                help_text=_("多个关键词用英文逗号分隔"))

    open_type = models.CharField(_("打开方式"),
                                 max_length=7,
                                 choices=OpenType.choices,
                                 default=OpenType.SELF,
                                 db_index=True)

    nav_status = models.PositiveSmallIntegerField(_("导航状态"),
                                                  choices=NavStatus.choices,
                                                  default=NavStatus.VISIBLE,
                                                  db_index=True)

    index_template = models.FilePathField(
        _("首页模板"),
        path=settings.TEMPLATES_THEME_DIR,  # 动态路径获取
        match=r'.*\.html$',
        recursive=True,
        allow_files=True,
        allow_folders=True,
        blank=True,
        max_length=500)

    content_template = models.FilePathField(_("内容模板"),
                                            path=settings.TEMPLATES_THEME_DIR,
                                            match=r'.*\.html$',
                                            recursive=True,
                                            allow_files=True,
                                            allow_folders=True,
                                            blank=True,
                                            max_length=500)

    created_time = models.DateTimeField(_("创建时间"),
                                        auto_now_add=True,
                                        editable=False)

    updated_time = models.DateTimeField(_("更新时间"),
                                        auto_now=True,
                                        editable=False)

    notes = models.TextField(_("站长备注"),
                             max_length=500,
                             blank=True,
                             help_text=_("本字段仅供后台管理使用，前端调用方式与其他字段相同"))

    review_status = models.PositiveSmallIntegerField(
        _("审核状态"),
        choices=ReviewStatus.choices,
        default=ReviewStatus.ACTIVE,
        db_index=True)
    staff_only = models.BooleanField(
        verbose_name="仅工作人员可见",
        default=False,
    )

    allowed_groups = models.ManyToManyField(Group,
                                            verbose_name="允许访问的用户组",
                                            blank=True,
                                            help_text="选择允许访问此内容的用户组(多选)")
    subdomain = models.CharField(
        max_length=100,
        null=True,
        blank=True,
        verbose_name="个性域名",
        help_text=mark_safe('''<div class="alert alert-warning">
    <ul>
        <li>不要使用http或者https,只需要使用个性域名。比如a.example.com,b.example.com,至于http或者https会以“//a.example.com”让浏览器处理，理论上也是可以绑定www域名的，也就是说你的域名为www.example.com,栏目中也是可以绑定www.example_a.com域名的，但是我们只保存纯净的域名部分。</li>
        <li>该设置与全局泛解析互斥，当开启泛解析后，文章以泛解析为先，但栏目URL则为该设置。</li>
        <li>目前有个性域名后的栏目首页,该设置的值具有优先性，比如本来的域名为解析为example.com和www.example.com,在没有此值是二者均可访问首页，当个性域名设置为www.example.com后，如果直接访问www.example.com将会使用栏目页进行展示。</li>
        <li>理论上个性域名可以绑定任何域名。</li>
        <li>有个性域名的URL只对文章模型起作用，其他模型的URL根据实际应用的需求暂未处理。</li>
        <li>当多级栏目时，只考虑当前栏目和根栏目，。</li>
        <li>当栏目使用个性域名或者该栏目的根栏目个性域名，则该值会使用缓存且永不过期，如果修改个性域名，设置将在项目重启后生效。</li>
        <li>该功能目前只是代码理论通过，还没有进入实测。</li>
    </ul>
</div>'''))

    class MPTTMeta:
        order_insertion_by = ['nav_status', 'created_time']
        level_attr = 'tree_depth'

    class Meta:
        db_table = "category"
        verbose_name = _("栏目分类")
        verbose_name_plural = _("栏目分类")
        ordering = ['tree_id', 'lft']
        indexes = [
            models.Index(
                fields=['nav_status', 'review_status', 'created_time']),
        ]
        permissions = [
            ("can_view_restricted", "可以查看受限内容"),
        ]

    @classmethod
    def get_template_path(cls):
        """动态获取模板路径的类方法"""
        return os.path.join(
            settings.BASE_DIR, 'templates',
            getattr(settings, 'TEMPLATEDIR', 'default_templates'))

    @property
    def is_published(self):
        """便捷状态判断属性"""
        return self.review_status == self.ReviewStatus.ACTIVE

    def clean(self):
        """模型层验证（执行顺序在save之前）"""
        super().clean()

        # 唯一性验证（即使有数据库约束仍需应用层验证）
        if Category.objects.filter(slug=self.slug).exclude(
                pk=self.pk).exists():
            raise ValidationError({'slug': _("该标识已被使用")})
        # 使用限制词管理器验证
        #有条件地进行限制词验证 ---
        need_validate_slug = True  # 默认需要验证

        # 情况1：如果是更新现有实例（非新实例）
        if not self._state.adding:
            try:
                # 从数据库中获取该实例当前的slug值
                current_db_slug = Category.objects.values_list('slug', flat=True).get(pk=self.pk)
                # 如果当前提交的slug与数据库中的slug一致，说明slug未改变，无需重复验证限制词
                if current_db_slug == self.slug:
                    need_validate_slug = False
            except Category.DoesNotExist:
                # 理论上不会发生，因为正在更新一个已存在的实例
                pass

        # 情况2：如果是新实例（self._state.adding 为 True），need_validate_slug 保持 True，必须验证。

        if need_validate_slug:
            # 使用限制词管理器验证（仅在slug为新值或实例为新创建时执行）
            RestrictedTermManager.validate_term(self.slug)
        """增加模型层验证"""
        if self.parent and self.parent.review_status != self.ReviewStatus.ACTIVE:
            raise ValidationError(_("父栏目必须处于已激活状态"))

    def __str__(self):
        return f"{self.name}(权限)" if self.staff_only or self.allowed_groups.exists(
        ) else f"{self.name}"

    def save(self, *args, **kwargs):
        """保存时的自定义处理"""
        # 先执行路径计算

        # 强制调用完整验证
        self.full_clean()
        if self.name:
            self.name = self.name.strip()
        # 在保存前处理 subdomain 字段
        if self.subdomain:
            # 新的正则表达式模式，用于匹配并提取可能的多级子域名（例如二级、三级域名）
            # 示例: 从 "https://sub.admin.example.com" 中提取 "sub.admin"
            # 模式解释:
            # ^(https?://)?  -> 可选的http://或https://
            # (www\.)?       -> 可选的www.
            # (?P<subdomain>([a-zA-Z0-9_-]+\.)+[a-zA-Z0-9_-]+) -> 核心部分，匹配并命名捕获组"subdomain"，用于匹配类似"sub.admin"、"l1.l2.l3"这样的多级子域名
            # (?::\d+)?      -> 可选的端口号（如:8080）
            # (/.*)?$        -> 可选的路径及后续内容
            pattern = r'^(https?://)?(?P<subdomain>([a-zA-Z0-9_-]+\.)+[a-zA-Z0-9_-]+)(?::\d+)?(/.*)?$'
            match = re.match(pattern, self.subdomain)
            if match:
                # 提取匹配到的子域名部分（可能包含多个层级）
                extracted_subdomain = match.group('subdomain')
                # 可选：如果你确定只需要最后两级（例如三级域名中的后两级），可以在这里进行进一步处理
                # 例如，使用 split('.') 然后取最后两个部分
                # 但根据你的需求“不止保存纯净的二级域名部分，可以为三级域名”，这里直接使用提取的字符串
                self.subdomain = extracted_subdomain
            else:
                # 如果匹配失败，可以选择清空字段或保留原值（根据业务逻辑决定）
                # 这里示例选择清空，确保数据格式的纯净
                self.subdomain = None

        # 事务保障原子性
        try:
            with transaction.atomic():
                # 先保存对象到数据库
                result = super().save(*args, **kwargs)
                
                # 保存成功后，将新的slug添加到限制词库
                # 只有在slug有值且对象是新创建的情况下才添加
                if self.slug and not kwargs.get('raw', False):
                    RestrictedTermManager.add_restricted_terms(self.slug)
                    print(f"新slug '{self.slug}' 已自动添加到限制词库。")  # 可选：日志记录
                
                return result
        except ValidationError:
            raise
        except IntegrityError as e:
            if 'unique_category_slug' in str(e):
                raise ValidationError({'slug': _("标识冲突，请重新输入")})
            raise
    def delete(self, *args, **kwargs):
        """删除对象时，可选：将其slug从限制词库中移除（根据你的业务逻辑决定）。"""
        # 注意：谨慎使用，如果其他模型可能引用此词，则不应移除
        # RestrictedTermManager.remove_restricted_term(self.slug)
        super().delete(*args, **kwargs)

    def get_absolute_url(self):
        """生成符合URL规则的多级分类地址"""
        # 获取当前URL配置参数（假设从配置中获取）
        # 优先使用当前栏目的 subdomain，如果没有则向上查找其祖先栏目中第一个有 subdomain 的。
        # 生成一个唯一的缓存键
        cache_key = f"category_{self.id}_absolute_url"

        # 尝试从缓存中获取
        cached_url = cache.get(cache_key)
        if cached_url is not None:
            return cached_url
        # 只检查当前栏目和根栏目 ---
        target_domain = self.subdomain  # 首先检查自己

        # 如果自己没设置，再去获取根栏目并检查它的 subdomain
        if not target_domain:
            root_category = self.get_root()  # 使用 MPTT 方法获取根节点
            target_domain = root_category.subdomain

        # 假设如果找到了 target_domain，就构建 URL
        if target_domain:
            url = f"//{target_domain}"
            # 设置为永久缓存（或者你也可以设置为一个很长的超时时间）
            cache.set(cache_key, url, timeout=None)
            return url
        url_config = UrlStyle.get_solo()

        # 构建基础路径
        slug_chain = '/'.join(
            [c.slug for c in self.get_ancestors(include_self=True)])

        base = self._get_base_path(url_config.url_front, slug_chain,
                                   url_config.custom_slug)

        # 处理中间标识符
        identifier = self._get_identifier(url_config.url_mid,
                                          url_config.url_front)
        if not url_config.url_suffix:
            identifier = ''
        subdomain_flag = self.check_subdomain_flag()
        if subdomain_flag:
            site_config = SiteSettings.get_solo()
            dns_host = "//" + site_config.site_url.split('//')[1]
            return f"{dns_host}/{base}{identifier}{url_config.url_suffix}"
        # 拼接完整URL
        return f"/{base}{identifier}{url_config.url_suffix}"

    def _get_base_path(self, front, slug_chain, custom_slug):
        """根据前端配置生成基础路径"""
        base_map = {
            'model': f'{self._meta.model_name}/',
            'slug': f'{self.slug}/',
            'moreslug': f'{slug_chain}/',  # 根据实际情况调整多级slug
            'random': f'{self.slug}/',
            'custom': f'{custom_slug}/' if custom_slug else f'{self.slug}/'
        }
        return base_map.get(front, '')

    def _get_identifier(self, mid, front):
        """生成中间标识符"""
        # 特殊规则：当使用slug类配置时自动添加index
        if front in ['slug', 'moreslug', 'random']:
            return 'index'
        if mid == 'id':
            return str(self.pk)
        elif mid == 'hash':
            return hash_id(self.pk)  # 示例哈希值，实际需生成逻辑

        return ''

    @classmethod
    def get_tree_list(cls,
                      obj_id=None,
                      obj=None,
                      review_status=1,
                      nav_status=1):
        """
        生成树形结构数据（支持状态过滤）
        参数规则：
        - id 和 obj 参数互斥（只能二选一）
        - review_status/nav_status=99 表示不限制该状态
        """
        # 参数互斥校验
        if obj_id is not None and obj is not None:
            raise ValidationError("id和obj参数不能同时存在")

        # 状态参数有效性校验
        valid_status = {
            'review': (99, *cls.ReviewStatus.values),
            'nav': (99, *cls.NavStatus.values)
        }
        if review_status not in valid_status['review']:
            raise ValueError(
                f"无效审核状态值：{review_status}，可选值：{valid_status['review']}")
        if nav_status not in valid_status['nav']:
            raise ValueError(f"无效导航状态值：{nav_status}，可选值：{valid_status['nav']}")

        # 确定根节点
        root_node = None
        if obj is not None:
            root_node = obj
        elif obj_id is not None:
            try:
                root_node = cls.objects.get(pk=obj_id)
            except cls.DoesNotExist:
                return []

        # 构建基础查询集
        queryset = cls.objects.all()

        # 应用状态过滤
        if review_status != 99:
            queryset = queryset.filter(review_status=review_status)
        if nav_status != 99:
            queryset = queryset.filter(nav_status=nav_status)

        # 处理子树范围
        if root_node:
            descendants = root_node.get_descendants(include_self=True)
            queryset = queryset.filter(id__in=descendants)

        return queryset

    @property
    def full_template_path(self):
        return os.path.join(self.template_path, self.slug)

    @property
    def content_type_chinese_name(self):
        content_type = ContentType.objects.get(id=self.content_type_id)
        model_class = content_type.model_class()
        return getattr(model_class._meta, 'verbose_name', content_type.model)

    @classmethod
    def get_or_create_with_slug(cls, **kwargs):
        """
        增强版get_or_create方法，自动处理slug字段
        参数:
            name: 必填字段，作为查找依据
            defaults: 创建时使用的默认值字典
        返回:
            (object, created) 元组
        """
        from django.core.exceptions import ValidationError
        from django.db import transaction

        # 参数预处理
        name = kwargs.get('name')
        defaults = kwargs.get('defaults', {})

        # 合并直接传递的参数
        for key, value in kwargs.items():
            if key not in {'name', 'defaults'}:
                defaults[key] = value

        # 强制验证content_type
        if 'content_type' in defaults and not defaults['content_type']:
            raise ValidationError({'content_type': ['该字段不能为null']})

        # 自动生成slug逻辑
        existing_slugs = set(cls.objects.values_list('slug', flat=True))
        if not defaults.get('slug'):
            defaults['slug'] = generate_slug(name, existing_slugs)

        with transaction.atomic():
            # 执行更新/创建操作
            obj, created = cls.objects.get_or_create(name=name,
                                                     defaults=defaults)

            # 二次验证slug唯一性
            if cls.objects.filter(slug=obj.slug).exclude(pk=obj.pk).exists():
                obj.slug = generate_slug(name, existing_slugs)
                obj.save()
        return obj, created

    @classmethod
    def check_subdomain_flag(cls):
        """
        检查是否存在 subdomain 字段不为空的 Category 记录，使用缓存。
        返回布尔值。
        """
        cache_key = 'category_subdomain_flag'
        flag = cache.get(cache_key)

        if flag is None:
            exists = cls.objects.filter(subdomain__isnull=False).exclude(
                subdomain='').exists()
            cache.set(cache_key, exists, timeout=None)  # 或者设置具体的超时时间
            return exists
        return flag
