"""
告警检测引擎 - 检测品牌可见性变化并生成告警

告警类型：
- score_drop: 评分下降超过阈值（默认5分）
- score_rise: 评分上升超过阈值（默认5分）
- negative_sentiment: 出现负面情感
- competitor_overtake: 竞品超越（竞品评分超过我方）
- new_platform_mention: 新平台出现提及

严重程度：
- critical: 需要立即关注（如评分大幅下降、负面情感）
- warning: 需要留意（如评分小幅下降、竞品接近）
- info: 一般信息（如评分上升、新平台提及）
"""
from __future__ import annotations

import logging
import uuid
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from typing import Optional

from sqlalchemy import select, and_, func
from sqlalchemy.ext.asyncio import AsyncSession

from app.models.alert import Alert
from app.models.alert_setting import AlertSetting
from app.models.brand import Brand
from app.models.citation_record import CitationRecord
from app.models.competitor import Competitor
from app.models.query import Query

logger = logging.getLogger(__name__)


# ============================================================
# 默认告警配置
# ============================================================

DEFAULT_ALERT_CONFIGS = {
    "score_drop": {
        "enabled": True,
        "threshold": 5.0,
        "severity": "warning",
    },
    "score_rise": {
        "enabled": True,
        "threshold": 5.0,
        "severity": "info",
    },
    "negative_sentiment": {
        "enabled": True,
        "threshold": 1.0,  # 出现1次负面即触发
        "severity": "critical",
    },
    "competitor_overtake": {
        "enabled": True,
        "threshold": 0.0,  # 只要超越即触发
        "severity": "warning",
    },
    "new_platform_mention": {
        "enabled": True,
        "threshold": 1.0,  # 新平台出现1次提及即触发
        "severity": "info",
    },
}


# ============================================================
# 数据结构
# ============================================================

@dataclass
class AlertContext:
    """告警检测上下文，包含检测所需的所有数据"""
    brand_id: uuid.UUID
    brand_name: str
    user_id: uuid.UUID
    current_score: float
    previous_score: Optional[float]
    sentiment_counts: dict[str, int]  # {"positive": int, "neutral": int, "negative": int}
    brand_mentions: int
    competitor_mentions: dict[str, int]  # {competitor_name: mention_count}
    competitor_scores: dict[str, float]  # {competitor_name: score}
    current_platforms: set[str]  # 当前已有提及的平台集合
    new_platforms: set[str]  # 新出现提及的平台集合


# ============================================================
# 告警检测引擎
# ============================================================

