from django.db import models
from django.conf import settings
from django.contrib.auth.models import AbstractUser, Group, Permission
from django.db.models import UniqueConstraint, Q
from rest_framework.authtoken.models import Token as DefaultToken


class ParadiseUser(AbstractUser):
    """
    自定义用户模型
    """
    phone_number = models.CharField('手机号', max_length=20, unique=True, null=True, blank=True)
    email = models.EmailField('邮箱', unique=True, null=True, blank=True)

    # 新增用户信息字段
    GENDER_CHOICES = (
        ('M', '男'),
        ('F', '女'),
        ('O', '其他'),
    )

    MBTI_CHOICES = (
        ('INTJ', 'INTJ'), ('INTP', 'INTP'), ('ENTJ', 'ENTJ'), ('ENTP', 'ENTP'),
        ('INFJ', 'INFJ'), ('INFP', 'INFP'), ('ENFJ', 'ENFJ'), ('ENFP', 'ENFP'),
        ('ISTJ', 'ISTJ'), ('ISFJ', 'ISFJ'), ('ESTJ', 'ESTJ'), ('ESFJ', 'ESFJ'),
        ('ISTP', 'ISTP'), ('ISFP', 'ISFP'), ('ESTP', 'ESTP'), ('ESFP', 'ESFP'),
    )

    favorability = models.IntegerField('好感度', default=0)
    gender = models.CharField('性别', max_length=1, choices=GENDER_CHOICES, null=True, blank=True)
    resident_city = models.CharField('常驻城市', max_length=50, null=True, blank=True)
    birthday = models.DateField('生日', null=True, blank=True)
    zodiac_sign = models.CharField('星座', max_length=20, null=True, blank=True)
    mbti = models.CharField('MBTI性格', max_length=4, choices=MBTI_CHOICES, null=True, blank=True)
    interests = models.TextField('兴趣爱好', null=True, blank=True)
    social_identity = models.CharField('社会身份', max_length=50, null=True, blank=True)

    groups = models.ManyToManyField(
        Group,
        related_name="paradiseuser_set",
        blank=True,
        help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
        related_query_name="user",
    )
    user_permissions = models.ManyToManyField(
        Permission,
        related_name="paradiseuser_set",
        blank=True,
        help_text="Specific permissions for this user.",
        related_query_name="user",
    )

    class Meta:
        verbose_name = '用户'
        verbose_name_plural = '用户'
        ordering = ['-date_joined']
        indexes = [
            models.Index(fields=['username']),  # 添加用户名索引
        ]
        constraints = [
            UniqueConstraint(
                fields=["phone_number"],
                condition=Q(phone_number__isnull=False),
                name="unique_phone_number",
            )
        ]

    def __str__(self):
        return self.username or self.phone_number or self.email

# class UserToken(DefaultToken):
#     user = models.OneToOneField(
#         settings.AUTH_USER_MODEL,
#         related_name='auth_token',
#         on_delete=models.CASCADE
#     )


