"""
Auto Cluster Keywords AI Function
"""
import logging
from typing import Dict, List, Any
from django.db import transaction
from igny8_core.ai.base import BaseAIFunction
from igny8_core.modules.planner.models import Keywords, Clusters
from igny8_core.ai.ai_core import AICore
from igny8_core.ai.prompts import PromptRegistry
from igny8_core.ai.settings import get_model_config

logger = logging.getLogger(__name__)


class AutoClusterFunction(BaseAIFunction):
    """Auto-cluster keywords using AI"""
    
    def get_name(self) -> str:
        return 'auto_cluster'
    
    def get_metadata(self) -> Dict:
        return {
            'display_name': 'Auto Cluster Keywords',
            'description': 'Group related keywords into semantic clusters using AI',
            'phases': {
                'INIT': 'Initializing clustering...',
                'PREP': 'Loading keywords...',
                'AI_CALL': 'Analyzing keyword relationships...',
                'PARSE': 'Parsing cluster data...',
                'SAVE': 'Creating clusters...',
                'DONE': 'Clustering complete!'
            }
        }
    
    def get_max_items(self) -> int:
        # No limit - return None
        return None
    
    def validate(self, payload: dict, account=None) -> Dict:
        """Custom validation for clustering"""
        from igny8_core.ai.validators import validate_ids, validate_keywords_exist
        
        # Base validation (no max_items limit)
        result = validate_ids(payload, max_items=None)
        if not result['valid']:
            return result
        
        # Check keywords exist
        ids = payload.get('ids', [])
        keywords_result = validate_keywords_exist(ids, account)
        if not keywords_result['valid']:
            return keywords_result
        
        # Removed plan limits check
        
        return {'valid': True}
    
    def prepare(self, payload: dict, account=None) -> Dict:
        """Load keywords with relationships"""
        ids = payload.get('ids', [])
        sector_id = payload.get('sector_id')
        
        queryset = Keywords.objects.filter(id__in=ids)
        if account:
            queryset = queryset.filter(account=account)
        if sector_id:
            queryset = queryset.filter(sector_id=sector_id)
        
        keywords = list(queryset.select_related('account', 'site', 'site__account', 'sector', 'sector__site'))
        
        if not keywords:
            raise ValueError("No keywords found")
        
        # Store original keyword objects for later use
        return {
            'keywords': keywords,
            'keyword_data': [
                {
                    'id': kw.id,
                    'keyword': kw.keyword,
                    'volume': kw.volume,
                    'difficulty': kw.difficulty,
                    'intent': kw.intent,
                }
                for kw in keywords
            ],
            'sector_id': sector_id
        }
    
    def build_prompt(self, data: Dict, account=None) -> str:
        """Build clustering prompt using registry"""
        keyword_data = data['keyword_data']
        sector_id = data.get('sector_id')
        
        # Format keywords
        keywords_text = '\n'.join([
            f"- {kw['keyword']} (Volume: {kw['volume']}, Difficulty: {kw['difficulty']}, Intent: {kw['intent']})"
            for kw in keyword_data
        ])
        
        # Build context
        context = {'KEYWORDS': keywords_text}
        
        # Add sector context if available
        if sector_id:
            try:
                from igny8_core.auth.models import Sector
                sector = Sector.objects.get(id=sector_id)
                if sector:
                    context['SECTOR'] = sector.name
            except Exception:
                pass
        
        # Get prompt from registry
        prompt = PromptRegistry.get_prompt(
            function_name='auto_cluster',
            account=account,
            context=context
        )
        
        # Verify placeholder replacement
        if '[IGNY8_KEYWORDS]' in prompt:
            logger.error(f"[IGNY8_KEYWORDS] placeholder NOT replaced! Prompt length: {len(prompt)}")
        else:
            logger.info(f"Prompt placeholder replaced successfully. Prompt length: {len(prompt)}, Keywords text length: {len(keywords_text)}")
        
        # IMPORTANT: When using JSON mode, OpenAI requires explicit JSON instruction
        # The prompt template already includes "Format the output as a JSON object"
        # but we need to ensure it's explicit for JSON mode compliance
        prompt_lower = prompt.lower()
        has_json_request = (
            'json' in prompt_lower and 
            ('format' in prompt_lower or 'respond' in prompt_lower or 'return' in prompt_lower or 'output' in prompt_lower)
        )
        
        if not has_json_request:
            prompt += "\n\nIMPORTANT: You must respond with valid JSON only. The response must be a JSON object with a 'clusters' array."
        
        return prompt
    
    def parse_response(self, response: str, step_tracker=None) -> List[Dict]:
        """Parse AI response into cluster data"""
        import json
        from igny8_core.ai.ai_core import AICore
        
        if not response or not response.strip():
            error_msg = "Empty response from AI"
            logger.error(f"parse_response: {error_msg}")
            raise ValueError(error_msg)
        
        # Try direct JSON parse first (most common case with JSON mode)
        json_data = None
        try:
            json_data = json.loads(response.strip())
        except json.JSONDecodeError as e:
            logger.warning(f"parse_response: Direct JSON parse failed: {e}, trying extract_json method")
            # Fall back to extract_json method which handles markdown code blocks
            ai_core = AICore(account=getattr(self, 'account', None))
            json_data = ai_core.extract_json(response)
        
        if not json_data:
            error_msg = f"Failed to parse clustering response. Response: {response[:200]}..."
            logger.error(f"parse_response: {error_msg}")
            raise ValueError(error_msg)
        
        # Extract clusters array
        clusters = []
        if isinstance(json_data, dict):
            if 'clusters' in json_data:
                clusters = json_data.get('clusters', [])
            else:
                # Try to find clusters in any key
                for key, value in json_data.items():
                    if isinstance(value, list) and len(value) > 0:
                        if isinstance(value[0], dict) and ('name' in value[0] or 'keywords' in value[0]):
                            clusters = value
                            break
        elif isinstance(json_data, list):
            clusters = json_data
        
        if not clusters:
            error_msg = f"No clusters found in AI response. JSON data: {json_data}"
            logger.error(f"parse_response: {error_msg}")
            raise ValueError(error_msg)
        # Step tracking is handled by the engine - don't add steps here
        return clusters
    
    def save_output(
        self,
        parsed: List[Dict],
        original_data: Dict,
        account=None,
        progress_tracker=None,
        step_tracker=None
    ) -> Dict:
        """Save clusters to database"""
        keywords = original_data['keywords']
        sector_id = original_data.get('sector_id')
        
        if not keywords:
            raise ValueError("No keywords available for saving")
        
        # Get context from first keyword (account/site/sector already validated at page level)
        first_keyword = keywords[0]
        account = account or first_keyword.account
        site = first_keyword.site
        
        # Get sector if needed
        from igny8_core.auth.models import Sector
        sector = first_keyword.sector
        if not sector and sector_id:
            try:
                sector = Sector.objects.get(id=sector_id)
            except Sector.DoesNotExist:
                sector = None
        
        if not account:
            raise ValueError("Account is required for cluster creation")
        
        clusters_created = 0
        keywords_updated = 0
        
        with transaction.atomic():
            for idx, cluster_data in enumerate(parsed):
                if progress_tracker:
                    progress = 80 + int((idx / len(parsed)) * 15)
                    progress_tracker.update(
                        "SAVE",
                        progress,
                        f"Creating cluster {idx + 1}/{len(parsed)}...",
                        current=idx + 1,
                        total=len(parsed),
                        current_item=cluster_data.get('name', '')
                    )
                
                cluster_name = cluster_data.get('name', '')
                cluster_keywords = cluster_data.get('keywords', [])
                
                if not cluster_name or not cluster_keywords:
                    continue
                
                # Get or create cluster
                if sector:
                    cluster, created = Clusters.objects.get_or_create(
                        name=cluster_name,
                        account=account,
                        site=site,
                        sector=sector,
                        defaults={
                            'description': cluster_data.get('description', ''),
                            'status': 'active',
                        }
                    )
                else:
                    cluster, created = Clusters.objects.get_or_create(
                        name=cluster_name,
                        account=account,
                        site=site,
                        sector__isnull=True,
                        defaults={
                            'description': cluster_data.get('description', ''),
                            'status': 'active',
                            'sector': None,
                        }
                    )
                
                if created:
                    clusters_created += 1
                
                # Match and assign keywords (case-insensitive)
                cluster_keywords_normalized = {kw.strip().lower(): kw.strip() for kw in cluster_keywords}
                available_keywords_normalized = {
                    kw_obj.keyword.strip().lower(): kw_obj
                    for kw_obj in keywords
                }
                
                matched_keyword_objects = []
                for cluster_kw_normalized, cluster_kw_original in cluster_keywords_normalized.items():
                    if cluster_kw_normalized in available_keywords_normalized:
                        matched_keyword_objects.append(available_keywords_normalized[cluster_kw_normalized])
                
                # Update matched keywords
                if matched_keyword_objects:
                    matched_ids = [kw.id for kw in matched_keyword_objects]
                    keyword_filter = Keywords.objects.filter(
                        id__in=matched_ids,
                        account=account
                    )
                    if sector:
                        keyword_filter = keyword_filter.filter(sector=sector)
                    else:
                        keyword_filter = keyword_filter.filter(sector__isnull=True)
                    
                    updated_count = keyword_filter.update(
                        cluster=cluster,
                        status='mapped'
                    )
                    keywords_updated += updated_count
            
            # Recalculate cluster metrics
            from django.db.models import Sum, Case, When, F, IntegerField
            cluster_filter = Clusters.objects.filter(account=account)
            if sector:
                cluster_filter = cluster_filter.filter(sector=sector)
            else:
                cluster_filter = cluster_filter.filter(sector__isnull=True)
            
            for cluster in cluster_filter:
                cluster.keywords_count = Keywords.objects.filter(cluster=cluster).count()
                # Volume calculation: use volume_override if available, otherwise seed_keyword__volume
                volume_sum = Keywords.objects.filter(cluster=cluster).aggregate(
                    total=Sum(
                        Case(
                            When(volume_override__isnull=False, then=F('volume_override')),
                            default=F('seed_keyword__volume'),
                            output_field=IntegerField()
                        )
                    )
                )['total']
                cluster.volume = volume_sum or 0
                cluster.save()
        
        return {
            'count': clusters_created,
            'clusters_created': clusters_created,
            'keywords_updated': keywords_updated
        }