class AlertEngine:
    """告警检测引擎"""

    def __init__(self, db: AsyncSession):
        self.db = db

    async def get_alert_setting(
        self,
        brand_id: uuid.UUID,
        alert_type: str,
    ) -> AlertSetting | None:
        """获取指定品牌和告警类型的设置"""
        stmt = select(AlertSetting).where(
            and_(
                AlertSetting.brand_id == brand_id,
                AlertSetting.alert_type == alert_type,
            )
        )
        result = await self.db.execute(stmt)
        return result.scalar_one_or_none()

    async def is_alert_enabled(
        self,
        brand_id: uuid.UUID,
        alert_type: str,
    ) -> bool:
        """检查告警是否启用"""
        setting = await self.get_alert_setting(brand_id, alert_type)
        if setting is None:
            # 没有设置记录时使用默认值
            return DEFAULT_ALERT_CONFIGS.get(alert_type, {}).get("enabled", True)
        return setting.enabled

    async def get_threshold(
        self,
        brand_id: uuid.UUID,
        alert_type: str,
    ) -> float:
        """获取告警阈值"""
        setting = await self.get_alert_setting(brand_id, alert_type)
        if setting is not None and setting.threshold is not None:
            return setting.threshold
        return DEFAULT_ALERT_CONFIGS.get(alert_type, {}).get("threshold", 5.0)

    async def _create_alert(
        self,
        brand_id: uuid.UUID,
        user_id: uuid.UUID,
        alert_type: str,
        severity: str,
        title: str,
        message: str,
        data: dict | None = None,
    ) -> Alert | None:
        """创建告警记录（内部方法）"""
        # 检查是否启用
        if not await self.is_alert_enabled(brand_id, alert_type):
            logger.debug(f"告警类型 {alert_type} 对品牌 {brand_id} 已禁用，跳过")
            return None

        # 防止重复告警：检查最近1小时内是否已有相同类型的告警
        one_hour_ago = datetime.now(timezone.utc) - timedelta(hours=1)
        existing_stmt = select(Alert).where(
            and_(
                Alert.brand_id == brand_id,
                Alert.alert_type == alert_type,
                Alert.created_at >= one_hour_ago,
            )
        )
        existing_result = await self.db.execute(existing_stmt)
        existing = existing_result.scalar_one_or_none()

        if existing:
            logger.debug(f"品牌 {brand_id} 最近1小时已有 {alert_type} 告警，跳过重复创建")
            return None

        alert = Alert(
            brand_id=brand_id,
            user_id=user_id,
            alert_type=alert_type,
            severity=severity,
            title=title,
            message=message,
            data=data,
            is_read=False,
        )
        self.db.add(alert)
        await self.db.flush()

        logger.info(
            f"创建告警: brand={brand_id}, type={alert_type}, "
            f"severity={severity}, title={title}"
        )
        return alert

    # ============================================================
    # 五种告警检测方法
    # ============================================================

    async def check_score_drop(self, ctx: AlertContext) -> Alert | None:
        """
        检测评分下降

        当评分下降超过阈值时触发，严重程度根据下降幅度判断：
        - 下降超过20分: critical
        - 下降超过阈值: warning
        """
        if ctx.previous_score is None:
            return None

        threshold = await self.get_threshold(ctx.brand_id, "score_drop")
        drop = ctx.previous_score - ctx.current_score

        if drop < threshold:
            return None

        severity = "critical" if drop >= 20 else "warning"

        return await self._create_alert(
            brand_id=ctx.brand_id,
            user_id=ctx.user_id,
            alert_type="score_drop",
            severity=severity,
            title=f"{ctx.brand_name} 评分下降 {drop:.1f} 分",
            message=(
                f"品牌「{ctx.brand_name}」的可见性评分从 {ctx.previous_score:.1f} "
                f"下降至 {ctx.current_score:.1f}，下降了 {drop:.1f} 分。"
                f"请关注近期品牌在各AI平台的表现变化。"
            ),
            data={
                "previous_score": ctx.previous_score,
                "current_score": ctx.current_score,
                "drop": round(drop, 2),
            },
        )

    async def check_score_rise(self, ctx: AlertContext) -> Alert | None:
        """
        检测评分上升

        当评分上升超过阈值时触发，severity 固定为 info
        """
        if ctx.previous_score is None:
            return None

        threshold = await self.get_threshold(ctx.brand_id, "score_rise")
        rise = ctx.current_score - ctx.previous_score

        if rise < threshold:
            return None

        return await self._create_alert(
            brand_id=ctx.brand_id,
            user_id=ctx.user_id,
            alert_type="score_rise",
            severity="info",
            title=f"{ctx.brand_name} 评分上升 {rise:.1f} 分",
            message=(
                f"品牌「{ctx.brand_name}」的可见性评分从 {ctx.previous_score:.1f} "
                f"上升至 {ctx.current_score:.1f}，上升了 {rise:.1f} 分。"
                f"品牌在AI平台的表现有所提升。"
            ),
            data={
                "previous_score": ctx.previous_score,
                "current_score": ctx.current_score,
                "rise": round(rise, 2),
            },
        )

    async def check_negative_sentiment(self, ctx: AlertContext) -> Alert | None:
        """
        检测负面情感

        当出现负面情感时触发，严重程度根据负面数量判断：
        - 负面数量 >= 3: critical
        - 负面数量 >= 1: warning
        """
        negative_count = ctx.sentiment_counts.get("negative", 0)
        threshold = await self.get_threshold(ctx.brand_id, "negative_sentiment")

        if negative_count < threshold:
            return None

        severity = "critical" if negative_count >= 3 else "warning"

        total = sum(ctx.sentiment_counts.values())
        negative_rate = (negative_count / total * 100) if total > 0 else 0

        return await self._create_alert(
            brand_id=ctx.brand_id,
            user_id=ctx.user_id,
            alert_type="negative_sentiment",
            severity=severity,
            title=f"{ctx.brand_name} 检测到负面情感",
            message=(
                f"品牌「{ctx.brand_name}」在AI回答中检测到 {negative_count} 条负面提及"
                f"（占比 {negative_rate:.1f}%）。"
                f"请关注AI平台对品牌的负面评价内容，及时采取应对措施。"
            ),
            data={
                "negative_count": negative_count,
                "total_count": total,
                "negative_rate": round(negative_rate, 2),
                "sentiment_counts": ctx.sentiment_counts,
            },
        )

    async def check_competitor_overtake(self, ctx: AlertContext) -> Alert | None:
        """
        检测竞品超越

        当竞品评分超过我方时触发，严重程度根据差距判断：
        - 竞品领先超过10分: critical
        - 竞品领先: warning
        """
        if not ctx.competitor_scores:
            return None

        overtake_competitors = []
        for comp_name, comp_score in ctx.competitor_scores.items():
            if comp_score > ctx.current_score:
                overtake_competitors.append({
                    "name": comp_name,
                    "score": comp_score,
                    "gap": round(comp_score - ctx.current_score, 2),
                })

        if not overtake_competitors:
            return None

        # 找出差距最大的竞品
        max_gap_competitor = max(overtake_competitors, key=lambda x: x["gap"])
        severity = "critical" if max_gap_competitor["gap"] >= 10 else "warning"

        comp_names = ", ".join(c["name"] for c in overtake_competitors)

        return await self._create_alert(
            brand_id=ctx.brand_id,
            user_id=ctx.user_id,
            alert_type="competitor_overtake",
            severity=severity,
            title=f"竞品 {comp_names} 评分已超越 {ctx.brand_name}",
            message=(
                f"品牌「{ctx.brand_name}」当前评分 {ctx.current_score:.1f}，"
                f"已被竞品「{comp_names}」超越。"
                f"最大差距为 {max_gap_competitor['gap']:.1f} 分"
                f"（{max_gap_competitor['name']}：{max_gap_competitor['score']:.1f}）。"
                f"建议关注竞品策略，优化品牌在AI平台的表现。"
            ),
            data={
                "brand_score": ctx.current_score,
                "overtake_competitors": overtake_competitors,
            },
        )

    async def check_new_platform_mention(self, ctx: AlertContext) -> Alert | None:
        """
        检测新平台出现提及

        当品牌在之前未出现的平台上被提及时触发，severity 固定为 info
        """
        if not ctx.new_platforms:
            return None

        platform_names = ", ".join(ctx.new_platforms)

        # 平台名称映射
        platform_labels = {
            "wenxin": "文心一言",
            "kimi": "Kimi",
            "tongyi": "通义千问",
            "doubao": "豆包",
            "xinghuo": "讯飞星火",
            "tiangong": "天工AI",
            "qingyan": "智谱清言",
            "search_engine": "搜索引擎",
        }

        display_names = [
            platform_labels.get(p, p) for p in ctx.new_platforms
        ]
        display_text = ", ".join(display_names)

        return await self._create_alert(
            brand_id=ctx.brand_id,
            user_id=ctx.user_id,
            alert_type="new_platform_mention",
            severity="info",
            title=f"{ctx.brand_name} 在新平台 {display_text} 被提及",
            message=(
                f"品牌「{ctx.brand_name}」在新的AI平台「{display_text}」中首次被提及。"
                f"这是一个积极的信号，表明品牌的影响力正在扩展到更多平台。"
            ),
            data={
                "new_platforms": list(ctx.new_platforms),
                "existing_platforms": list(ctx.current_platforms - ctx.new_platforms),
            },
        )

    # ============================================================
    # 综合检测入口
    # ============================================================

    async def detect_all(self, ctx: AlertContext) -> list[Alert]:
        """
        执行所有告警检测

        Args:
            ctx: 告警检测上下文

        Returns:
            生成的告警列表
        """
        alerts: list[Alert] = []

        check_methods = [
            self.check_score_drop,
            self.check_score_rise,
            self.check_negative_sentiment,
            self.check_competitor_overtake,
            self.check_new_platform_mention,
        ]

        for check_method in check_methods:
            try:
                alert = await check_method(ctx)
                if alert is not None:
                    alerts.append(alert)
            except Exception as e:
                logger.error(
                    f"告警检测失败: method={check_method.__name__}, "
                    f"brand={ctx.brand_id}, error={e}"
                )

        return alerts

    # ============================================================
    # 便捷方法：从评分结果构建上下文并检测
    # ============================================================

    async def detect_after_scoring(
        self,
        brand_id: uuid.UUID,
        brand_name: str,
        user_id: uuid.UUID,
        current_score: float,
        sentiment_counts: dict[str, int],
        brand_mentions: int,
        competitor_mentions: dict[str, int],
        competitor_scores: dict[str, float] | None = None,
        current_platforms: set[str] | None = None,
        new_platforms: set[str] | None = None,
    ) -> list[Alert]:
        """
        评分计算后执行告警检测

        自动获取上一次评分进行对比，构建告警上下文后执行所有检测。

        Args:
            brand_id: 品牌ID
            brand_name: 品牌名称
            user_id: 用户ID
            current_score: 当前评分
            sentiment_counts: 情感分布统计
            brand_mentions: 品牌提及次数
            competitor_mentions: 竞品提及次数
            competitor_scores: 竞品评分
            current_platforms: 当前已有提及的平台集合
            new_platforms: 新出现提及的平台集合

        Returns:
            生成的告警列表
        """
        # 获取上一次评分（从最近的历史告警数据中获取，或从评分历史获取）
        previous_score = await self._get_previous_score(brand_id, user_id)

        ctx = AlertContext(
            brand_id=brand_id,
            brand_name=brand_name,
            user_id=user_id,
            current_score=current_score,
            previous_score=previous_score,
            sentiment_counts=sentiment_counts,
            brand_mentions=brand_mentions,
            competitor_mentions=competitor_mentions,
            competitor_scores=competitor_scores or {},
            current_platforms=current_platforms or set(),
            new_platforms=new_platforms or set(),
        )

        return await self.detect_all(ctx)

    async def _get_previous_score(
        self,
        brand_id: uuid.UUID,
        user_id: uuid.UUID,
    ) -> float | None:
        """
        获取品牌上一次评分

        从 score_drop 或 score_rise 类型的告警数据中获取，
        如果没有则从引用记录中估算。
        """
        # 尝试从最近的评分变化告警中获取
        recent_alert_stmt = (
            select(Alert)
            .where(
                and_(
                    Alert.brand_id == brand_id,
                    Alert.alert_type.in_(["score_drop", "score_rise"]),
                )
            )
            .order_by(Alert.created_at.desc())
            .limit(1)
        )
        result = await self.db.execute(recent_alert_stmt)
        recent_alert = result.scalar_one_or_none()

        if recent_alert and recent_alert.data:
            return recent_alert.data.get("current_score")

        # 没有历史告警，尝试从引用记录估算历史评分
        return await self._estimate_previous_score(brand_id, user_id)

    async def _estimate_previous_score(
        self,
        brand_id: uuid.UUID,
        user_id: uuid.UUID,
    ) -> float | None:
        """
        从历史引用记录估算上一次评分

        使用昨天的引用数据计算一个简化的评分
        """
        brand_stmt = select(Brand).where(Brand.id == brand_id)
        brand_result = await self.db.execute(brand_stmt)
        brand = brand_result.scalar_one_or_none()

        if not brand:
            return None

        # 获取昨天的引用数据
        yesterday = datetime.now(timezone.utc).date() - timedelta(days=1)
        today_start = datetime.combine(yesterday, datetime.min.time()).replace(
            tzinfo=timezone.utc
        )
        today_end = datetime.combine(
            yesterday + timedelta(days=1), datetime.min.time()
        ).replace(tzinfo=timezone.utc)

        queries_stmt = select(Query).where(
            and_(
                Query.user_id == user_id,
                Query.target_brand == brand.name,
            )
        )
        queries_result = await self.db.execute(queries_stmt)
        queries = list(queries_result.scalars().all())

        if not queries:
            return None

        query_ids = [q.id for q in queries]

        citations_stmt = select(CitationRecord).where(
            and_(
                CitationRecord.query_id.in_(query_ids),
                CitationRecord.queried_at >= today_start,
                CitationRecord.queried_at < today_end,
            )
        )
        citations_result = await self.db.execute(citations_stmt)
        citations = list(citations_result.scalars().all())

        if not citations:
            return None

        # 简化评分：提及率 * 100
        cited_count = sum(1 for c in citations if c.cited)
        total_count = len(citations)
        if total_count > 0:
            return round((cited_count / total_count) * 100, 2)

        return None

    # ============================================================
    # 告警清理
    # ============================================================

    async def cleanup_old_alerts(self, days: int = 90) -> int:
        """
        清理超过指定天数的旧告警

        Args:
            days: 保留天数，默认90天

        Returns:
            删除的告警数量
        """
        cutoff = datetime.now(timezone.utc) - timedelta(days=days)
        stmt = select(Alert).where(Alert.created_at < cutoff)
        result = await self.db.execute(stmt)
        old_alerts = list(result.scalars().all())

        count = len(old_alerts)
        for alert in old_alerts:
            await self.db.delete(alert)

        await self.db.flush()

        if count > 0:
            logger.info(f"已清理 {count} 条超过 {days} 天的旧告警")

        return count

    # ============================================================
    # 初始化默认告警设置
    # ============================================================

    async def ensure_default_settings(
        self,
        brand_id: uuid.UUID,
        user_id: uuid.UUID,
    ) -> list[AlertSetting]:
        """
        确保品牌有默认的告警设置

        如果某种告警类型没有设置记录，则创建默认设置。

        Returns:
            所有告警设置列表
        """
        settings: list[AlertSetting] = []

        for alert_type, config in DEFAULT_ALERT_CONFIGS.items():
            existing = await self.get_alert_setting(brand_id, alert_type)
            if existing is None:
                setting = AlertSetting(
                    brand_id=brand_id,
                    user_id=user_id,
                    alert_type=alert_type,
                    enabled=config["enabled"],
                    threshold=config["threshold"],
                )
                self.db.add(setting)
                settings.append(setting)
            else:
                settings.append(existing)

        await self.db.flush()
        return settings
