import json
import logging
import uuid
from datetime import datetime, timedelta, timezone
from collections import defaultdict

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

from app.models.citation_record import CitationRecord
from app.models.query import Query
from app.models.trend_insight import TrendInsight
from app.models.brand import Brand
from app.services.llm import LLMFactory, LLMError
from app.utils.json_extractor import extract_json

logger = logging.getLogger(__name__)


class TrendAnalyzerService:

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

    async def analyze_trends(
        self,
        brand_id: uuid.UUID,
        days: int = 30,
        platforms: list[str] | None = None,
        keywords: list[str] | None = None,
    ) -> dict:
        brand = await self._get_brand(brand_id)
        if brand is None:
            raise ValueError("品牌不存在")

        citations = await self.get_time_series_data(brand.name, days, platforms, keywords)
        if len(citations) < 7:
            now = datetime.now(timezone.utc)
            return {
                "status": "insufficient_data",
                "message": f"数据不足7天（当前{len(citations)}天），无法进行趋势分析",
                "brand_id": str(brand_id),
                "days": days,
            }

        aggregated = self._aggregate_time_series(citations, days)
        change_rate = self.calculate_change_rate(aggregated)
        classified_type = self.detect_trends(change_rate)
        absolute_change = self._calculate_absolute_change(aggregated)
        sentiment_trend = self._analyze_sentiment_trend(citations)
        platform_comparison = await self.compare_platforms(citations)
        cause_analysis = await self.infer_causes(
            brand_name=brand.name,
            keyword="",
            trend_type=classified_type,
            change_rate=change_rate,
            data_points=aggregated,
        )
        recommendations = self.generate_recommendations(
            classified_type, change_rate, sentiment_trend
        )
        confidence = self._calculate_confidence(len(citations), change_rate)
        severity = self._determine_severity(classified_type, change_rate)

        now = datetime.now(timezone.utc)
        period_start = now - timedelta(days=days)

        insight = TrendInsight(
            brand_id=str(brand_id),
            trend_type=classified_type,
            keyword=brand.name,
            platform="all",
            period_start=period_start,
            period_end=now,
            data_points=aggregated,
            change_rate=change_rate,
            absolute_change=absolute_change,
            sentiment_trend=sentiment_trend,
            cause_analysis=cause_analysis,
            recommendations=recommendations,
            confidence=confidence,
            severity=severity,
        )
        self.db.add(insight)
        await self.db.commit()
        await self.db.refresh(insight)

        return {
            "status": "success",
            "insight_id": str(insight.id),
            "trend_type": insight.trend_type,
            "change_rate": insight.change_rate,
            "absolute_change": insight.absolute_change,
            "sentiment_trend": insight.sentiment_trend,
            "cause_analysis": insight.cause_analysis,
            "recommendations": insight.recommendations,
            "confidence": insight.confidence,
            "severity": insight.severity,
            "data_points": insight.data_points,
        }

    async def get_time_series_data(
        self,
        brand_name: str,
        days: int,
        platforms: list[str] | None = None,
        keywords: list[str] | None = None,
    ) -> list[dict]:
        now = datetime.now(timezone.utc)
        start_date = now - timedelta(days=days)

        conditions = [
            Query.target_brand == brand_name,
            CitationRecord.queried_at >= start_date,
        ]
        if platforms:
            conditions.append(CitationRecord.platform.in_(platforms))
        if keywords:
            conditions.append(Query.keyword.in_(keywords))

        stmt = (
            select(
                CitationRecord.platform,
                CitationRecord.cited,
                CitationRecord.sentiment,
                CitationRecord.queried_at,
                Query.keyword,
            )
            .join(Query, CitationRecord.query_id == Query.id)
            .where(and_(*conditions))
            .order_by(CitationRecord.queried_at.asc())
        )
        result = await self.db.execute(stmt)

        citations = []
        for row in result.all():
            citations.append({
                "platform": row.platform,
                "cited": row.cited,
                "sentiment": row.sentiment or "neutral",
                "queried_at": row.queried_at,
                "keyword": row.keyword,
            })

        return citations

    def detect_trends(self, change_rate: float) -> str:
        if change_rate > 20:
            return "rising"
        elif change_rate < -20:
            return "declining"
        else:
            return "stable"

    def detect_hotspots(
        self,
        citations: list[dict],
        days: int = 30,
    ) -> list[dict]:
        if not citations:
            return []

        daily_counts = defaultdict(lambda: defaultdict(int))
        for c in citations:
            date_str = c.get("queried_at", datetime.now(timezone.utc)).strftime("%Y-%m-%d")
            keyword = c.get("keyword", "")
            daily_counts[keyword][date_str] += 1

        hotspots = []
        for keyword, daily in daily_counts.items():
            counts = list(daily.values())
            if len(counts) < 7:
                continue
            recent_7 = counts[-7:]
            mean_7 = sum(recent_7) / len(recent_7)
            latest = recent_7[-1]
            if mean_7 > 0 and latest > mean_7 * 2:
                hotspots.append({
                    "keyword": keyword,
                    "latest_count": latest,
                    "mean_7d": round(mean_7, 2),
                    "surge_ratio": round(latest / mean_7, 2),
                    "trend_type": "hotspot",
                })

        hotspots.sort(key=lambda x: x["surge_ratio"], reverse=True)
        return hotspots

    async def compare_platforms(self, citations: list[dict]) -> dict:
        platform_data = defaultdict(lambda: {"cited": 0, "total": 0, "positive": 0})
        for c in citations:
            platform = c.get("platform", "unknown")
            entry = platform_data[platform]
            entry["total"] += 1
            if c.get("cited"):
                entry["cited"] += 1
            if c.get("sentiment") == "positive":
                entry["positive"] += 1

        result = {}
        for platform, data in platform_data.items():
            citation_rate = (data["cited"] / data["total"] * 100) if data["total"] > 0 else 0.0
            positive_rate = (data["positive"] / data["total"] * 100) if data["total"] > 0 else 0.0
            result[platform] = {
                "citation_rate": round(citation_rate, 2),
                "positive_rate": round(positive_rate, 2),
                "total_mentions": data["total"],
            }

        return result

    async def infer_causes(
        self,
        brand_name: str,
        keyword: str,
        trend_type: str,
        change_rate: float,
        data_points: list[dict],
    ) -> str:
        trend_desc = {
            "rising": "上升",
            "declining": "下降",
            "stable": "平稳",
            "hotspot": "热点",
            "platform_shift": "平台迁移",
        }.get(trend_type, trend_type)

        messages = [
            {
                "role": "system",
                "content": "你是一个品牌趋势分析专家，请根据提供的数据推断趋势变化的可能原因。返回一段简洁的分析文本。",
            },
            {
                "role": "user",
                "content": (
                    f"品牌: {brand_name}\n"
                    f"关键词: {keyword or brand_name}\n"
                    f"趋势类型: {trend_desc}\n"
                    f"变化率: {change_rate}%\n"
                    f"时间序列数据(最近5点): {json.dumps(data_points[-5:], ensure_ascii=False)}\n\n"
                    f"请推断导致该趋势变化的可能原因，返回简洁的分析文本。"
                ),
            },
        ]

        try:
            provider = LLMFactory.get_default()
            response = await provider.chat(messages, temperature=0.3, max_tokens=1024)
            return response.content.strip()
        except (LLMError, Exception) as e:
            logger.warning(f"LLM推断原因失败: {e}")
            return f"品牌{brand_name}的引用趋势{trend_desc}，变化率{change_rate}%"

    def generate_recommendations(
        self,
        trend_type: str,
        change_rate: float,
        sentiment_trend: dict,
    ) -> list[str]:
        recommendations = []
        sentiment_direction = sentiment_trend.get("direction", "neutral") if sentiment_trend else "neutral"

        if trend_type == "rising":
            recommendations.append("引用趋势上升，建议加大内容投入以巩固优势")
            if sentiment_direction == "positive":
                recommendations.append("情感倾向正向提升，可强化正面内容传播")
            elif sentiment_direction == "negative":
                recommendations.append("虽然引用量上升但情感转负，需关注负面评价并积极回应")
        elif trend_type == "declining":
            recommendations.append("引用趋势下降，建议审查内容策略并优化关键词覆盖")
            recommendations.append("考虑增加高质量内容产出，提升AI引擎引用概率")
            if sentiment_direction == "negative":
                recommendations.append("情感倾向同步恶化，建议优先处理负面舆情")
        elif trend_type == "stable":
            recommendations.append("引用趋势平稳，建议探索新的关键词和内容方向以突破现状")
        elif trend_type == "hotspot":
            recommendations.append("检测到热点趋势，建议快速响应并产出相关内容以获取流量红利")
        elif trend_type == "platform_shift":
            recommendations.append("检测到平台迁移趋势，建议调整各平台内容策略以适应变化")

        if abs(change_rate) > 50:
            recommendations.append("变化幅度较大，建议密切关注后续走势并准备应对方案")

        return recommendations

    def calculate_change_rate(self, data_points: list[dict]) -> float:
        if len(data_points) < 2:
            return 0.0

        half = len(data_points) // 2
        previous = sum(p.get("citation_count", 0) for p in data_points[:half])
        current = sum(p.get("citation_count", 0) for p in data_points[half:])

        if previous == 0:
            return 100.0 if current > 0 else 0.0

        return round(((current - previous) / previous) * 100, 2)

    async def get_insights(
        self,
        brand_id: uuid.UUID,
        skip: int = 0,
        limit: int = 20,
    ) -> tuple[list[TrendInsight], int]:
        conditions = [TrendInsight.brand_id == str(brand_id)]
        stmt = (
            select(TrendInsight)
            .where(and_(*conditions))
            .order_by(TrendInsight.created_at.desc())
            .offset(skip)
            .limit(limit)
        )
        result = await self.db.execute(stmt)
        items = result.scalars().all()

        count_stmt = (
            select(func.count())
            .select_from(TrendInsight)
            .where(and_(*conditions))
        )
        count_result = await self.db.execute(count_stmt)
        total = count_result.scalar_one()

        return list(items), total

    async def get_insight_by_id(self, insight_id: uuid.UUID) -> TrendInsight | None:
        stmt = select(TrendInsight).where(TrendInsight.id == insight_id)
        result = await self.db.execute(stmt)
        return result.scalar_one_or_none()

    async def get_summary(
        self,
        brand_id: uuid.UUID,
        days: int = 30,
    ) -> dict:
        conditions = [TrendInsight.brand_id == str(brand_id)]
        stmt = (
            select(TrendInsight.trend_type, func.count().label("count"))
            .where(and_(*conditions))
            .group_by(TrendInsight.trend_type)
        )
        result = await self.db.execute(stmt)

        summary = {
            "brand_id": brand_id,
            "period_days": days,
            "rising_count": 0,
            "declining_count": 0,
            "hotspot_count": 0,
            "top_keywords": [],
            "platform_highlights": {},
        }
        for row in result.all():
            trend_type = row.trend_type
            count = row.count
            if trend_type == "rising":
                summary["rising_count"] = count
            elif trend_type == "declining":
                summary["declining_count"] = count
            elif trend_type == "hotspot":
                summary["hotspot_count"] = count

        keyword_stmt = (
            select(TrendInsight.keyword, func.count().label("count"))
            .where(and_(*conditions), TrendInsight.keyword.isnot(None))
            .group_by(TrendInsight.keyword)
            .order_by(func.count().desc())
            .limit(10)
        )
        keyword_result = await self.db.execute(keyword_stmt)
        summary["top_keywords"] = [row.keyword for row in keyword_result.all()]

        platform_stmt = (
            select(TrendInsight.platform, TrendInsight.trend_type, func.count().label("count"))
            .where(and_(*conditions), TrendInsight.platform.isnot(None))
            .group_by(TrendInsight.platform, TrendInsight.trend_type)
        )
        platform_result = await self.db.execute(platform_stmt)
        for row in platform_result.all():
            platform = row.platform
            if platform not in summary["platform_highlights"]:
                summary["platform_highlights"][platform] = {}
            summary["platform_highlights"][platform][row.trend_type] = row.count

        return summary

    async def get_hotspots(
        self,
        brand_id: uuid.UUID,
        days: int = 30,
    ) -> dict:
        brand = await self._get_brand(brand_id)
        if brand is None:
            raise ValueError("品牌不存在")

        citations = await self.get_time_series_data(brand.name, days)
        if len(citations) < 7:
            return {
                "status": "insufficient_data",
                "message": f"数据不足7天（当前{len(citations)}天），无法进行热点分析",
                "hotspots": [],
            }

        hotspots = self.detect_hotspots(citations, days)

        return {
            "status": "success",
            "hotspots": hotspots,
        }

    def _aggregate_time_series(self, citations: list[dict], days: int) -> list[dict]:
        daily = defaultdict(lambda: {"citation_count": 0, "positive_count": 0})
        for c in citations:
            date_str = c.get("queried_at", datetime.now(timezone.utc)).strftime("%Y-%m-%d")
            entry = daily[date_str]
            entry["citation_count"] += 1
            sentiment = c.get("sentiment", "neutral")
            if sentiment == "positive":
                entry["positive_count"] += 1

        result = []
        for date_str in sorted(daily.keys()):
            entry = daily[date_str]
            positive_ratio = entry["positive_count"] / entry["citation_count"] if entry["citation_count"] > 0 else 0.0
            result.append({
                "date": date_str,
                "citation_count": entry["citation_count"],
                "positive_ratio": round(positive_ratio, 4),
            })

        return result

    def _calculate_absolute_change(self, data_points: list[dict]) -> int:
        if len(data_points) < 2:
            return 0

        half = len(data_points) // 2
        previous = sum(p.get("citation_count", 0) for p in data_points[:half])
        current = sum(p.get("citation_count", 0) for p in data_points[half:])
        return current - previous

    def _analyze_sentiment_trend(self, citations: list[dict]) -> dict:
        if not citations:
            return {"direction": "neutral", "positive_ratio": 0.0, "negative_ratio": 0.0}

        half = len(citations) // 2
        first_half = citations[:half]
        second_half = citations[half:]

        def sentiment_ratios(records: list[dict]) -> dict:
            if not records:
                return {"positive": 0.0, "negative": 0.0}
            positive = sum(1 for r in records if r.get("sentiment") == "positive")
            negative = sum(1 for r in records if r.get("sentiment") == "negative")
            total = len(records)
            return {
                "positive": round(positive / total, 4),
                "negative": round(negative / total, 4),
            }

        prev = sentiment_ratios(first_half)
        curr = sentiment_ratios(second_half)
        diff = curr["positive"] - prev["positive"]

        if diff > 0.1:
            direction = "positive"
        elif diff < -0.1:
            direction = "negative"
        else:
            direction = "neutral"

        return {
            "direction": direction,
            "previous_positive_ratio": prev["positive"],
            "current_positive_ratio": curr["positive"],
            "previous_negative_ratio": prev["negative"],
            "current_negative_ratio": curr["negative"],
        }

    def _calculate_confidence(self, data_days: int, change_rate: float) -> float:
        base = 0.3
        if data_days >= 21:
            base += 0.3
        elif data_days >= 14:
            base += 0.2
        elif data_days >= 7:
            base += 0.1

        if abs(change_rate) > 30:
            base += 0.2
        elif abs(change_rate) > 10:
            base += 0.1

        return round(min(base, 1.0), 2)

    def _determine_severity(self, trend_type: str, change_rate: float) -> str:
        if trend_type == "hotspot" or abs(change_rate) > 50:
            return "critical"
        elif trend_type in ("rising", "declining") and abs(change_rate) > 20:
            return "warning"
        return "info"

    async def _get_brand(self, brand_id: uuid.UUID) -> Brand | None:
        stmt = select(Brand).where(Brand.id == brand_id)
        result = await self.db.execute(stmt)
        return result.scalar_one_or_none()
