from django.db import models
from django.db.models import Q
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
from .utils.template_validation import validate_template, get_template_statistics, log_template_change
from django.utils import timezone
from django.contrib.auth import get_user_model

# Create your models here.

class PromptTemplate(models.Model):
    """
    Model for storing and managing AI prompt templates.
    Templates can have different statuses (draft, active, archived).
    """
    
    DIVINATION_TYPE_CHOICES = [
        ('bazi', 'BaZi'),
        ('number', 'Number Power'),
        ('liuyao', 'LiuYao'),
    ]
    
    LANGUAGE_CHOICES = [
        ('zh-hans', 'Chinese (Simplified)'),
        ('en', 'English'),
    ]

    STATUS_CHOICES = [
        ('draft', 'Draft'),
        ('active', 'Active'),
        ('archived', 'Archived'),
    ]
    
    name = models.CharField(max_length=100, verbose_name=_('Name'))
    description = models.TextField(blank=True, verbose_name=_('Description'))
    content = models.TextField(verbose_name=_('Content'))
    divination_type = models.CharField(
        max_length=10, 
        choices=DIVINATION_TYPE_CHOICES, 
        verbose_name=_('Divination Type')
    )
    language = models.CharField(
        max_length=10,
        choices=LANGUAGE_CHOICES,
        default='zh-hans',
        verbose_name=_('Language'),
        help_text=_('Language for this prompt template')
    )
    status = models.CharField(
        max_length=10, 
        choices=STATUS_CHOICES, 
        default='draft', 
        verbose_name=_('Status')
    )
    
    # Track history
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='created_templates',
        verbose_name=_('Created By')
    )
    modified_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='modified_templates',
        verbose_name=_('Last Modified By')
    )
    created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Created At'))
    updated_at = models.DateTimeField(auto_now=True, verbose_name=_('Updated At'))
    
    # Validation fields
    validation_warnings = models.JSONField(
        null=True, 
        blank=True, 
        verbose_name=_('Validation Warnings'),
        help_text=_('Warnings from template validation')
    )
    last_validated = models.DateTimeField(
        null=True, 
        blank=True, 
        verbose_name=_('Last Validated')
    )
    
    class Meta:
        ordering = ['-updated_at']
        verbose_name = _('Prompt Template')
        verbose_name_plural = _('Prompt Templates')
        unique_together = ['divination_type', 'name', 'language']
        constraints = [
            models.UniqueConstraint(
                fields=['divination_type', 'language'],
                condition=Q(status='active'),
                name='unique_active_template_per_language'
            )
        ]
    
    def __str__(self):
        return f"{self.name} ({self.divination_type}, {self.language})"
    
    def clean(self):
        """Validate the template content before saving."""
        super().clean()
        
        # Validate template content
        is_valid, warnings = validate_template(self.content, self.divination_type)
        
        # Store validation results
        self.validation_warnings = warnings if warnings else None
        
        # If template is being set to active, ensure it's valid
        if self.status == 'active' and not is_valid:
            # Convert translation proxies to strings before joining
            warning_messages = [str(warning) for warning in warnings]
            raise ValidationError({
                'content': _('Template must be valid before it can be activated. Please fix the following issues: ') + 
                          ', '.join(warning_messages)
            })
    
    def save(self, *args, **kwargs):
        """
        When a template becomes active, make sure to set any other active templates
        of the same divination type to archived.
        """
        # If this template is being set to active, archive other active templates
        if self.status == 'active':
            PromptTemplate.objects.filter(
                divination_type=self.divination_type,
                language=self.language,
                status='active'
            ).exclude(pk=self.pk).update(status='archived')
            
        # Log the template change
        if self.pk:  # Update
            log_template_change(
                self.pk,
                self.modified_by.id if self.modified_by else None,
                'updated',
                [str(warning) for warning in (self.validation_warnings or [])]
            )
        else:  # Create
            log_template_change(
                None,
                self.created_by.id if self.created_by else None,
                'created',
                [str(warning) for warning in (self.validation_warnings or [])]
            )
            
        super().save(*args, **kwargs)
    
    def get_statistics(self):
        """Get template statistics."""
        return get_template_statistics(self.content)
    
    def validate(self):
        """Manually validate the template and update validation fields."""
        is_valid, warnings = validate_template(self.content, self.divination_type)
        self.validation_warnings = warnings if warnings else None
        self.last_validated = timezone.now()
        self.save(update_fields=['validation_warnings', 'last_validated'])
        return is_valid, warnings