class AffinityRule(models.Model):
    """好感度规则：定义哪些行为可以获得好感度积分

    P1-01/P1-02 扩展后字段：
        rule_key: 代码标识，客户端事件通过此匹配规则
        trigger_type: 触发类型 (action / companion_time / decay)
        min_change/max_change: 单次变化范围（[min, max] 闭区间随机取整数）
        single_cap/daily_cap/cooldown_seconds: 上限保护
        is_negative/is_enabled/is_deleted: 标记
        min_continuous_minutes/max_count_per_day: 陪伴时长专用字段
    旧字段 points/daily_limit/is_active 保留作为兼容字段，下个版本删除。
    """

    TRIGGER_TYPE_CHOICES = (
        ('action', '动作触发'),
        ('companion_time', '陪伴时长'),
        ('decay', '衰减'),
    )

    # 业务字段
    rule_key = models.CharField(
        '规则代码', max_length=50, unique=True, null=True, blank=True,
        help_text='代码标识，客户端事件通过此 key 匹配规则（如 chat/sing/dance/touch...）'
    )
    name = models.CharField('规则名称', max_length=100)
    description = models.TextField('规则描述', blank=True)
    trigger_type = models.CharField(
        '触发类型', max_length=20, choices=TRIGGER_TYPE_CHOICES, default='action'
    )

    # 变化值
    min_change = models.IntegerField(
        '最小变化值', default=1,
        help_text='单次变化最小值，负数表示扣分'
    )
    max_change = models.IntegerField(
        '最大变化值', default=1,
        help_text='单次变化最大值（含），实际值在 [min, max] 之间随机取整数'
    )

    # 上限保护
    single_cap = models.IntegerField(
        '单次上限', default=10,
        help_text='单次变化绝对值上限（保护性钳位）'
    )
    daily_cap = models.IntegerField(
        '每日上限', default=20,
        help_text='本规则在每台设备每日累计变化上限（绝对值）'
    )
    cooldown_seconds = models.IntegerField(
        '冷却时间(秒)', default=0,
        help_text='同一设备同一规则的最小触发间隔，0 表示无冷却'
    )

    # 标记
    is_negative = models.BooleanField('是否负向规则', default=False)
    is_enabled = models.BooleanField('已启用', default=True)
    is_deleted = models.BooleanField('已删除（软删除）', default=False)

    # 陪伴时长专用字段（P1-02）
    min_continuous_minutes = models.IntegerField(
        '最小连续分钟数', null=True, blank=True,
        help_text='trigger_type=companion_time 时使用：满 X 分钟触发 1 次'
    )
    max_count_per_day = models.IntegerField(
        '每日最多次数', null=True, blank=True,
        help_text='trigger_type=companion_time 时使用：每台设备每日最多触发次数'
    )

    # 兼容旧字段（已弃用，下个版本删除）
    points = models.IntegerField(
        '积分(已弃用)', default=0,
        help_text='已弃用，使用 min_change/max_change'
    )
    daily_limit = models.IntegerField(
        '每日上限(已弃用)', null=True, blank=True,
        help_text='已弃用，使用 daily_cap'
    )
    is_active = models.BooleanField(
        '已启用(已弃用)', default=True,
        help_text='已弃用，使用 is_enabled'
    )

    created_at = models.DateTimeField('创建时间', auto_now_add=True)
    updated_at = models.DateTimeField('更新时间', auto_now=True)

    class Meta:
        verbose_name = '好感度规则'
        verbose_name_plural = '好感度规则'
        ordering = ['-created_at']

    def __str__(self):
        return f"{self.name} ({self.rule_key or '-'})"


class AffinityLevel(models.Model):
    """好感度等级：定义不同好感度区间对应的等级和奖励

    P1-03 扩展后字段：
        min_affinity/max_affinity: 等级好感度区间（闭区间）
        unlock_content: 解锁内容文案
        reward_type: 奖励类型 (unlock / item / currency / mixed / none)
        reward_currency/reward_items: 奖励内容
        is_enabled/is_deleted: 标记
    旧字段 required_points/rewards 保留作为兼容字段，下个版本删除。
    """

    REWARD_TYPE_CHOICES = (
        ('unlock', '内容解锁'),
        ('item', '道具奖励'),
        ('currency', '虚拟货币'),
        ('mixed', '混合奖励'),
        ('none', '无奖励'),
    )

    level = models.IntegerField('等级', unique=True)
    name = models.CharField('等级名称', max_length=50)
    description = models.TextField('等级描述', blank=True)

    # 区间（P1-03）
    min_affinity = models.IntegerField(
        '最小好感度', default=0,
        help_text='等级好感度区间下限（闭区间）'
    )
    max_affinity = models.IntegerField(
        '最大好感度', default=0,
        help_text='等级好感度区间上限（闭区间）'
    )

    # 解锁/奖励
    unlock_content = models.TextField(
        '解锁内容', blank=True,
        help_text='展示文案，描述该等级解锁的功能/外观/剧情等'
    )
    reward_type = models.CharField(
        '奖励类型', max_length=10, choices=REWARD_TYPE_CHOICES, default='unlock'
    )
    reward_currency = models.IntegerField('虚拟货币奖励', default=0)
    reward_items = models.JSONField(
        '道具奖励', default=list,
        help_text='道具列表，如 [{"item_id": 1, "qty": 2}]'
    )

    is_enabled = models.BooleanField('已启用', default=True)
    is_deleted = models.BooleanField('已删除（软删除）', default=False)

    # 兼容旧字段（已弃用）
    required_points = models.IntegerField(
        '所需积分(已弃用)', default=0,
        help_text='已弃用，使用 min_affinity/max_affinity'
    )
    rewards = models.JSONField(
        '奖励(已弃用)', default=list,
        help_text='已弃用，使用 reward_currency/reward_items'
    )

    created_at = models.DateTimeField('创建时间', auto_now_add=True)
    updated_at = models.DateTimeField('更新时间', auto_now=True)

    class Meta:
        verbose_name = '好感度等级'
        verbose_name_plural = '好感度等级'
        ordering = ['level']

    def __str__(self):
        return f"Lv{self.level} {self.name}"


