"""
Niche Prediction Service

This service handles AI/ML-based niche prediction for influencers:
1. Collecting post captions and hashtags
2. Preparing data for ML service
3. Calling ML service for predictions
4. Saving predicted niche and probabilities

Separation of Concerns:
- This service focuses ONLY on niche prediction
- Data fetching is handled by DataService
- OAuth is handled by OAuthService
- ML inference is handled by MLClient
"""

import logging
from typing import Dict, List, Optional

from ..models import InfluencerProfile, MediaPost
from ..constants import NICHE_KEYWORDS, DEFAULT_NICHE
from core.services import MLClient


logger = logging.getLogger(__name__)


class NicheService:
    """
    Service class for handling niche prediction operations.
    
    This class encapsulates all niche prediction logic, making it
    easy to swap out the simple keyword-based approach with a
    real ML model in the future.
    """
    
    @staticmethod
    def predict_niche(profile: InfluencerProfile) -> str:
        """
        Predict the content niche for an influencer based on their posts.

        Args:
            profile: InfluencerProfile instance

        Returns:
            str: Predicted niche (e.g., 'Fashion', 'Fitness', 'Food')

        Flow:
            1. Collect all post captions and hashtags
            2. Prepare data for ML service
            3. Call ML service for prediction
            4. Save probabilities to profile
            5. Return top predicted niche
            6. Fallback to keyword-based if ML service fails
        """
        logger.info(f"Predicting niche for {profile.instagram_handle}")

        # Collect all captions
        posts = MediaPost.objects.filter(influencer=profile)

        # Concatenate captions
        all_captions = " ".join([
            post.caption for post in posts
            if post.caption
        ])

        # Extract hashtags from captions (since MediaPost doesn't have a separate hashtags field)
        all_hashtags = ""
        for post in posts:
            if post.caption:
                # Extract words that start with #
                hashtags = [word for word in post.caption.split() if word.startswith('#')]
                all_hashtags += " ".join(hashtags) + " "

        logger.info(f"Prepared {len(posts)} posts for niche prediction "
                   f"(captions: {len(all_captions)} chars, hashtags: {len(all_hashtags.strip())} chars)")

        # Try ML service first
        ml_result = NicheService._ml_prediction(profile, all_captions, all_hashtags)

        if ml_result:
            logger.info(f"ML prediction successful for {profile.instagram_handle}: {ml_result}")
            return ml_result

        # Fallback to keyword-based prediction
        logger.warning(f"ML service unavailable, using keyword-based prediction for {profile.instagram_handle}")
        predicted_niche = NicheService._simple_keyword_prediction(all_captions)

        logger.info(f"Keyword-based prediction for {profile.instagram_handle}: {predicted_niche}")

        return predicted_niche
    
    @staticmethod
    def _ml_prediction(profile: InfluencerProfile, captions: str, hashtags: str) -> Optional[str]:
        """
        Use ML service to predict niche and popularity

        Args:
            profile: InfluencerProfile instance
            captions: Concatenated captions
            hashtags: Concatenated hashtags

        Returns:
            str: Top predicted niche, or None if ML service fails
        """
        try:
            ml_client = MLClient()

            # Prepare data for ML service
            # Note: Using available fields from InfluencerProfile model
            result = ml_client.predict_influencer(
                biography=profile.biography or "",
                recent_captions=captions,
                recent_hashtags=hashtags,
                followers=profile.followers_count,
                following=profile.following_count,
                posts_count=profile.media_count,
                verified=bool(profile.verification_status),  # Use verification_status field
                avg_likes=float(profile.avg_likes or 0),
                avg_comments=float(profile.avg_comments or 0),
                avg_video_views=float(profile.ave_video_views or 0),
                engagement_rate=float(profile.engagement_rate_percent or 0)
            )

            if not result:
                return None

            # Save probabilities to profile
            profile.niche_probabilities = result.get('niche_probabilities', {})
            profile.popularity_probabilities = result.get('popularity_probabilities', {})

            # Extract top predictions
            top_niche = result.get('processed_data', {}).get('top_niche')
            top_popularity = result.get('processed_data', {}).get('top_popularity')

            # Save top predictions
            profile.ai_predicted_niche = top_niche
            profile.ai_predicted_popularity = top_popularity

            # Generate niche explanation
            NicheService._generate_niche_explanation(profile)

            # Calculate and save estimated pricing (with tier explanation)
            # Don't fail the entire ML prediction if pricing fails
            try:
                NicheService._calculate_estimated_pricing(profile)
            except Exception as e:
                logger.warning(f"Pricing calculation failed but ML prediction succeeded: {e}")

            profile.save(update_fields=[
                'niche_probabilities',
                'popularity_probabilities',
                'ai_predicted_niche',
                'ai_predicted_popularity',
                'niche_classification_explanation',  # NEW: Niche explanation
                'estimated_price_min',
                'estimated_price_max',
                'estimated_price_avg',
                'tier_classification_explanation'  # NEW: Hybrid tier explanation
            ])

            # Return top niche
            return top_niche

        except Exception as e:
            logger.error(f"ML prediction failed: {e}", exc_info=True)
            return None

    @staticmethod
    def _generate_niche_explanation(profile):
        """
        Generate human-readable explanation for niche classification.

        Args:
            profile: InfluencerProfile instance
        """
        niche_probs = profile.niche_probabilities or {}
        top_niche = profile.ai_predicted_niche

        if not top_niche or not niche_probs:
            return

        # Get top 3 niches
        sorted_niches = sorted(niche_probs.items(), key=lambda x: x[1], reverse=True)[:3]
        top_prob = niche_probs.get(top_niche, 0)

        # Check if it's "Other" (lack of context)
        if top_niche == "Other" and top_prob >= 0.6:
            profile.niche_classification_explanation = (
                f"Classified as 'Other' niche due to limited context in bio and posts. "
                f"As your content grows, we'll be able to provide a more specific niche classification. "
                f"Consider adding more descriptive content to your bio and posts."
            )
        elif len(sorted_niches) >= 2 and sorted_niches[1][1] >= 0.2:
            # Multiple strong niches (multi-niche creator)
            secondary_niche = sorted_niches[1][0]
            secondary_prob = sorted_niches[1][1]
            profile.niche_classification_explanation = (
                f"Classified as {top_niche} niche ({top_prob:.0%} confidence) based on your content themes, "
                f"bio, and hashtags. You also show strong presence in {secondary_niche} ({secondary_prob:.0%}), "
                f"making you a versatile creator for cross-niche collaborations."
            )
        else:
            # Clear single niche
            profile.niche_classification_explanation = (
                f"Classified as {top_niche} niche ({top_prob:.0%} confidence) based on analysis of your "
                f"bio, post captions, and hashtags. Your content consistently focuses on {top_niche.lower()}-related themes."
            )

    @staticmethod
    def _calculate_estimated_pricing(profile):
        """
        Calculate and set estimated pricing for influencer profile using HYBRID approach.

        HYBRID PRICING MODEL:
        1. Calculate base price using follower-count-based tier (predictable)
        2. Apply quality multipliers (engagement, verification, bot detection)
        3. If LLM predicted different tier, apply tier adjustment multiplier
        4. Result: Consistent pricing that reflects both quantity AND quality

        This ensures:
        - Mega influencers with different follower counts get different pricing
        - High-quality Micro influencers can get Mid-tier pricing
        - Low-quality Mid influencers get downgraded pricing
        - Tier and price are always consistent

        Args:
            profile: InfluencerProfile instance
        """
        try:
            # Import pricing utilities from backend core (no longer importing from ml_service)
            from core.utils.pricing import calculate_estimated_price

            followers = profile.followers_count
            engagement_rate = profile.engagement_rate_percent
            verified = profile.verification_status
            avg_likes = float(profile.avg_likes or 0)
            avg_comments = float(profile.avg_comments or 0)
            llm_tier = profile.ai_predicted_popularity  # LLM-predicted tier based on quality

            # Use the centralized pricing function with hybrid approach
            pricing_result = calculate_estimated_price(
                followers=followers,
                engagement_rate=engagement_rate,
                verified=verified,
                avg_likes=avg_likes,
                avg_comments=avg_comments,
                llm_predicted_tier=llm_tier  # Pass LLM tier for hybrid pricing
            )

            # Set pricing fields from result
            profile.estimated_price_min = pricing_result['estimated_min']
            profile.estimated_price_max = pricing_result['estimated_max']
            profile.estimated_price_avg = pricing_result['estimated_avg']

            # Store tier information for transparency
            benchmark_tier = pricing_result['tier']
            is_adjusted = pricing_result['is_quality_adjusted']
            q_multiplier = pricing_result['quality_multiplier']

            # Generate tier classification explanation
            if is_adjusted and llm_tier:
                # Determine if it was an upgrade or downgrade for the message
                tier_order = ["Nano", "Micro", "Mid", "Macro", "Mega"]
                try:
                    benchmark_idx = tier_order.index(benchmark_tier)
                    current_idx = tier_order.index(llm_tier)
                    direction = "upgraded" if current_idx > benchmark_idx else "downgraded"
                    
                    profile.tier_classification_explanation = (
                        f"Classified as {llm_tier} tier ({direction} from {benchmark_tier} benchmark). "
                        f"Pricing is adjusted based on quality signals (multiplier: {q_multiplier}x) "
                        f"including engagement rate, authenticity, and audience interaction."
                    )
                except (ValueError, IndexError):
                    profile.tier_classification_explanation = (
                        f"Classified as {llm_tier} quality-tier. Pricing accounts for "
                        f"engagement quality and audience authenticity."
                    )
            elif llm_tier:
                profile.tier_classification_explanation = (
                    f"Classified as {llm_tier} tier based on {followers:,} followers and quality metrics. "
                    f"Pricing is consistent with typical {llm_tier} tier performers."
                )
        except ImportError as e:
            # If core.utils.pricing is not available, skip pricing calculation
            logger.warning(f"Could not import pricing module from core.utils: {e}. Skipping pricing calculation.")
        except Exception as e:
            logger.error(f"Error calculating estimated pricing: {e}", exc_info=True)

    @staticmethod
    def _simple_keyword_prediction(text: str) -> str:
        """
        Simple keyword-based niche prediction (placeholder for real ML model).
        
        This is a TEMPORARY implementation. In production, this should be
        replaced with a trained PyTorch NLP model.
        
        Args:
            text: Concatenated captions
        
        Returns:
            str: Predicted niche
            
        How it works:
            1. Convert text to lowercase
            2. Check for presence of niche-specific keywords
            3. Return first matching niche
            4. Default to 'lifestyle' if no match
            
        TODO: Replace with PyTorch model
            Example:
            ```python
            from .ml_model import NicheClassifier
            
            model = NicheClassifier.load_model('path/to/model.pth')
            prediction = model.predict(text)
            return prediction['niche']
            ```
        """
        text_lower = text.lower()
        
        # Check each niche's keywords
        for niche, keywords in NICHE_KEYWORDS.items():
            if any(word in text_lower for word in keywords):
                return niche
        
        # Default niche if no keywords match
        return DEFAULT_NICHE
    
    @staticmethod
    def get_all_niches() -> List[str]:
        """
        Get list of all available niches.
        
        Returns:
            list: List of niche names
        """
        return list(NICHE_KEYWORDS.keys()) + [DEFAULT_NICHE]
    
    @staticmethod
    def get_niche_keywords(niche: str) -> List[str]:
        """
        Get keywords associated with a specific niche.
        
        Args:
            niche: Niche name
            
        Returns:
            list: List of keywords for the niche
        """
        return NICHE_KEYWORDS.get(niche, [])

