"""
Trending Service for calculating and caching trending content.

This service implements the weighted trending algorithm that combines:
- View growth rates
- Content ratings
- Recency factors
- Popularity metrics

Results are cached in Redis for performance optimization.
"""

import logging
from datetime import datetime, timedelta
from typing import Any

from django.contrib.contenttypes.models import ContentType
from django.core.cache import cache
from django.db.models import Q
from django.utils import timezone

from apps.core.analytics import PageView
from apps.core.utils import capture_and_log
from apps.parks.models import Park
from apps.rides.models import Ride

logger = logging.getLogger(__name__)


class TrendingService:
    """
    Service for calculating trending content using weighted algorithm.

    Algorithm Components:
    - View Growth Rate (40% weight): Recent view increase vs historical
    - Rating Score (30% weight): Average user rating normalized
    - Recency Factor (20% weight): How recently content was added/updated
    - Popularity Boost (10% weight): Total view count normalization
    """

    # Algorithm weights (must sum to 1.0)
    WEIGHT_VIEW_GROWTH = 0.4
    WEIGHT_RATING = 0.3
    WEIGHT_RECENCY = 0.2
    WEIGHT_POPULARITY = 0.1

    # Cache configuration
    CACHE_PREFIX = "trending"
    CACHE_TTL = 86400  # 24 hours (daily refresh)

    # Time windows for calculations
    CURRENT_PERIOD_HOURS = 168  # 7 days
    PREVIOUS_PERIOD_HOURS = 336  # 14 days (for previous 7-day window comparison)
    RECENCY_BASELINE_DAYS = 365

    def __init__(self):
        self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")

    def get_trending_content(
        self, content_type: str = "all", limit: int = 20, force_refresh: bool = False
    ) -> list[dict[str, Any]]:
        """
        Get trending content using direct calculation.

        Args:
            content_type: 'parks', 'rides', or 'all'
            limit: Maximum number of results
            force_refresh: Skip cache and recalculate

        Returns:
            List of trending content with exact frontend format
        """
        cache_key = f"trending:calculated:{content_type}:{limit}"

        if not force_refresh:
            cached_result = cache.get(cache_key)
            if cached_result is not None:
                self.logger.debug(f"Returning cached trending results for {content_type}")
                return cached_result

        self.logger.info(f"Getting trending content for {content_type}")

        try:
            # Calculate directly without Celery
            trending_items = []

            if content_type in ["all", "parks"]:
                park_items = self._calculate_trending_parks(limit * 2 if content_type == "all" else limit)
                trending_items.extend(park_items)

            if content_type in ["all", "rides"]:
                ride_items = self._calculate_trending_rides(limit * 2 if content_type == "all" else limit)
                trending_items.extend(ride_items)

            # Sort by trending score and apply limit
            trending_items.sort(key=lambda x: x.get("trending_score", 0), reverse=True)
            trending_items = trending_items[:limit]

            # Format results for API consumption
            formatted_results = self._format_trending_results(trending_items)

            # Cache results
            cache.set(cache_key, formatted_results, self.CACHE_TTL)

            self.logger.info(f"Calculated {len(formatted_results)} trending items for {content_type}")
            return formatted_results

        except Exception as e:
            capture_and_log(e, f'Get trending content ({content_type})', source='service')
            return []

    def get_new_content(
        self,
        content_type: str = "all",
        limit: int = 20,
        days_back: int = 30,
        force_refresh: bool = False,
    ) -> list[dict[str, Any]]:
        """
        Get recently added content using direct calculation.

        Args:
            content_type: 'parks', 'rides', or 'all'
            limit: Maximum number of results
            days_back: How many days to look back
            force_refresh: Skip cache and recalculate

        Returns:
            List of new content with exact frontend format
        """
        cache_key = f"new_content:calculated:{content_type}:{days_back}:{limit}"

        if not force_refresh:
            cached_result = cache.get(cache_key)
            if cached_result is not None:
                self.logger.debug(f"Returning cached new content results for {content_type}")
                return cached_result

        self.logger.info(f"Getting new content for {content_type}")

        try:
            # Calculate directly without Celery
            cutoff_date = timezone.now() - timedelta(days=days_back)
            new_items = []

            if content_type in ["all", "parks"]:
                parks = self._get_new_parks(cutoff_date, limit * 2 if content_type == "all" else limit)
                new_items.extend(parks)

            if content_type in ["all", "rides"]:
                rides = self._get_new_rides(cutoff_date, limit * 2 if content_type == "all" else limit)
                new_items.extend(rides)

            # Sort by date added (most recent first) and apply limit
            new_items.sort(key=lambda x: x.get("date_added", ""), reverse=True)
            new_items = new_items[:limit]

            # Format results for API consumption
            formatted_results = self._format_new_content_results(new_items)

            # Cache results
            cache.set(cache_key, formatted_results, 1800)  # Cache for 30 minutes

            self.logger.info(f"Calculated {len(formatted_results)} new items for {content_type}")
            return formatted_results

        except Exception as e:
            capture_and_log(e, f'Get new content ({content_type})', source='service')
            return []

    def _calculate_trending_parks(self, limit: int) -> list[dict[str, Any]]:
        """Calculate trending scores for parks."""
        parks = Park.objects.filter(status="OPERATING").select_related("location", "operator", "card_image")

        trending_parks = []

        for park in parks:
            try:
                score = self._calculate_content_score(park, "park")
                if score > 0:  # Only include items with positive trending scores
                    # Get opening date for date_opened field
                    opening_date = getattr(park, "opening_date", None)
                    if opening_date and isinstance(opening_date, datetime):
                        opening_date = opening_date.date()

                    # Get location fields
                    city = ""
                    state = ""
                    country = ""
                    try:
                        location = getattr(park, "location", None)
                        if location:
                            city = getattr(location, "city", "") or ""
                            state = getattr(location, "state", "") or ""
                            country = getattr(location, "country", "") or ""
                    except Exception:
                        pass

                    # Get card image URL
                    card_image_url = ""
                    if park.card_image and hasattr(park.card_image, "image"):
                        card_image_url = park.card_image.image.public_url if park.card_image.image else ""

                    # Get primary company (operator)
                    primary_company = park.operator.name if park.operator else ""

                    trending_parks.append(
                        {
                            "content_object": park,
                            "content_type": "park",
                            "trending_score": score,
                            "id": park.id,
                            "name": park.name,
                            "slug": park.slug,
                            "park": park.name,  # For parks, park field is the park name itself
                            "category": "park",
                            "rating": (float(park.average_rating) if park.average_rating else 0.0),
                            "date_opened": (opening_date.isoformat() if opening_date else ""),
                            "url": park.url,
                            "card_image": card_image_url,
                            "city": city,
                            "state": state,
                            "country": country,
                            "primary_company": primary_company,
                        }
                    )
            except Exception as e:
                self.logger.warning(f"Error calculating score for park {park.id}: {e}")

        return trending_parks

    def _calculate_trending_rides(self, limit: int) -> list[dict[str, Any]]:
        """Calculate trending scores for rides."""
        rides = Ride.objects.filter(status="OPERATING").select_related("park", "park__location", "card_image")

        trending_rides = []

        for ride in rides:
            try:
                score = self._calculate_content_score(ride, "ride")
                if score > 0:  # Only include items with positive trending scores
                    # Get opening date for date_opened field
                    opening_date = getattr(ride, "opening_date", None)
                    if opening_date and isinstance(opening_date, datetime):
                        opening_date = opening_date.date()

                    # Get card image URL
                    card_image_url = ""
                    if ride.card_image and hasattr(ride.card_image, "image"):
                        card_image_url = ride.card_image.image.public_url if ride.card_image.image else ""

                    trending_rides.append(
                        {
                            "content_object": ride,
                            "content_type": "ride",
                            "trending_score": score,
                            "id": ride.pk,  # Use pk instead of id
                            "name": ride.name,
                            "slug": ride.slug,
                            "park": ride.park.name if ride.park else "",
                            "category": "ride",
                            "rating": (float(ride.average_rating) if ride.average_rating else 0.0),
                            "date_opened": (opening_date.isoformat() if opening_date else ""),
                            "url": ride.url,
                            "park_url": ride.park.url if ride.park else "",
                            "card_image": card_image_url,
                        }
                    )
            except Exception as e:
                self.logger.warning(f"Error calculating score for ride {ride.pk}: {e}")

        return trending_rides

    def _calculate_content_score(self, content_obj: Any, content_type: str) -> float:
        """
        Calculate weighted trending score for content object.

        Returns:
            Float between 0.0 and 1.0 representing trending strength
        """
        try:
            # Get content type for PageView queries
            ct = ContentType.objects.get_for_model(content_obj)

            # 1. View Growth Score (40% weight)
            view_growth_score = self._calculate_view_growth_score(ct, content_obj.id)

            # 2. Rating Score (30% weight)
            rating_score = self._calculate_rating_score(content_obj)

            # 3. Recency Score (20% weight)
            recency_score = self._calculate_recency_score(content_obj)

            # 4. Popularity Score (10% weight)
            popularity_score = self._calculate_popularity_score(ct, content_obj.id)

            # Calculate weighted final score
            final_score = (
                view_growth_score * self.WEIGHT_VIEW_GROWTH
                + rating_score * self.WEIGHT_RATING
                + recency_score * self.WEIGHT_RECENCY
                + popularity_score * self.WEIGHT_POPULARITY
            )

            self.logger.debug(
                f"{content_type} {content_obj.id}: "
                f"growth={view_growth_score:.3f}, rating={rating_score:.3f}, "
                f"recency={recency_score:.3f}, popularity={popularity_score:.3f}, "
                f"final={final_score:.3f}"
            )

            return final_score

        except Exception as e:
            capture_and_log(e, f'Calculate content score ({content_type} {content_obj.id})', source='service', severity='low')
            return 0.0

    def _calculate_view_growth_score(self, content_type: ContentType, object_id: int) -> float:
        """Calculate normalized view growth score."""
        try:
            current_views, previous_views, growth_percentage = PageView.get_views_growth(
                content_type,
                object_id,
                self.CURRENT_PERIOD_HOURS,
                self.PREVIOUS_PERIOD_HOURS,
            )

            if previous_views == 0:
                # New content with views gets boost
                return min(current_views / 100.0, 1.0) if current_views > 0 else 0.0

            # Normalize growth percentage to 0-1 scale
            # 100% growth = 0.5, 500% growth = 1.0
            normalized_growth = min(growth_percentage / 500.0, 1.0) if growth_percentage > 0 else 0.0
            return max(normalized_growth, 0.0)

        except Exception as e:
            self.logger.warning(f"Error calculating view growth: {e}")
            return 0.0

    def _calculate_rating_score(self, content_obj: Any) -> float:
        """Calculate normalized rating score."""
        try:
            rating = getattr(content_obj, "average_rating", None)
            if rating is None or rating == 0:
                return 0.3  # Neutral score for unrated content

            # Normalize rating from 1-10 scale to 0-1 scale
            # Rating of 5 = 0.4, Rating of 8 = 0.7, Rating of 10 = 1.0
            return min(max((float(rating) - 1) / 9.0, 0.0), 1.0)

        except Exception as e:
            self.logger.warning(f"Error calculating rating score: {e}")
            return 0.3

    def _calculate_recency_score(self, content_obj: Any) -> float:
        """Calculate recency score based on when content was added/updated."""
        try:
            # Use opening_date for parks/rides, or created_at as fallback
            date_added = getattr(content_obj, "opening_date", None)
            if not date_added:
                date_added = getattr(content_obj, "created_at", None)
            if not date_added:
                return 0.5  # Neutral score for unknown dates

            # Handle both date and datetime objects
            if hasattr(date_added, "date"):
                date_added = date_added.date()

            # Calculate days since added
            today = timezone.now().date()
            days_since_added = (today - date_added).days

            # Recency score: newer content gets higher scores
            # 0 days = 1.0, 30 days = 0.8, 365 days = 0.1, >365 days = 0.0
            if days_since_added <= 0:
                return 1.0
            elif days_since_added <= 30:
                return 1.0 - (days_since_added / 30.0) * 0.2  # 1.0 to 0.8
            elif days_since_added <= self.RECENCY_BASELINE_DAYS:
                return 0.8 - ((days_since_added - 30) / (self.RECENCY_BASELINE_DAYS - 30)) * 0.7  # 0.8 to 0.1
            else:
                return 0.0

        except Exception as e:
            self.logger.warning(f"Error calculating recency score: {e}")
            return 0.5

    def _calculate_popularity_score(self, content_type: ContentType, object_id: int) -> float:
        """Calculate popularity score based on total view count."""
        try:
            total_views = PageView.get_total_views_count(
                content_type,
                object_id,
                hours=168,  # Last 7 days
            )

            # Normalize views to 0-1 scale
            # 0 views = 0.0, 100 views = 0.5, 1000+ views = 1.0
            if total_views == 0:
                return 0.0
            elif total_views <= 100:
                return total_views / 200.0  # 0.0 to 0.5
            else:
                return min(0.5 + (total_views - 100) / 1800.0, 1.0)  # 0.5 to 1.0

        except Exception as e:
            self.logger.warning(f"Error calculating popularity score: {e}")
            return 0.0

    def _get_new_parks(self, cutoff_date: datetime, limit: int) -> list[dict[str, Any]]:
        """Get recently added parks."""
        new_parks = (
            Park.objects.filter(
                Q(created_at__gte=cutoff_date) | Q(opening_date__gte=cutoff_date.date()),
                status="OPERATING",
            )
            .select_related("location", "operator", "card_image")
            .order_by("-created_at", "-opening_date")[:limit]
        )

        results = []
        for park in new_parks:
            date_added = park.opening_date or park.created_at
            # Handle datetime to date conversion
            if date_added:  # noqa: SIM102
                # If it's a datetime, convert to date
                if isinstance(date_added, datetime):
                    date_added = date_added.date()
                # If it's already a date, keep it as is

            # Get opening date for date_opened field
            opening_date = getattr(park, "opening_date", None)
            if opening_date and isinstance(opening_date, datetime):
                opening_date = opening_date.date()

            # Get location fields
            city = ""
            state = ""
            country = ""
            try:
                location = getattr(park, "location", None)
                if location:
                    city = getattr(location, "city", "") or ""
                    state = getattr(location, "state", "") or ""
                    country = getattr(location, "country", "") or ""
            except Exception:
                pass

            # Get card image URL
            card_image_url = ""
            if park.card_image and hasattr(park.card_image, "image"):
                card_image_url = park.card_image.image.public_url if park.card_image.image else ""

            # Get primary company (operator)
            primary_company = park.operator.name if park.operator else ""

            results.append(
                {
                    "content_object": park,
                    "content_type": "park",
                    "id": park.pk,  # Use pk instead of id for Django compatibility
                    "name": park.name,
                    "slug": park.slug,
                    "park": park.name,  # For parks, park field is the park name itself
                    "category": "park",
                    "date_added": date_added.isoformat() if date_added else "",
                    "date_opened": opening_date.isoformat() if opening_date else "",
                    "url": park.url,
                    "card_image": card_image_url,
                    "city": city,
                    "state": state,
                    "country": country,
                    "primary_company": primary_company,
                }
            )

        return results

    def _get_new_rides(self, cutoff_date: datetime, limit: int) -> list[dict[str, Any]]:
        """Get recently added rides."""
        new_rides = (
            Ride.objects.filter(
                Q(created_at__gte=cutoff_date) | Q(opening_date__gte=cutoff_date.date()),
                status="OPERATING",
            )
            .select_related("park", "park__location", "card_image")
            .order_by("-created_at", "-opening_date")[:limit]
        )

        results = []
        for ride in new_rides:
            date_added = getattr(ride, "opening_date", None) or getattr(ride, "created_at", None)
            # Handle datetime to date conversion
            if date_added:  # noqa: SIM102
                # If it's a datetime, convert to date
                if isinstance(date_added, datetime):
                    date_added = date_added.date()
                # If it's already a date, keep it as is

            # Get opening date for date_opened field
            opening_date = getattr(ride, "opening_date", None)
            if opening_date and isinstance(opening_date, datetime):
                opening_date = opening_date.date()

            # Get card image URL
            card_image_url = ""
            if ride.card_image and hasattr(ride.card_image, "image"):
                card_image_url = ride.card_image.image.public_url if ride.card_image.image else ""

            results.append(
                {
                    "content_object": ride,
                    "content_type": "ride",
                    "id": ride.pk,  # Use pk instead of id for Django compatibility
                    "name": ride.name,
                    "slug": ride.slug,
                    "park": ride.park.name if ride.park else "",
                    "category": "ride",
                    "date_added": date_added.isoformat() if date_added else "",
                    "date_opened": opening_date.isoformat() if opening_date else "",
                    "url": ride.url,
                    "park_url": ride.park.url if ride.park else "",
                    "card_image": card_image_url,
                }
            )

        return results

    def _format_trending_results(self, trending_items: list[dict[str, Any]]) -> list[dict[str, Any]]:
        """Format trending results for frontend consumption."""
        formatted_results = []

        for rank, item in enumerate(trending_items, 1):
            try:
                # Get view change for display
                content_obj = item["content_object"]
                ct = ContentType.objects.get_for_model(content_obj)
                current_views, previous_views, growth_percentage = PageView.get_views_growth(
                    ct,
                    content_obj.id,
                    self.CURRENT_PERIOD_HOURS,
                    self.PREVIOUS_PERIOD_HOURS,
                )

                # Format exactly as frontend expects
                formatted_item = {
                    "id": item["id"],
                    "name": item["name"],
                    "park": item["park"],
                    "category": item["category"],
                    "rating": item["rating"],
                    "rank": rank,
                    "views": current_views,
                    "views_change": (
                        f"+{growth_percentage:.1f}%" if growth_percentage > 0 else f"{growth_percentage:.1f}%"
                    ),
                    "slug": item["slug"],
                    "date_opened": item["date_opened"],
                    "url": item["url"],
                }

                # Add card_image for all items
                if item.get("card_image"):
                    formatted_item["card_image"] = item["card_image"]

                # Add park-specific fields
                if item["content_type"] == "park":
                    if item.get("city"):
                        formatted_item["city"] = item["city"]
                    if item.get("state"):
                        formatted_item["state"] = item["state"]
                    if item.get("country"):
                        formatted_item["country"] = item["country"]
                    if item.get("primary_company"):
                        formatted_item["primary_company"] = item["primary_company"]

                # Add park_url for rides
                if item.get("park_url"):
                    formatted_item["park_url"] = item["park_url"]

                formatted_results.append(formatted_item)

            except Exception as e:
                self.logger.warning(f"Error formatting trending item: {e}")

        return formatted_results

    def _format_new_content_results(self, new_items: list[dict[str, Any]]) -> list[dict[str, Any]]:
        """Format new content results for frontend consumption."""
        formatted_results = []

        for item in new_items:
            try:
                # Format exactly as frontend expects
                formatted_item = {
                    "id": item["id"],
                    "name": item["name"],
                    "park": item["park"],
                    "category": item["category"],
                    "date_added": item["date_added"],
                    "date_opened": item["date_opened"],
                    "slug": item["slug"],
                    "url": item["url"],
                }

                # Add card_image for all items
                if item.get("card_image"):
                    formatted_item["card_image"] = item["card_image"]

                # Add park-specific fields
                if item["content_type"] == "park":
                    if item.get("city"):
                        formatted_item["city"] = item["city"]
                    if item.get("state"):
                        formatted_item["state"] = item["state"]
                    if item.get("country"):
                        formatted_item["country"] = item["country"]
                    if item.get("primary_company"):
                        formatted_item["primary_company"] = item["primary_company"]

                # Add park_url for rides
                if item.get("park_url"):
                    formatted_item["park_url"] = item["park_url"]

                formatted_results.append(formatted_item)

            except Exception as e:
                self.logger.warning(f"Error formatting new content item: {e}")

        return formatted_results

    def clear_cache(self, content_type: str = "all") -> None:
        """Clear trending and new content caches."""
        try:
            cache_patterns = [
                f"{self.CACHE_PREFIX}:trending:{content_type}:*",
                f"{self.CACHE_PREFIX}:new:{content_type}:*",
            ]

            if content_type == "all":
                cache_patterns.extend(
                    [
                        f"{self.CACHE_PREFIX}:trending:parks:*",
                        f"{self.CACHE_PREFIX}:trending:rides:*",
                        f"{self.CACHE_PREFIX}:new:parks:*",
                        f"{self.CACHE_PREFIX}:new:rides:*",
                    ]
                )

            # Note: This is a simplified cache clear
            # In production, you might want to use cache.delete_many() or similar
            cache.clear()
            self.logger.info(f"Cleared trending caches for {content_type}")

        except Exception as e:
            capture_and_log(e, f'Clear trending cache ({content_type})', source='service', severity='low')


# Singleton service instance
trending_service = TrendingService()