class AffinitySetting(models.Model):
    """好感度系统全局参数（单例表）— P1-04

    所有字段对应「好感度系统功能与规则设计.md」§3.2 全局参数 + §5.1 衰减字段。
    通过 get_solo() 取唯一实例，save() 强制单例。
    """

    # 全局参数
    initial_affinity = models.IntegerField(
        '初始好感度', default=10,
        help_text='新建 UserDevice 绑定时的初始好感度'
    )
    max_affinity = models.IntegerField(
        '最大好感度', default=100,
        help_text='好感度上限（管理员手动调整也不能突破）'
    )
    daily_cap = models.IntegerField(
        '每日全局增长上限', default=20,
        help_text='每台设备每日好感度净增长上限（跨规则汇总，仅限正向）'
    )

    # 衰减
    decay_rate = models.IntegerField('全局衰减速率(点/天)', default=2)
    decay_threshold = models.IntegerField(
        '衰减开始天数', default=3,
        help_text='设备多少天不互动后开始衰减'
    )
    decay_min_decay = models.IntegerField('单日衰减最小值', default=1)
    decay_max_decay = models.IntegerField('单日衰减最大值', default=3)
    decay_cap = models.IntegerField('单日衰减上限', default=5)
    decay_min_floor = models.IntegerField(
        '衰减下限', default=0,
        help_text='好感度不会低于此值'
    )

    # 通知/奖励
    enable_notify = models.BooleanField('启用变化通知', default=True)
    enable_rewards = models.BooleanField('启用等级奖励', default=True)
    notify_decay = models.BooleanField('衰减时通知', default=True)

    # 时区
    timezone = models.CharField(
        '时区', max_length=50, default='Asia/Shanghai',
        help_text='自然日 0 点重置基准（全用户统一）'
    )

    created_at = models.DateTimeField('创建时间', auto_now_add=True)
    updated_at = models.DateTimeField('更新时间', auto_now=True)

    class Meta:
        verbose_name = '好感度系统设置'
        verbose_name_plural = '好感度系统设置'

    def __str__(self):
        return f"AffinitySetting(initial={self.initial_affinity}, max={self.max_affinity})"

    def save(self, *args, **kwargs):
        # 强制单例：新增时如果已有记录则覆盖到现有 pk
        if not self.pk and AffinitySetting.objects.exists():
            existing = AffinitySetting.objects.first()
            self.pk = existing.pk
        super().save(*args, **kwargs)

    @classmethod
    def get_solo(cls):
        """获取单例实例，不存在则用默认值创建"""
        instance, _ = cls.objects.get_or_create(pk=1)
        return instance