class PromptPlaceholder(models.Model):
    """
    Model for documenting placeholders used in prompt templates.
    This helps administrators understand what variables are available for templates.
    """
    name = models.CharField(max_length=100, verbose_name=_('Placeholder Name'), unique=True)
    description = models.TextField(verbose_name=_('Description'))
    example_value = models.TextField(blank=True, verbose_name=_('Example Value'))
    divination_type = models.CharField(
        max_length=10, 
        choices=PromptTemplate.DIVINATION_TYPE_CHOICES, 
        verbose_name=_('Divination Type')
    )
    
    class Meta:
        ordering = ['name']
        verbose_name = _('Prompt Placeholder')
        verbose_name_plural = _('Prompt Placeholders')
    
    def __str__(self):
        return f"{{{{{self.name}}}}} - {self.description[:30]}"


class AIProviderConfig(models.Model):
    """
    Model for storing AI provider and model configurations for different divination types.
    Allows admins to dynamically configure which AI provider and model to use as default.
    """
    
    DIVINATION_TYPE_CHOICES = [
        ('bazi', 'BaZi'),
        ('number', 'Number Power'),
        ('liuyao', 'LiuYao'),
        ('bazi_conversation', 'BaZi Conversation'),
        ('liuyao_conversation', 'LiuYao Conversation'),
    ]
    
    PROVIDER_CHOICES = [
        ('groq', 'Groq'),
        ('openai', 'OpenAI'),
    ]
    
    # Available models for each provider
    GROQ_MODEL_CHOICES = [
        ('llama-3.3-70b-versatile', 'Llama 3.3 70B (Best quality & context - 128k)'),
        ('meta-llama/llama-4-maverick-17b-128e-instruct', 'Llama 4 Maverick 17B (Good balance - 128k)'),
        ('llama-3.1-8b-instant', 'Llama 3.1 8B (Faster - 128k)'),
    ]
    
    OPENAI_MODEL_CHOICES = [
        ('gpt-4o', 'GPT-4o (Latest version, best quality)'),
        ('gpt-4-turbo', 'GPT-4 Turbo (Fast with good quality)'),
        ('gpt-3.5-turbo', 'GPT-3.5 Turbo (Fastest, good for simpler tasks)'),
        ('gpt-4', 'GPT-4 (Original GPT-4)'),
        ('gpt-4.1', 'GPT-4.1'),
        ('gpt-5', 'GPT-5'),
    ]
    
    divination_type = models.CharField(
        max_length=20,
        choices=DIVINATION_TYPE_CHOICES,
        unique=True,
        verbose_name=_('Divination Type'),
        help_text=_('The type of divination this configuration applies to')
    )
    
    provider = models.CharField(
        max_length=10,
        choices=PROVIDER_CHOICES,
        verbose_name=_('AI Provider'),
        help_text=_('The AI provider to use for this divination type')
    )
    
    model = models.CharField(
        max_length=100,
        verbose_name=_('Model'),
        help_text=_('The specific model to use with the selected provider')
    )
    
    is_active = models.BooleanField(
        default=True,
        verbose_name=_('Is Active'),
        help_text=_('Whether this configuration is currently active')
    )
    
    created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Created At'))
    updated_at = models.DateTimeField(auto_now=True, verbose_name=_('Updated At'))
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='created_ai_configs',
        verbose_name=_('Created By')
    )
    modified_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='modified_ai_configs',
        verbose_name=_('Last Modified By')
    )
    
    class Meta:
        ordering = ['divination_type']
        verbose_name = _('AI Provider Configuration')
        verbose_name_plural = _('AI Provider Configurations')
    
    def __str__(self):
        return f"{self.get_divination_type_display()}: {self.get_provider_display()} - {self.model}"
    
    def clean(self):
        """Validate that the model is appropriate for the selected provider."""
        super().clean()
        
        if self.provider == 'groq':
            valid_models = [choice[0] for choice in self.GROQ_MODEL_CHOICES]
            if self.model not in valid_models:
                raise ValidationError({
                    'model': _('Selected model is not valid for Groq provider. Valid options: {}').format(
                        ', '.join(valid_models)
                    )
                })
        elif self.provider == 'openai':
            valid_models = [choice[0] for choice in self.OPENAI_MODEL_CHOICES]
            if self.model not in valid_models:
                raise ValidationError({
                    'model': _('Selected model is not valid for OpenAI provider. Valid options: {}').format(
                        ', '.join(valid_models)
                    )
                })
    
    @classmethod
    def get_config(cls, divination_type):
        """
        Get the active configuration for a divination type.
        Returns fallback from settings if no config exists.
        """
        try:
            config = cls.objects.get(divination_type=divination_type, is_active=True)
            return {
                'provider': config.provider,
                'model': config.model
            }
        except cls.DoesNotExist:
            # Fallback to settings
            from django.conf import settings
            
            # Get fallback values from settings
            provider = getattr(settings, 'DEFAULT_LLM_PROVIDER', 'groq')
            
            if provider == 'groq':
                model = getattr(settings, 'GROQ_MODEL', 'llama-3.3-70b-versatile')
            else:
                model = getattr(settings, 'OPENAI_MODEL', 'gpt-4o')
            
            return {
                'provider': provider,
                'model': model
            }
    
    @classmethod
    def get_available_models(cls, provider):
        """Get available models for a specific provider."""
        if provider == 'groq':
            return cls.GROQ_MODEL_CHOICES
        elif provider == 'openai':
            return cls.OPENAI_MODEL_CHOICES
        return []


