"""
Viral Detector v2 - Outlier-based scoring.

Detects truly viral content by measuring how much a video
outperforms its creator's baseline, not just absolute numbers.

Score components:
- Outlier Ratio (35%): views vs creator's median views
- View Velocity (25%): views per hour, normalized by platform
- Engagement Quality (20%): weighted engagement with emphasis on comments/shares
- Share Virality (10%): shares/views ratio
- Growth Acceleration (10%): is the video still accelerating?
"""

from datetime import datetime, timezone
import json
import math


class ViralDetector:
    WEIGHTS = {
        "outlier": 0.35,
        "velocity": 0.25,
        "engagement": 0.20,
        "shares": 0.10,
        "acceleration": 0.10,
    }

    PLATFORM_THRESHOLDS = {
        "youtube": {
            "velocity_max": 50_000,       # views/hr for score 100
            "engagement_max": 0.08,
            "share_max": 0.02,
            "outlier_max": 20,            # 20x median = score 100
        },
        "tiktok": {
            "velocity_max": 100_000,
            "engagement_max": 0.15,
            "share_max": 0.05,
            "outlier_max": 20,
        },
        "instagram": {
            "velocity_max": 30_000,
            "engagement_max": 0.12,
            "share_max": 0.03,
            "outlier_max": 20,
        },
    }

    def calculate_score(self, video_data: dict) -> dict:
        platform = video_data.get("platform", "youtube")
        thresholds = self.PLATFORM_THRESHOLDS.get(
            platform, self.PLATFORM_THRESHOLDS["youtube"]
        )

        views = max(video_data.get("views", 0), 1)
        likes = video_data.get("likes", 0)
        comments = video_data.get("comments", 0)
        shares = video_data.get("shares", 0)
        published_at = video_data.get("published_at")
        creator_avg_views = video_data.get("creator_avg_views", 0)
        creator_median_views = video_data.get("creator_median_views", 0)
        creator_followers = video_data.get("creator_followers", 0)

        hours_since = self._hours_since_published(published_at)

        # 1. Outlier Ratio Score (35%) - THE KEY METRIC
        baseline = creator_median_views or creator_avg_views
        if baseline and baseline > 0:
            outlier_ratio = views / baseline
        elif creator_followers and creator_followers > 0:
            # Estimate: avg video gets ~2-5% of followers as views
            estimated_avg = creator_followers * 0.03
            outlier_ratio = views / max(estimated_avg, 1)
        else:
            # No creator data: use platform median as fallback
            platform_medians = {"youtube": 5000, "tiktok": 10000, "instagram": 3000}
            median = platform_medians.get(platform, 5000)
            outlier_ratio = views / median

        outlier_score = self._normalize(outlier_ratio, thresholds["outlier_max"])

        # 2. View Velocity (25%)
        views_per_hour = views / max(hours_since, 0.5)
        velocity_score = self._normalize(views_per_hour, thresholds["velocity_max"])
        # Bonus for fresh content (< 24h)
        if hours_since < 24:
            velocity_score = min(velocity_score * 1.2, 1.0)

        # 3. Engagement Quality (20%) - weighted: comments 3x, shares 5x
        quality_numerator = (likes * 1) + (comments * 3) + (shares * 5)
        quality_rate = quality_numerator / views
        engagement_score = self._normalize(quality_rate, thresholds["engagement_max"])

        # Simple engagement rate for display
        engagement_rate = (likes + comments + shares) / views

        # 4. Share Virality (10%)
        share_rate = shares / views if shares > 0 else 0
        share_score = self._normalize(share_rate, thresholds["share_max"])

        # 5. Growth Acceleration (10%)
        snapshots_raw = video_data.get("growth_snapshots")
        acceleration_score = self._acceleration_score(snapshots_raw, views)

        # Weighted final score
        final = (
            outlier_score * self.WEIGHTS["outlier"]
            + velocity_score * self.WEIGHTS["velocity"]
            + engagement_score * self.WEIGHTS["engagement"]
            + share_score * self.WEIGHTS["shares"]
            + acceleration_score * self.WEIGHTS["acceleration"]
        )

        viral_score = round(min(final * 100, 100), 1)

        # Calculate views per follower
        vpf = views / creator_followers if creator_followers > 0 else 0

        return {
            "viral_score": viral_score,
            "engagement_rate": round(engagement_rate * 100, 2),
            "outlier_ratio": round(outlier_ratio, 1),
            "views_per_follower": round(vpf, 2),
            "creator_avg_views": creator_avg_views,
            "creator_followers": creator_followers,
            "is_small_creator": creator_followers > 0 and creator_followers < 100_000,
            "components": {
                "outlier": round(outlier_score * 100, 1),
                "velocity": round(velocity_score * 100, 1),
                "engagement": round(engagement_score * 100, 1),
                "shares": round(share_score * 100, 1),
                "acceleration": round(acceleration_score * 100, 1),
            },
            "metrics": {
                "views_per_hour": round(views_per_hour, 0),
                "hours_since_published": round(hours_since, 1),
                "outlier_ratio": round(outlier_ratio, 1),
            },
        }

    def calculate_replicability(self, video_data: dict, score_data: dict) -> float:
        """
        Replicability Score (0-100): how easy is this content to replicate?

        Components:
        - Creator size inverse (30%): smaller creator = more replicable
        - Short format (25%): < 60s = more replicable
        - Trending elements (25%): uses trending sound/hashtag
        - Timing (20%): trend still rising = replicate NOW
        """
        creator_followers = video_data.get("creator_followers", 0)
        duration = video_data.get("duration", 0)
        hashtags = video_data.get("hashtags", [])
        outlier_ratio = score_data.get("outlier_ratio", 1)

        # 1. Creator size inverse (30%)
        if creator_followers <= 0:
            size_score = 0.5  # Unknown
        elif creator_followers < 10_000:
            size_score = 1.0  # Nano
        elif creator_followers < 100_000:
            size_score = 0.8  # Micro
        elif creator_followers < 1_000_000:
            size_score = 0.5  # Mid
        elif creator_followers < 10_000_000:
            size_score = 0.3  # Macro
        else:
            size_score = 0.1  # Mega

        # 2. Short format (25%)
        if duration <= 0:
            format_score = 0.5
        elif duration <= 15:
            format_score = 1.0
        elif duration <= 30:
            format_score = 0.9
        elif duration <= 60:
            format_score = 0.7
        elif duration <= 180:
            format_score = 0.4
        else:
            format_score = 0.2

        # 3. Trending elements (25%)
        trending_tags = {"viral", "fyp", "foryou", "foryoupage", "trending",
                         "trend", "fy", "parati", "viralvideo", "explore"}
        has_trending = bool(set(h.lower() for h in hashtags) & trending_tags)
        trend_score = 0.8 if has_trending else 0.3
        # Bonus if outlier ratio is high (content itself is trending)
        if outlier_ratio > 10:
            trend_score = min(trend_score + 0.2, 1.0)

        # 4. Timing (20%)
        hours = score_data.get("metrics", {}).get("hours_since_published", 48)
        if hours <= 12:
            timing_score = 1.0
        elif hours <= 24:
            timing_score = 0.9
        elif hours <= 48:
            timing_score = 0.7
        elif hours <= 168:
            timing_score = 0.4
        else:
            timing_score = 0.2

        replicability = (
            size_score * 0.30
            + format_score * 0.25
            + trend_score * 0.25
            + timing_score * 0.20
        )

        return round(replicability * 100, 1)

    @staticmethod
    def _normalize(value: float, threshold: float) -> float:
        if threshold <= 0:
            return 0
        return min(value / threshold, 1.0)

    @staticmethod
    def _normalize_log(value: float, max_value: float) -> float:
        if value <= 0 or max_value <= 0:
            return 0
        return min(math.log10(value + 1) / math.log10(max_value + 1), 1.0)

    @staticmethod
    def _acceleration_score(snapshots_raw, current_views: int) -> float:
        """Calculate growth acceleration from snapshots."""
        if not snapshots_raw:
            return 0.5  # Neutral when no data

        try:
            snapshots = json.loads(snapshots_raw) if isinstance(snapshots_raw, str) else snapshots_raw
        except (json.JSONDecodeError, TypeError):
            return 0.5

        if len(snapshots) < 2:
            return 0.5

        # Compare growth rates between last two intervals
        latest = snapshots[-1]
        previous = snapshots[-2]

        latest_views = latest.get("views", current_views)
        previous_views = previous.get("views", 0)

        latest_ts = latest.get("timestamp", 0)
        previous_ts = previous.get("timestamp", 0)

        hours_diff = max((latest_ts - previous_ts) / 3600, 0.5)
        growth_rate = (latest_views - previous_views) / hours_diff

        if len(snapshots) >= 3:
            even_earlier = snapshots[-3]
            earlier_views = even_earlier.get("views", 0)
            earlier_ts = even_earlier.get("timestamp", 0)
            earlier_hours = max((previous_ts - earlier_ts) / 3600, 0.5)
            earlier_rate = (previous_views - earlier_views) / earlier_hours

            if earlier_rate > 0:
                acceleration = growth_rate / earlier_rate
                # > 1 means accelerating, < 1 means decelerating
                return min(acceleration / 2.0, 1.0)

        # With only 2 snapshots, just check if growth is positive
        if growth_rate > 0:
            return 0.7
        return 0.3

    @staticmethod
    def _hours_since_published(published_at) -> float:
        if not published_at:
            return 48.0

        now = datetime.now(timezone.utc)

        if isinstance(published_at, str):
            try:
                published_at = datetime.fromisoformat(
                    published_at.replace("Z", "+00:00")
                )
            except (ValueError, TypeError):
                return 48.0

        if published_at.tzinfo is None:
            published_at = published_at.replace(tzinfo=timezone.utc)

        delta = now - published_at
        return max(delta.total_seconds() / 3600, 0.1)

    @classmethod
    def get_viral_label(cls, score: float) -> str:
        if score >= 90:
            return "MEGA VIRAL"
        if score >= 75:
            return "VIRAL"
        if score >= 60:
            return "Trending"
        if score >= 40:
            return "Em alta"
        if score >= 20:
            return "Crescendo"
        return "Normal"

    @classmethod
    def get_viral_color(cls, score: float) -> str:
        if score >= 90:
            return "text-red-500"
        if score >= 75:
            return "text-orange-500"
        if score >= 60:
            return "text-yellow-500"
        if score >= 40:
            return "text-blue-500"
        if score >= 20:
            return "text-green-500"
        return "text-gray-500"

    @classmethod
    def get_outlier_label(cls, ratio: float) -> str:
        if ratio >= 50:
            return "EXPLOSAO"
        if ratio >= 20:
            return "Super Viral"
        if ratio >= 10:
            return "Outlier"
        if ratio >= 5:
            return "Acima da media"
        if ratio >= 2:
            return "Bom"
        return "Normal"

    @classmethod
    def get_size_tier(cls, followers: int) -> str:
        if followers <= 0:
            return "unknown"
        if followers < 10_000:
            return "nano"
        if followers < 100_000:
            return "micro"
        if followers < 1_000_000:
            return "mid"
        if followers < 10_000_000:
            return "macro"
        return "mega"
