import json
import logging
import uuid
from collections import defaultdict
from datetime import datetime, timedelta
from typing import Callable

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

from app.database import AsyncSessionLocal
from app.models.brand import Brand
from app.models.competitor import Competitor
from app.models.competitor_insight import CompetitorInsight
from app.models.citation_record import CitationRecord
from app.models.query import Query
from app.services.llm import LLMFactory, LLMError
from app.utils.json_extractor import extract_json

logger = logging.getLogger(__name__)

VALID_ANALYSIS_TYPES = [
    "citation_gap",
    "content_strategy",
    "platform_coverage",
    "query_overlap",
    "differentiation",
]


class CompetitorAnalyzerService:

    async def analyze_competitor(
        self,
        brand_id: uuid.UUID,
        analysis_types: list[str] | None = None,
        period_days: int = 30,
        progress_callback: Callable[[float, str], None] | None = None,
    ) -> dict:
        if analysis_types is None:
            analysis_types = VALID_ANALYSIS_TYPES

        invalid = set(analysis_types) - set(VALID_ANALYSIS_TYPES)
        if invalid:
            raise ValueError(f"不支持的分析类型: {', '.join(invalid)}")

        async with AsyncSessionLocal() as session:
            brand = await session.get(Brand, brand_id)
            if not brand:
                raise ValueError(f"品牌不存在: {brand_id}")

            if progress_callback:
                await progress_callback(0.1, "获取竞品列表...")

            competitors = await self._get_competitors(session, brand_id)
            if not competitors:
                raise ValueError("未找到竞品数据")

            if progress_callback:
                await progress_callback(0.2, "聚合品牌引用数据...")

            brand_citation_data = await self._aggregate_citation_data(
                session, brand_id, brand.name, period_days,
            )

            results = []
            total = len(competitors)
            for i, competitor in enumerate(competitors):
                if progress_callback:
                    progress = 0.25 + (0.5 * i / total)
                    await progress_callback(progress, f"分析竞品 {competitor.name}...")

                competitor_citation_data = await self._aggregate_citation_data(
                    session, brand_id, competitor.name, period_days,
                )

                for analysis_type in analysis_types:
                    insight = await self._build_insight(
                        session=session,
                        brand_id=brand_id,
                        brand_name=brand.name,
                        competitor=competitor,
                        analysis_type=analysis_type,
                        brand_data=brand_citation_data,
                        competitor_data=competitor_citation_data,
                        period_days=period_days,
                    )
                    session.add(insight)
                    results.append(insight)

            await session.commit()
            for r in results:
                await session.refresh(r)

            return {
                "brand_id": str(brand_id),
                "brand_name": brand.name,
                "insights": [
                    {
                        "id": str(r.id),
                        "competitor_name": r.competitor_name,
                        "analysis_type": r.analysis_type,
                        "citation_count_brand": r.citation_count_brand,
                        "citation_count_competitor": r.citation_count_competitor,
                        "sentiment_brand": r.sentiment_brand,
                        "sentiment_competitor": r.sentiment_competitor,
                        "platform_breakdown": r.platform_breakdown,
                        "gap_analysis": r.gap_analysis,
                        "opportunity_areas": r.opportunity_areas,
                        "recommendations": r.recommendations,
                        "confidence": r.confidence,
                        "period_days": r.period_days,
                        "created_at": r.created_at.isoformat() if r.created_at else None,
                    }
                    for r in results
                ],
                "total": len(results),
            }

    async def compare_citation_volume(
        self,
        brand_data: dict,
        competitor_data: dict,
    ) -> dict:
        brand_count = brand_data["citation_count"]
        competitor_count = competitor_data["citation_count"]
        total = brand_count + competitor_count

        return {
            "brand": brand_count,
            "competitor": competitor_count,
            "diff": brand_count - competitor_count,
            "brand_share": round(brand_count / total, 4) if total > 0 else 0.0,
            "competitor_share": round(competitor_count / total, 4) if total > 0 else 0.0,
            "by_platform": self._compare_platform_citations(
                brand_data, competitor_data,
            ),
        }

    async def compare_citation_quality(
        self,
        brand_data: dict,
        competitor_data: dict,
    ) -> dict:
        brand_positive = brand_data.get("positive_ratio", 0.0)
        competitor_positive = competitor_data.get("positive_ratio", 0.0)
        brand_rank = brand_data.get("avg_rank", 0.0)
        competitor_rank = competitor_data.get("avg_rank", 0.0)

        return {
            "sentiment": {
                "brand_positive_ratio": brand_positive,
                "competitor_positive_ratio": competitor_positive,
                "diff": round(brand_positive - competitor_positive, 4),
            },
            "ranking": {
                "brand_avg_rank": brand_rank,
                "competitor_avg_rank": competitor_rank,
                "diff": round(brand_rank - competitor_rank, 2),
            },
            "brand_sentiment_breakdown": brand_data.get("sentiment_breakdown", {}),
            "competitor_sentiment_breakdown": competitor_data.get("sentiment_breakdown", {}),
        }

    async def analyze_content_strategy(
        self,
        brand_data: dict,
        competitor_data: dict,
    ) -> dict:
        brand_types = brand_data.get("content_types", {})
        competitor_types = competitor_data.get("content_types", {})

        all_types = set(brand_types.keys()) | set(competitor_types.keys())
        type_comparison = {}
        for ct in all_types:
            type_comparison[ct] = {
                "brand": brand_types.get(ct, 0),
                "competitor": competitor_types.get(ct, 0),
            }

        competitor_only_types = set(competitor_types.keys()) - set(brand_types.keys())
        brand_only_types = set(brand_types.keys()) - set(competitor_types.keys())

        return {
            "type_comparison": type_comparison,
            "competitor_unique_types": list(competitor_only_types),
            "brand_unique_types": list(brand_only_types),
            "competitor_top_types": sorted(
                competitor_types.items(), key=lambda x: x[1], reverse=True,
            )[:5],
        }

    async def identify_opportunities(
        self,
        brand_data: dict,
        competitor_data: dict,
        comparison: dict,
    ) -> dict:
        opportunities = []

        brand_platforms = set(brand_data["by_platform"].keys())
        competitor_platforms = set(competitor_data["by_platform"].keys())

        brand_only = brand_platforms - competitor_platforms
        if brand_only:
            for platform in brand_only:
                opportunities.append({
                    "area": f"platform_{platform}",
                    "description": f"品牌在{platform}平台有引用而竞品没有，可加大投入建立差异化优势",
                    "potential": "high",
                    "action": f"增加在{platform}平台的内容投放和优化",
                })

        competitor_only = competitor_platforms - brand_platforms
        if competitor_only:
            for platform in competitor_only:
                opportunities.append({
                    "area": f"platform_{platform}",
                    "description": f"竞品在{platform}平台有引用而品牌没有，存在进入机会",
                    "potential": "medium",
                    "action": f"研究{platform}平台的内容偏好，制定进入策略",
                })

        citation_volume = comparison.get("citation_volume", {})
        if citation_volume.get("diff", 0) > 0:
            opportunities.append({
                "area": "citation_volume_advantage",
                "description": "品牌引用量高于竞品，可强化品牌权威性传播",
                "potential": "high",
                "action": "收集高引用内容案例，扩大品牌影响力",
            })

        quality = comparison.get("quality", {})
        sentiment = quality.get("sentiment", {})
        if sentiment.get("diff", 0) > 0.1:
            opportunities.append({
                "area": "sentiment_advantage",
                "description": "品牌正面引用比例显著高于竞品，可强化正面形象传播",
                "potential": "high",
                "action": "收集正面引用案例，制作品牌优势内容",
            })

        if not opportunities:
            opportunities.append({
                "area": "general",
                "description": "当前数据未发现明显差异化机会，建议持续监测并积累更多数据",
                "potential": "low",
                "action": "增加查询频率和覆盖平台，积累更多引用数据",
            })

        return {
            "opportunities": opportunities,
            "total_opportunities": len(opportunities),
            "high_potential_count": sum(1 for o in opportunities if o["potential"] == "high"),
        }

    async def generate_recommendations(
        self,
        brand_name: str,
        competitor_name: str,
        comparison: dict,
        gaps: dict,
        opportunities: dict,
        data_sufficiency: str,
    ) -> dict:
        prompt = f"""你是一个专业的GEO（Generative Engine Optimization）策略分析师。
请基于以下品牌与竞品的引用对比数据，生成策略建议。

品牌: {brand_name}
竞品: {competitor_name}
数据充分性: {data_sufficiency}

对比数据:
{json.dumps(comparison, ensure_ascii=False, indent=2)}

差距分析:
{json.dumps(gaps, ensure_ascii=False, indent=2)}

机会发现:
{json.dumps(opportunities, ensure_ascii=False, indent=2)}

请返回JSON格式（不要包含其他文字）:
{{
  "gap_closing_strategies": [
    {{"strategy": "策略描述", "priority": "high/medium/low", "expected_impact": "预期效果"}}
  ],
  "differentiation_strategies": [
    {{"strategy": "策略描述", "priority": "high/medium/low", "expected_impact": "预期效果"}}
  ],
  "quick_wins": [
    {{"action": "行动描述", "effort": "low/medium/high", "timeline": "预计时间"}}
  ],
  "long_term_recommendations": [
    {{"recommendation": "建议描述", "rationale": "理由"}}
  ]
}}"""

        try:
            provider = LLMFactory.get_default()
            response = await provider.chat(
                [{"role": "user", "content": prompt}],
                temperature=0.3,
                max_tokens=2000,
            )
            result = json.loads(extract_json(response.content))
            result["usage"] = response.usage
            return result
        except (LLMError, json.JSONDecodeError, ValueError) as e:
            logger.warning(f"LLM策略生成失败，使用默认策略: {e}")
            return self._default_strategy(gaps, opportunities)

    async def calculate_gap_score(
        self,
        db: AsyncSession,
        brand_id: uuid.UUID,
        brand_name: str,
    ) -> list[dict]:
        stmt = (
            select(CompetitorInsight)
            .where(CompetitorInsight.brand_id == brand_id)
            .order_by(CompetitorInsight.created_at.desc())
        )
        result = await db.execute(stmt)
        insights = list(result.scalars().all())

        competitor_map: dict[str, list[CompetitorInsight]] = defaultdict(list)
        for insight in insights:
            competitor_map[insight.competitor_name].append(insight)

        summaries = []
        for comp_name, comp_insights in competitor_map.items():
            gap_dimensions = []
            score_components = []

            for insight in comp_insights:
                gap = insight.gap_analysis or {}
                if not gap:
                    continue

                for g in gap.get("gaps", []):
                    dimension = g.get("dimension", "unknown")
                    severity = g.get("severity", "low")
                    gap_value = g.get("gap", 0)

                    severity_score = {"high": 30, "medium": 15, "low": 5}.get(severity, 5)
                    score_components.append(severity_score)

                    gap_dimensions.append({
                        "dimension": dimension,
                        "severity": severity,
                        "gap": gap_value,
                        "analysis_type": insight.analysis_type,
                    })

            overall_score = min(sum(score_components), 100) if score_components else 0.0

            summaries.append({
                "brand_name": brand_name,
                "competitor_name": comp_name,
                "gap_dimensions": gap_dimensions,
                "overall_gap_score": round(overall_score, 2),
            })

        return summaries

    async def _build_insight(
        self,
        session: AsyncSession,
        brand_id: uuid.UUID,
        brand_name: str,
        competitor: Competitor,
        analysis_type: str,
        brand_data: dict,
        competitor_data: dict,
        period_days: int,
    ) -> CompetitorInsight:
        comparison = {}
        comparison["citation_volume"] = await self.compare_citation_volume(
            brand_data, competitor_data,
        )
        comparison["quality"] = await self.compare_citation_quality(
            brand_data, competitor_data,
        )

        gap = self._identify_gaps(comparison, brand_name, competitor.name)
        opportunities = await self.identify_opportunities(
            brand_data, competitor_data, comparison,
        )

        data_sufficiency = self._assess_data_sufficiency(brand_data, competitor_data)

        insight_data = {}
        if analysis_type == "content_strategy":
            insight_data = await self.analyze_content_strategy(
                brand_data, competitor_data,
            )
        elif analysis_type == "platform_coverage":
            insight_data = comparison["citation_volume"]["by_platform"]
        elif analysis_type == "query_overlap":
            insight_data = await self._analyze_query_overlap(
                session, brand_id, brand_name, competitor.name, period_days,
            )
        elif analysis_type == "differentiation":
            insight_data = {
                "brand_unique_platforms": list(
                    set(brand_data["by_platform"].keys()) - set(competitor_data["by_platform"].keys())
                ),
                "competitor_unique_platforms": list(
                    set(competitor_data["by_platform"].keys()) - set(brand_data["by_platform"].keys())
                ),
                "sentiment_diff": comparison["quality"].get("sentiment", {}),
            }

        recommendations = await self.generate_recommendations(
            brand_name=brand_name,
            competitor_name=competitor.name,
            comparison=comparison,
            gaps=gap,
            opportunities=opportunities,
            data_sufficiency=data_sufficiency,
        )

        confidence = self._determine_confidence(brand_data, competitor_data)

        return CompetitorInsight(
            brand_id=brand_id,
            competitor_name=competitor.name,
            analysis_type=analysis_type,
            insight_data=insight_data if insight_data else None,
            citation_count_brand=brand_data["citation_count"],
            citation_count_competitor=competitor_data["citation_count"],
            sentiment_brand=brand_data.get("positive_ratio"),
            sentiment_competitor=competitor_data.get("positive_ratio"),
            platform_breakdown=comparison["citation_volume"]["by_platform"],
            gap_analysis=gap,
            opportunity_areas=opportunities,
            recommendations=recommendations,
            confidence=confidence,
            period_days=period_days,
        )

    async def _get_competitors(
        self,
        db: AsyncSession,
        brand_id: uuid.UUID,
    ) -> list[Competitor]:
        stmt = select(Competitor).where(Competitor.brand_id == brand_id)
        result = await db.execute(stmt)
        return list(result.scalars().all())

    async def _aggregate_citation_data(
        self,
        db: AsyncSession,
        brand_id: uuid.UUID,
        target_name: str,
        period_days: int = 30,
    ) -> dict:
        since = datetime.utcnow() - timedelta(days=period_days)

        query_stmt = select(Query).where(Query.brand_id == brand_id)
        query_result = await db.execute(query_stmt)
        queries = list(query_result.scalars().all())

        if not queries:
            return {
                "citation_count": 0,
                "positive_ratio": 0.0,
                "avg_rank": 0.0,
                "by_platform": {},
                "content_types": {},
                "sentiment_breakdown": {"positive": 0, "neutral": 0, "negative": 0},
                "total_records": 0,
            }

        query_ids = [q.id for q in queries]
        query_aliases = set()
        for q in queries:
            query_aliases.add(q.target_brand.lower())
            if q.brand_aliases:
                for alias in q.brand_aliases:
                    query_aliases.add(alias.lower())

        conditions = [CitationRecord.query_id.in_(query_ids)]
        if since:
            conditions.append(CitationRecord.queried_at >= since)

        stmt = select(CitationRecord).where(and_(*conditions))
        result = await db.execute(stmt)
        records = list(result.scalars().all())

        target_lower = target_name.lower()
        matching_records = []
        for record in records:
            if record.cited and record.competitor_brands:
                is_target = False
                for cb in record.competitor_brands:
                    if isinstance(cb, str) and cb.lower() == target_lower:
                        is_target = True
                        break
                    elif isinstance(cb, str) and cb.lower() in query_aliases:
                        is_target = True
                        break
                if is_target:
                    matching_records.append(record)
            elif record.cited and not record.competitor_brands:
                matching_records.append(record)

        total_citations = len(matching_records)
        if total_citations == 0:
            return {
                "citation_count": 0,
                "positive_ratio": 0.0,
                "avg_rank": 0.0,
                "by_platform": {},
                "content_types": {},
                "sentiment_breakdown": {"positive": 0, "neutral": 0, "negative": 0},
                "total_records": len(records),
            }

        sentiment_breakdown = {"positive": 0, "neutral": 0, "negative": 0}
        for r in matching_records:
            s = r.sentiment or "neutral"
            if s in sentiment_breakdown:
                sentiment_breakdown[s] += 1
            else:
                sentiment_breakdown["neutral"] += 1

        positive_count = sentiment_breakdown["positive"]
        positive_ratio = positive_count / total_citations if total_citations > 0 else 0.0

        ranks = [
            r.citation_position for r in matching_records
            if r.citation_position is not None and r.citation_position > 0
        ]
        avg_rank = sum(ranks) / len(ranks) if ranks else 0.0

        by_platform = defaultdict(lambda: {"citations": 0, "positive": 0, "ranks": []})
        for r in matching_records:
            platform = r.platform
            by_platform[platform]["citations"] += 1
            if r.sentiment == "positive":
                by_platform[platform]["positive"] += 1
            if r.citation_position is not None and r.citation_position > 0:
                by_platform[platform]["ranks"].append(r.citation_position)

        platform_stats = {}
        for platform, data in by_platform.items():
            platform_stats[platform] = {
                "citations": data["citations"],
                "positive_ratio": data["positive"] / data["citations"] if data["citations"] > 0 else 0.0,
                "avg_rank": sum(data["ranks"]) / len(data["ranks"]) if data["ranks"] else 0.0,
            }

        content_types = defaultdict(int)
        for r in matching_records:
            match_type = r.match_type or "unknown"
            content_types[match_type] += 1

        return {
            "citation_count": total_citations,
            "positive_ratio": round(positive_ratio, 4),
            "avg_rank": round(avg_rank, 2),
            "by_platform": platform_stats,
            "content_types": dict(content_types),
            "sentiment_breakdown": sentiment_breakdown,
            "total_records": len(records),
        }

    def _compare_platform_citations(
        self,
        brand_data: dict,
        competitor_data: dict,
    ) -> dict:
        all_platforms = set(brand_data["by_platform"].keys()) | set(competitor_data["by_platform"].keys())
        result = {}
        for platform in all_platforms:
            bp = brand_data["by_platform"].get(platform, {"citations": 0, "positive_ratio": 0.0, "avg_rank": 0.0})
            cp = competitor_data["by_platform"].get(platform, {"citations": 0, "positive_ratio": 0.0, "avg_rank": 0.0})
            result[platform] = {
                "brand": bp,
                "competitor": cp,
            }
        return result

    def _identify_gaps(
        self,
        comparison: dict,
        brand_name: str,
        competitor_name: str,
    ) -> dict:
        gaps = []

        volume = comparison.get("citation_volume", {})
        citation_diff = volume.get("diff", 0)
        if citation_diff < 0:
            gaps.append({
                "dimension": "citation_count",
                "brand_value": volume.get("brand", 0),
                "competitor_value": volume.get("competitor", 0),
                "gap": abs(citation_diff),
                "severity": "high" if abs(citation_diff) >= 5 else "medium" if abs(citation_diff) >= 2 else "low",
            })

        quality = comparison.get("quality", {})
        sentiment = quality.get("sentiment", {})
        positive_diff = sentiment.get("diff", 0)
        if positive_diff < -0.1:
            gaps.append({
                "dimension": "positive_ratio",
                "brand_value": sentiment.get("brand_positive_ratio", 0),
                "competitor_value": sentiment.get("competitor_positive_ratio", 0),
                "gap": abs(positive_diff),
                "severity": "high" if abs(positive_diff) >= 0.3 else "medium" if abs(positive_diff) >= 0.15 else "low",
            })

        ranking = quality.get("ranking", {})
        rank_diff = ranking.get("diff", 0)
        if rank_diff > 1.0:
            gaps.append({
                "dimension": "avg_rank",
                "brand_value": ranking.get("brand_avg_rank", 0),
                "competitor_value": ranking.get("competitor_avg_rank", 0),
                "gap": abs(rank_diff),
                "severity": "high" if abs(rank_diff) >= 3.0 else "medium" if abs(rank_diff) >= 2.0 else "low",
            })

        for platform, data in volume.get("by_platform", {}).items():
            brand_citations = data.get("brand", {}).get("citations", 0)
            competitor_citations = data.get("competitor", {}).get("citations", 0)
            if competitor_citations > brand_citations + 2:
                gaps.append({
                    "dimension": f"platform_{platform}",
                    "brand_value": brand_citations,
                    "competitor_value": competitor_citations,
                    "gap": competitor_citations - brand_citations,
                    "severity": "high" if (competitor_citations - brand_citations) >= 5 else "medium",
                })

        return {
            "brand_name": brand_name,
            "competitor_name": competitor_name,
            "gaps": gaps,
            "total_gaps": len(gaps),
            "high_severity_count": sum(1 for g in gaps if g["severity"] == "high"),
        }

    async def _analyze_query_overlap(
        self,
        db: AsyncSession,
        brand_id: uuid.UUID,
        brand_name: str,
        competitor_name: str,
        period_days: int,
    ) -> dict:
        since = datetime.utcnow() - timedelta(days=period_days)

        stmt = select(Query).where(
            Query.brand_id == brand_id,
            Query.created_at >= since,
        )
        result = await db.execute(stmt)
        queries = list(result.scalars().all())

        brand_keywords = set()
        competitor_keywords = set()

        for q in queries:
            keyword = q.keyword.lower()
            brand_keywords.add(keyword)
            if competitor_name.lower() in keyword or any(
                a.lower() in keyword for a in (q.brand_aliases or [])
            ):
                competitor_keywords.add(keyword)

        overlap = brand_keywords & competitor_keywords
        brand_only = brand_keywords - competitor_keywords
        competitor_only = competitor_keywords - brand_keywords

        return {
            "brand_keyword_count": len(brand_keywords),
            "competitor_keyword_count": len(competitor_keywords),
            "overlap_count": len(overlap),
            "overlap_keywords": list(overlap)[:20],
            "brand_only_count": len(brand_only),
            "competitor_only_count": len(competitor_only),
            "overlap_ratio": round(len(overlap) / len(brand_keywords), 4) if brand_keywords else 0.0,
        }

    def _assess_data_sufficiency(
        self,
        brand_data: dict,
        competitor_data: dict,
    ) -> str:
        brand_count = brand_data["citation_count"]
        competitor_count = competitor_data["citation_count"]
        min_count = min(brand_count, competitor_count)

        if min_count > 10:
            return "sufficient"
        elif min_count >= 5:
            return "limited"
        else:
            return "insufficient"

    def _determine_confidence(
        self,
        brand_data: dict,
        competitor_data: dict,
    ) -> str:
        brand_count = brand_data["citation_count"]
        competitor_count = competitor_data["citation_count"]
        min_count = min(brand_count, competitor_count)

        if min_count > 20:
            return "high"
        elif min_count >= 5:
            return "medium"
        else:
            return "low"

    def _default_strategy(self, gaps: dict, opportunities: dict) -> dict:
        gap_strategies = []
        for gap in gaps.get("gaps", []):
            gap_strategies.append({
                "strategy": f"提升{gap['dimension']}维度表现，缩小与竞品差距",
                "priority": gap["severity"],
                "expected_impact": f"预计可将{gap['dimension']}差距缩小{gap['gap'] * 0.5:.1f}",
            })

        diff_strategies = []
        for opp in opportunities.get("opportunities", []):
            if opp["potential"] in ("high", "medium"):
                diff_strategies.append({
                    "strategy": opp["action"],
                    "priority": opp["potential"],
                    "expected_impact": "建立差异化竞争优势",
                })

        return {
            "gap_closing_strategies": gap_strategies[:5],
            "differentiation_strategies": diff_strategies[:5],
            "quick_wins": [],
            "long_term_recommendations": [
                {
                    "recommendation": "持续监测竞品引用数据变化，定期更新策略",
                    "rationale": "GEO优化是长期过程，需要持续迭代",
                }
            ],
        }