class ConversationConfig(models.Model):
    """
    Model for storing conversation configuration settings.
    Allows admins to dynamically configure conversation limits and settings.
    Only one active configuration should exist at a time.
    """
    
    max_messages = models.IntegerField(
        default=25,
        verbose_name=_('Max Messages Per Conversation'),
        help_text=_('Maximum number of messages allowed per conversation (only messages with status="sent" count)')
    )
    
    is_active = models.BooleanField(
        default=True,
        verbose_name=_('Is Active'),
        help_text=_('Whether this configuration is currently active')
    )
    
    created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Created At'))
    updated_at = models.DateTimeField(auto_now=True, verbose_name=_('Updated At'))
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='created_conversation_configs',
        verbose_name=_('Created By')
    )
    modified_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='modified_conversation_configs',
        verbose_name=_('Last Modified By')
    )
    
    class Meta:
        ordering = ['-is_active', '-updated_at']
        verbose_name = _('Conversation Configuration')
        verbose_name_plural = _('Conversation Configurations')
    
    def __str__(self):
        return f"Max Messages: {self.max_messages} ({'Active' if self.is_active else 'Inactive'})"
    
    def clean(self):
        """Validate that max_messages is positive."""
        super().clean()
        if self.max_messages <= 0:
            raise ValidationError({
                'max_messages': _('Max messages must be greater than 0')
            })
    
    def save(self, *args, **kwargs):
        """When a config is set to active, deactivate other active configs."""
        if self.is_active:
            ConversationConfig.objects.filter(is_active=True).exclude(pk=self.pk).update(is_active=False)
        super().save(*args, **kwargs)
    
    @classmethod
    def get_max_messages(cls):
        """
        Get the maximum messages per conversation from active configuration.
        Returns fallback from settings if no config exists.
        """
        try:
            config = cls.objects.get(is_active=True)
            return config.max_messages
        except cls.DoesNotExist:
            # Fallback to settings
            from django.conf import settings
            return getattr(settings, 'CONVERSATION_MAX_MESSAGES', 25)


