"""维度1：情绪趋势检测"""

from __future__ import annotations

import json
from datetime import datetime, timedelta
from typing import Optional

import numpy as np

from cogprofile.detectors.base import BaseDetector, register_detector
from cogprofile.models import DetectedEmotionTrend as EmotionTrend, Insight, InsightType, Memory

# ============================================================
# sentiment scoring提示词
# ============================================================

SENTIMENT_SYSTEM_PROMPT = """你是一个sentiment analysis专家。
给定一条用户的对话记忆，请评估其中反映的情感状态。

返回JSON格式：
{"score": 0.5, "keywords": ["开心", "期待"]}

评分规则：
- score: -1.0（极度负面）到 1.0（极度正面），0为中性
- keywords: 提取1-3个情感关键词

只返回JSON，不要任何其他内容。"""

# ============================================================
# 趋势分析提示词
# ============================================================

TREND_SUMMARY_SYSTEM_PROMPT = """你是一个用户认知分析师。
根据以下用户在不同时间段的情感数据，用一句自然的中文总结情绪变化趋势。

要求：
- 像朋友一样说话，不要用学术用语
- 要具体引用用户说过的话作为证据
- 一句话，不超过80个字"""


@register_detector(InsightType.EMOTION_TREND)
class EmotionTrendDetector(BaseDetector):
    """
    情绪趋势检测器。

    工作流程：
    1. 对每条记忆做sentiment scoring（如果还没有的话）
    2. 按时间窗口分段计算平均分
    3. 用线性回归检测趋势方向
    4. 如果变化显著，用LLM生成自然语言总结
    """

    async def detect(
        self,
        memories: list[Memory],
        user_id: str,
        window_days: int = 14,
        min_memories: int = 5,
        significance_threshold: float = 0.2,
        **kwargs,
    ) -> list[Insight]:

        if len(memories) < min_memories:
            return []

        # 第1步：确保每条记忆都有sentiment score
        await self._fill_sentiment_scores(memories)

        scored = [m for m in memories if m.sentiment_score is not None]
        if len(scored) < min_memories:
            return []

        # 第2步：计算趋势
        now = datetime.now()
        window_start = now - timedelta(days=window_days)
        window_memories = [m for m in scored if m.created_at >= window_start]

        if len(window_memories) < 3:
            return []

        # 把时间转换为数值（距离窗口起点的天数），做线性回归
        t0 = window_memories[0].created_at.timestamp()
        x = np.array([(m.created_at.timestamp() - t0) / 86400 for m in window_memories])
        y = np.array([m.sentiment_score for m in window_memories])

        # 简单线性回归：y = slope * x + intercept
        slope, intercept = np.polyfit(x, y, 1)

        start_score = float(np.mean(y[:len(y)//3])) if len(y) >= 3 else float(y[0])
        end_score = float(np.mean(y[-len(y)//3:])) if len(y) >= 3 else float(y[-1])
        delta = end_score - start_score

        # 第3步：判断是否显著
        if abs(delta) < significance_threshold:
            return []

        # 确定方向
        if delta > 0.3:
            direction = "rising"
        elif delta < -0.3:
            direction = "declining"
        elif np.std(y) > 0.4:
            direction = "volatile"
        else:
            direction = "stable"

        # 第4步：用LLM生成总结
        evidence_items = self._select_evidence(window_memories, direction)
        summary = await self._generate_summary(
            direction, evidence_items, window_days
        )

        return [EmotionTrend(
            confidence=min(0.95, 0.5 + abs(delta)),
            summary=summary,
            evidence=[f"{m.created_at.strftime('%m/%d')}: {m.content[:50]}" for m in evidence_items],
            direction=direction,
            period_days=window_days,
            start_score=round(start_score, 2),
            end_score=round(end_score, 2),
            delta=round(delta, 2),
        )]

    async def _fill_sentiment_scores(self, memories: list[Memory]):
        """为没有sentiment score的记忆批量打分"""
        unscored = [m for m in memories if m.sentiment_score is None]
        if not unscored:
            return

        # 批量处理，每次最多20条（控制token用量）
        for i in range(0, len(unscored), 20):
            batch = unscored[i:i+20]
            prompt = "\n".join(
                f"[{j}] {m.content[:200]}" for j, m in enumerate(batch)
            )
            try:
                result = await self.llm.chat_json(
                    system_prompt=(
                        "你是sentiment analysis专家。对以下每条记忆评估sentiment score。\n"
                        "返回JSON数组，每个元素格式：{\"index\": 0, \"score\": 0.5}\n"
                        "score范围：-1.0（极度负面）到 1.0（极度正面），0为中性。\n"
                        "只返回JSON数组，不要其他内容。"
                    ),
                    user_prompt=prompt,
                )
                scores = json.loads(result)
                for item in scores:
                    idx = item.get("index", -1)
                    if 0 <= idx < len(batch):
                        batch[idx].sentiment_score = float(item["score"])
            except (json.JSONDecodeError, KeyError, ValueError):
                # 解析失败则跳过这批，下次再试
                continue

    def _select_evidence(
        self, memories: list[Memory], direction: str, count: int = 3
    ) -> list[Memory]:
        """选择最能说明趋势的记忆作为证据"""
        sorted_by_score = sorted(
            memories,
            key=lambda m: m.sentiment_score or 0,
            reverse=(direction == "rising"),
        )
        # 取首尾和中间各一条
        if len(sorted_by_score) >= 3:
            return [
                sorted_by_score[0],
                sorted_by_score[len(sorted_by_score)//2],
                sorted_by_score[-1],
            ]
        return sorted_by_score[:count]

    async def _generate_summary(
        self, direction: str, evidence: list[Memory], window_days: int
    ) -> str:
        """用LLM生成自然语言总结"""
        direction_cn = {
            "rising": "在好转",
            "declining": "在下滑",
            "volatile": "起伏很大",
            "stable": "比较稳定",
        }.get(direction, "有变化")

        evidence_text = "\n".join(
            f"- {m.created_at.strftime('%m月%d日')}: \"{m.content[:100]}\" (情感分: {m.sentiment_score:.1f})"
            for m in evidence
        )

        result = await self.llm.chat(
            system_prompt=TREND_SUMMARY_SYSTEM_PROMPT,
            user_prompt=(
                f"过去{window_days}天，用户的情绪整体{direction_cn}。\n\n"
                f"关键证据：\n{evidence_text}\n\n"
                f"请用一句话总结。"
            ),
        )
        return result.strip()