class AffinityLog(models.Model):
    """好感度变化日志 — P1-05

    记录每次好感度变动的来源、值、上下文。
    rule 用 SET_NULL，规则被删除后日志保留；rule_key 冗余文本字段保证可读性。
    event_id 用于客户端事件幂等去重（部分唯一索引）。
    """

    SOURCE_CHOICES = (
        ('device_event', '设备端事件'),
        ('mobile_event', '手机端事件'),
        ('system_decay', '系统衰减'),
        ('admin_adjust_single', '管理员单次调整'),
        ('admin_adjust_batch', '管理员批量调整'),
    )

    # 关联对象
    user = models.ForeignKey(
        'userapp.ParadiseUser', on_delete=models.CASCADE,
        verbose_name='用户', related_name='affinity_logs'
    )
    device = models.ForeignKey(
        'device_interaction.UserDevice', on_delete=models.SET_NULL,
        verbose_name='用户设备绑定', related_name='affinity_logs',
        null=True, blank=True
    )
    rule = models.ForeignKey(
        'AffinityRule', on_delete=models.SET_NULL,
        verbose_name='规则', related_name='logs',
        null=True, blank=True
    )
    rule_key = models.CharField(
        '规则代码(冗余)', max_length=50, blank=True,
        help_text='冗余 rule_key 文本，规则被删除后仍可读'
    )

    # 变化数据
    change_value = models.IntegerField('变化值', help_text='正负整数，含符号')
    before_value = models.IntegerField('变化前值')
    after_value = models.IntegerField('变化后值')

    # 来源
    source = models.CharField('来源', max_length=30, choices=SOURCE_CHOICES)
    event_id = models.CharField(
        '事件ID', max_length=64, blank=True, db_index=True,
        help_text='客户端事件 UUID，用于幂等去重'
    )

    # 管理员调整专用
    operator_admin_id = models.IntegerField(
        '操作管理员ID', null=True, blank=True,
        help_text='source=admin_adjust_* 时记录操作管理员'
    )
    reason = models.TextField(
        '操作原因', blank=True,
        help_text='管理员调整时填写的原因/备注'
    )

    # 扩展上下文
    metadata = models.JSONField(
        '元数据', default=dict, blank=True,
        help_text='扩展字段，如对话 message_id、设备状态等'
    )

    created_at = models.DateTimeField('创建时间', auto_now_add=True, db_index=True)

    class Meta:
        verbose_name = '好感度变化日志'
        verbose_name_plural = '好感度变化日志'
        ordering = ['-created_at']
        indexes = [
            models.Index(fields=['device', '-created_at']),
            models.Index(fields=['user', '-created_at']),
            models.Index(fields=['rule_key', '-created_at']),
            models.Index(fields=['source', '-created_at']),
        ]
        constraints = [
            UniqueConstraint(
                fields=['event_id'],
                condition=Q(event_id__gt=''),
                name='unique_affinity_event_id',
            ),
        ]

    def __str__(self):
        return f"#{self.id} {self.rule_key or self.source} {self.before_value}->{self.after_value}"


class UserAffinityDailyCounter(models.Model):
    """好感度每日计数器（数据库兜底，热路径走 Redis）— P1-06

    Redis key 命名约定：
        daily:{device_id}:{rule_key}:{YYYYMMDD}  -> trigger_count + accumulated_change
        daily:{device_id}:_global:{YYYYMMDD}     -> 全局日上限（仅正向汇总）
    每晚定时任务把 Redis 当日数据落库，作为审计/查询兜底。
    """

    device = models.ForeignKey(
        'device_interaction.UserDevice', on_delete=models.CASCADE,
        verbose_name='用户设备绑定', related_name='daily_counters'
    )
    rule = models.ForeignKey(
        'AffinityRule', on_delete=models.CASCADE,
        verbose_name='规则', related_name='daily_counters'
    )
    date = models.DateField(
        '日期', db_index=True,
        help_text='Asia/Shanghai 时区的自然日'
    )
    accumulated_change = models.IntegerField('累计变化（含符号）', default=0)
    trigger_count = models.IntegerField('触发次数', default=0)

    created_at = models.DateTimeField('创建时间', auto_now_add=True)
    updated_at = models.DateTimeField('更新时间', auto_now=True)

    class Meta:
        verbose_name = '好感度每日计数器'
        verbose_name_plural = '好感度每日计数器'
        unique_together = [('device', 'rule', 'date')]
        indexes = [
            models.Index(fields=['date']),
        ]

    def __str__(self):
        return f"{self.device_id}/{self.rule_id}/{self.date}={self.accumulated_change}"


class UserLevelRewardGrant(models.Model):
    """等级奖励发放记录 — P1-07

    (device, level) 唯一，永久幂等防止重复发放。
    衰减回升再次跨过同等级也不补发；新增等级时已在区间的设备不立即触发。
    reward_snapshot 保存发放时的奖励内容快照，避免 AffinityLevel 后续修改影响审计。
    """

    device = models.ForeignKey(
        'device_interaction.UserDevice', on_delete=models.CASCADE,
        verbose_name='用户设备绑定', related_name='level_reward_grants'
    )
    level = models.IntegerField('等级')
    granted_at = models.DateTimeField('发放时间', auto_now_add=True)
    reward_snapshot = models.JSONField(
        '奖励快照', default=dict,
        help_text='发放时的奖励内容快照（防 AffinityLevel 后续修改）'
    )

    class Meta:
        verbose_name = '等级奖励发放记录'
        verbose_name_plural = '等级奖励发放记录'
        unique_together = [('device', 'level')]
        ordering = ['-granted_at']

    def __str__(self):
        return f"{self.device_id}/Lv{self.level}@{self.granted_at}"