class AdminAIAnalysis(models.Model):
    PROVIDER_CHOICES = [
        ('groq', 'Groq'),
        ('openai', 'OpenAI'),
    ]
    title = models.CharField(max_length=200)
    prompt = models.TextField()
    provider = models.CharField(max_length=20, choices=PROVIDER_CHOICES)
    model = models.CharField(max_length=100)
    response = models.TextField(blank=True, null=True)
    status = models.CharField(max_length=20, default='pending')
    created_by = models.ForeignKey(get_user_model(), on_delete=models.SET_NULL, null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    meta = models.JSONField(default=dict, blank=True)

    class Meta:
        permissions = [
            ("can_send_admin_llm_analysis", "Can send admin LLM analysis request")
        ]
        verbose_name = _('AI Analysis')
        verbose_name_plural = _('AI Analysis')

    def __str__(self):
        return self.title


class ConversationSubject(models.Model):
    """
    Pivot table for linking conversations to different divination types.
    Supports BaZi (Person), LiuYao, and future divination types.
    """
    CONTENT_TYPE_CHOICES = [
        ('bazi', 'BaZi'),
        ('liuyao', 'LiuYao'),
    ]
    
    content_type = models.CharField(
        max_length=10,
        choices=CONTENT_TYPE_CHOICES,
        verbose_name=_('Content Type'),
        help_text=_('Type of divination record this subject represents')
    )
    object_id = models.IntegerField(
        verbose_name=_('Object ID'),
        help_text=_('ID of the related object (Person ID for BaZi, LiuYao ID for LiuYao)')
    )
    created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Created At'))
    
    class Meta:
        ordering = ['-created_at']
        verbose_name = _('Conversation Subject')
        verbose_name_plural = _('Conversation Subjects')
        unique_together = ['content_type', 'object_id']
        indexes = [
            models.Index(fields=['content_type', 'object_id']),
        ]
    
    def __str__(self):
        return f"{self.get_content_type_display()} #{self.object_id}"
    
    def get_object(self):
        """Get the actual object this subject refers to."""
        if self.content_type == 'bazi':
            from bazi.models import Person
            try:
                return Person.objects.get(pk=self.object_id)
            except Person.DoesNotExist:
                return None
        elif self.content_type == 'liuyao':
            from liuyao.models import liuyao
            try:
                return liuyao.objects.get(pk=self.object_id)
            except liuyao.DoesNotExist:
                return None
        return None
    
    @classmethod
    def get_or_create_subject(cls, content_type, object_id):
        """Get or create a ConversationSubject for the given content type and object ID."""
        subject, created = cls.objects.get_or_create(
            content_type=content_type,
            object_id=object_id
        )
        return subject


class Conversation(models.Model):
    """
    Model representing a conversation thread between a user and AI about a divination record.
    Each conversation is tied to a ConversationSubject (which links to BaZi, LiuYao, etc.) and user.
    """
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='conversations',
        verbose_name=_('User')
    )
    subject = models.ForeignKey(
        ConversationSubject,
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        related_name='conversations',
        verbose_name=_('Subject'),
        help_text=_('The divination record this conversation is about')
    )
    # Keep person field for backward compatibility during migration
    person = models.ForeignKey(
        'bazi.Person',
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        related_name='conversations_legacy',
        verbose_name=_('Person (Legacy)'),
        help_text=_('Legacy field - use subject instead. The BaZi chart this conversation is about')
    )
    title = models.CharField(
        max_length=200,
        blank=True,
        verbose_name=_('Title'),
        help_text=_('Optional title for the conversation')
    )
    created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Created At'))
    updated_at = models.DateTimeField(auto_now=True, verbose_name=_('Updated At'))
    
    # Context management fields
    context_summary = models.TextField(
        blank=True,
        null=True,
        verbose_name=_('Context Summary'),
        help_text=_('AI-generated summary of older conversation messages')
    )
    context_summary_updated_at = models.DateTimeField(
        null=True,
        blank=True,
        verbose_name=_('Context Summary Updated At'),
        help_text=_('When the context summary was last updated')
    )
    last_summarized_message_id = models.IntegerField(
        null=True,
        blank=True,
        verbose_name=_('Last Summarized Message ID'),
        help_text=_('ID of the last message included in the summary')
    )
    
    class Meta:
        ordering = ['-updated_at']
        verbose_name = _('Conversation')
        verbose_name_plural = _('Conversations')
        indexes = [
            models.Index(fields=['user', '-updated_at']),
            models.Index(fields=['subject', '-updated_at']),
            models.Index(fields=['person', '-updated_at']),  # Keep for backward compatibility
        ]
    
    def __str__(self):
        if self.subject:
            subject_obj = self.subject.get_object()
            if subject_obj:
                if self.subject.content_type == 'bazi':
                    name = subject_obj.name if hasattr(subject_obj, 'name') else 'Unknown'
                elif self.subject.content_type == 'liuyao':
                    name = subject_obj.question if hasattr(subject_obj, 'question') else 'Unknown'
                else:
                    name = 'Unknown'
            else:
                name = 'Unknown'
        elif self.person:
            # Legacy support
            name = self.person.name if hasattr(self.person, 'name') else 'Unknown'
        else:
            name = 'Unknown'
        title = self.title or f"Conversation about {name}"
        return f"{title} ({self.user})"
    
    def get_subject_object(self):
        """Get the actual object this conversation is about."""
        if self.subject:
            return self.subject.get_object()
        # Fallback to legacy person field
        return self.person
    
    def get_last_message(self):
        """Get the last message in this conversation."""
        return self.messages.order_by('-created_at').first()
    
    def get_message_count(self):
        """Get the total number of successfully sent messages in this conversation.
        Only counts messages with status='sent' or status=None (for backward compatibility).
        """
        # Count messages that are successfully sent (status='sent' or None for backward compatibility)
        return self.messages.filter(
            Q(status='sent') | Q(status__isnull=True)
        ).count()


class Message(models.Model):
    """
    Model representing a single message in a conversation.
    Messages can be from the user or from the AI assistant.
    """
    ROLE_CHOICES = [
        ('user', 'User'),
        ('assistant', 'Assistant'),
    ]
    
    STATUS_CHOICES = [
        ('pending', 'Pending'),
        ('sent', 'Sent'),
        ('failed', 'Failed'),
    ]
    
    conversation = models.ForeignKey(
        Conversation,
        on_delete=models.CASCADE,
        related_name='messages',
        verbose_name=_('Conversation')
    )
    role = models.CharField(
        max_length=10,
        choices=ROLE_CHOICES,
        verbose_name=_('Role'),
        help_text=_('Whether this message is from the user or assistant')
    )
    content = models.TextField(verbose_name=_('Content'))
    created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Created At'))
    
    # Message status (for user messages: pending -> sent/failed)
    status = models.CharField(
        max_length=10,
        choices=STATUS_CHOICES,
        null=True,
        blank=True,
        verbose_name=_('Status'),
        help_text=_('Status of the message: pending (waiting for AI), sent (successful), failed (error occurred)')
    )
    error_message = models.TextField(
        null=True,
        blank=True,
        verbose_name=_('Error Message'),
        help_text=_('Error message if status is failed')
    )
    
    # Metadata for AI messages
    provider = models.CharField(
        max_length=20,
        choices=AIProviderConfig.PROVIDER_CHOICES,
        null=True,
        blank=True,
        verbose_name=_('AI Provider'),
        help_text=_('AI provider used for this message')
    )
    model = models.CharField(
        max_length=100,
        null=True,
        blank=True,
        verbose_name=_('Model'),
        help_text=_('AI model used for this message')
    )
    meta = models.JSONField(
        default=dict,
        blank=True,
        verbose_name=_('Metadata'),
        help_text=_('Additional metadata about the message')
    )
    
    class Meta:
        ordering = ['created_at']
        verbose_name = _('Message')
        verbose_name_plural = _('Messages')
        indexes = [
            models.Index(fields=['conversation', 'created_at']),
        ]
    
    def __str__(self):
        role_display = self.get_role_display()
        content_preview = self.content[:50] + '...' if len(self.content) > 50 else self.content
        return f"{role_display}: {content_preview}"
