from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
from ckeditor.fields import RichTextField
import uuid
from django.utils.text import slugify
from django.utils.timezone import now
import os
from django.db import transaction

from django.db import models
from django.contrib.auth import get_user_model
from django.utils import timezone
import json

from .utils import translate_to_english


class BaseModel(models.Model):
    """
    Abstract base model that contains common fields used across multiple models.
    
    Fields:
        guid: Unique identifier for the record
        created_at: Timestamp when the record was created
        ldm: Last date modified timestamp
        user: User ID who created/modified the record
        rskey: Record status key
        slug: URL-friendly version of the title (for models with titles)
    """
    guid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
    created_at = models.DateTimeField(auto_now_add=True, editable=False)
    ldm = models.DateTimeField(auto_now=True, editable=False)
    user = models.IntegerField(default=0, help_text=_('User ID'), editable=False, blank=True)
    rskey = models.IntegerField(default=0, help_text=_('Record Status Key'), editable=False)


    class Meta:
        abstract = True

    def clean(self):
        """Validate the model data before saving"""
        super().clean()

    def delete(self, *args, **kwargs):
        """Override delete to handle image deletion"""
        super().delete(*args, **kwargs)

    
    def srb(self, *args, **kwargs):
        """
        Soft record blocking - sets rskey to 1 and saves the record
        """
        self.rskey = 1
        self.save()

    def save(self, *args, **kwargs):
        """
        Override save method to perform validation before saving
        """
        try:
            self.full_clean()
            super().save(*args, **kwargs)
        except ValidationError as e:
            raise

    def assign_user_to_model(instance, user_id=None):
        """
        Helper function to assign a user ID to a model instance that inherits from BaseModel.
        If no user_id is provided, defaults to 0.
        
        Args:
            instance: An instance of a model that inherits from BaseModel
            user_id: (Optional) The user ID to assign to the instance
            
        Returns:
            The instance with the user field updated (not saved)
            
        Raises:
            ValueError: If the instance is not a subclass of BaseModel or if user_id is invalid
        """
        if not isinstance(instance, BaseModel):
            raise ValueError("Instance must be a subclass of BaseModel")
        
        if user_id is None:
            instance.user = 0
        elif isinstance(user_id, int) and user_id >= 0:
            instance.user = user_id
        else:
            raise ValueError("user_id must be a non-negative integer or None")
        
        return instance


class BaseWithSeo(BaseModel):

    meta_image = models.ImageField(null=True, blank=True, upload_to='meta_images/', help_text=_('meta_image'))
    meta_title = models.CharField(max_length=255, null=True, blank=True, help_text=_('meta_title'))
    meta_description = models.CharField(max_length=999, null=True, blank=True, help_text=_('meta_description'))
    meta_keywords = models.CharField(max_length=999, null=True, blank=True, help_text=_('meta_keywords'))

    class Meta:
        abstract = True         


    def clean(self):
        """Validate the model data before saving"""
        super().clean()
    
    def save(self, *args, **kwargs):
        if self.pk:
            try:
                old_instance = self.__class__.objects.get(pk=self.pk)
                if old_instance.meta_image and old_instance.meta_image != self.meta_image:
                    if os.path.isfile(old_instance.meta_image.path):
                        os.remove(old_instance.meta_image.path)
            except self.__class__.DoesNotExist:
                pass
        super().save(*args, **kwargs)
        
    def delete(self, *args, **kwargs):
        """Override delete to handle image deletion"""
        if self.meta_image:        
            if os.path.isfile(self.meta_image.path):    
                os.remove(self.meta_image.path)
        super().delete(*args, **kwargs)

  
class SluggedBaseModel(BaseModel):
    """
    Abstract base model that extends BaseModel and adds slug functionality
    for models that need a URL-friendly version of their title.
    """
    title = models.CharField(max_length=255, null=False, blank=False, help_text=_('title'))
    slug = models.SlugField(max_length=255, unique=True, editable=False, help_text=_('URL-friendly version of the title'))

    class Meta:
        abstract = True

    def generate_slug(self):
        """
        Generate a slug from the title if slug is empty.
        If the slug already exists, append a counter to make it unique.
        """
        if not self.slug:
            base_slug = slugify(self.title)
            self.slug = base_slug
            counter = 1
            
            # Get the model class dynamically
            model_class = self.__class__
            
            # Check if slug exists, if so generate a new one with counter
            while model_class.objects.filter(slug=self.slug).exclude(pk=self.pk).exists():
                self.slug = f"{base_slug}-{counter}"
                counter += 1

    def save(self, *args, **kwargs):
        """
        Override save method to generate slug before saving
        """
        self.generate_slug()
        super().save(*args, **kwargs)


class Author(BaseModel):
    """
    Model representing blog authors with verification status
    """
    class AuthorStatus(models.IntegerChoices):
        USER_SUBMITTED = 1, _('User Submitted')
        UNVERIFIED = 2, _('Unverified')
        VERIFIED = 3, _('Verified')

    status = models.IntegerField(choices=AuthorStatus.choices, default=AuthorStatus.UNVERIFIED)
    name = models.CharField(max_length=255, null=False, blank=False, help_text=_('name'))
    image = models.ImageField(null=False, upload_to='author_images/', blank=False, help_text=_('image'))
    description = models.CharField(max_length=255, null=False, blank=False, help_text=_('description'))
    affilation = models.CharField(max_length=255, null=False, blank=False, help_text=_('affilation'))
    phone = models.CharField(null=False, blank=False, unique=True, help_text=_('phone'))
    email = models.EmailField(null=False, blank=False, unique=True, help_text=_('email'))

    def __str__(self):
        return self.name


    def save(self, *args, **kwargs):
        """Override save to handle old image deletion when updating"""
        try:
            # Get the existing instance from database
            old_instance = self.__class__.objects.get(pk=self.pk)
            if old_instance.image and old_instance.image != self.image:
                # Delete the old image file if it exists
                if os.path.isfile(old_instance.image.path):
                    os.remove(old_instance.image.path)
        except self.__class__.DoesNotExist:
            pass  # New instance, no old image to delete
        
        super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """Override delete to handle image deletion"""
        if self.image:
            if os.path.isfile(self.image.path):
                os.remove(self.image.path)
        super().delete(*args, **kwargs)


class BlogCategory(SluggedBaseModel):
    """
    Model representing blog categories with auto-generated slugs
    """
    def __str__(self):
        return self.title


class Topics(SluggedBaseModel):
    """
    Model representing individual topics within topic categories with auto-generated slugs
    """
    
    def __str__(self):
        return self.title


class Blog(BaseWithSeo):
    """
    Main blog post model with content, categorization, and status tracking
    """
    class Status(models.IntegerChoices):
        PUBLISHED = 1, _('Publish Now')
        DRAFT = 2, _('Save Draft')
        USER_SUBMITTED = 3, _('User Submitted')
        ARCHIVED = 4, _('Archived')

    status = models.IntegerField(choices=Status.choices, default=Status.DRAFT)
    title = models.CharField(max_length=255, null=False, blank=False)
    content = RichTextField()
    category = models.ForeignKey('BlogCategory', on_delete=models.SET_NULL, null=True, blank=True, related_name='blogs')
    # Changed to ManyToManyField to allow multiple authors
    authors = models.ManyToManyField('Author', related_name='blogs')

    topic = models.ForeignKey('Topics', on_delete=models.SET_NULL, null=True, blank=True, related_name='blogs')
    feature_this = models.BooleanField(default=False)
    slug = models.SlugField(max_length=255, unique=True, editable=False, help_text=_('URL-friendly version of the title'))

    # New: Added published_time field
    published_time = models.DateTimeField(null=True, blank=True)
    
    # New: Added related_topics field as ManyToManyField
    related_topics = models.ManyToManyField('Topics', related_name='related_topics', blank=True)




    def __str__(self):
        return self.title

    def clean(self):
        """Validate blog data before saving"""
        if self.feature_this:
            featured_count = Blog.objects.filter(feature_this=True).exclude(pk=self.pk).count()
            if featured_count >= 4:
                raise ValidationError("Only four blogs can be featured at a time.")
        super().clean()

    def generate_slug(self):
        """Generate a unique slug from the (translated) title"""
        title_for_slug = translate_to_english(self.title)
        base_slug = slugify(title_for_slug)
        slug = base_slug
        counter = 1

        while Blog.objects.filter(slug=slug).exclude(pk=self.pk).exists():
            slug = f"{base_slug}-{counter}"
            counter += 1

        return slug

    def save(self, *args, **kwargs):
        """Override save to update slug when title changes"""
        if self.pk:
            # Check if title has changed
            old_title = Blog.objects.filter(pk=self.pk).values_list('title', flat=True).first()
            if old_title and old_title != self.title:
                self.slug = self.generate_slug()
        else:
            # New instance
            self.slug = self.generate_slug()

        super().save(*args, **kwargs)


class Poll(BaseModel):
    """
    Model for polls with a question
    """
    question = models.CharField(max_length=255)
    created_at = models.DateTimeField(auto_now_add=True, editable=False)

    def __str__(self):
        return self.question


class Option(BaseModel):
    """
    Model for poll options with vote tracking
    """
    poll = models.ForeignKey(Poll, related_name='options', on_delete=models.CASCADE)
    option_text = models.CharField(max_length=255)
    votes = models.IntegerField(default=0)

    @property
    def percentage(self):
        """Calculate the percentage of votes for this option"""
        total_votes = self.poll.options.aggregate(models.Sum('votes'))['votes__sum'] or 0
        return (self.votes / total_votes) * 100 if total_votes > 0 else 0

    def __str__(self):
        return self.option_text


class Vote(BaseModel):
    """
    Model to track individual votes on polls
    """
    poll = models.ForeignKey(Poll, on_delete=models.CASCADE)
    ip_address = models.GenericIPAddressField()
    voted_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = ('poll', 'ip_address')

    def __str__(self):
        return f"{self.ip_address} voted on {self.poll.question}"


class Subscriber(BaseModel):
    """
    Model for newsletter subscribers
    """
    name = models.CharField(max_length=100)
    email = models.EmailField(unique=True)
    is_active = models.BooleanField(default=True)

    def __str__(self):
        return f"{self.name} ({self.email})"


class TeamMember(BaseModel):
    """
    Model for team members with social media links
    """
    name = models.CharField(max_length=100)
    role = models.CharField(max_length=100)
    image = models.ImageField(upload_to='team_images/')
    facebook_link = models.URLField(blank=True, null=True)
    insta_link = models.URLField(blank=True, null=True)
    twitter_link = models.URLField(blank=True, null=True)
    email = models.EmailField(blank=True, null=True, unique=True)

    def __str__(self):
        return self.name
    
    def save(self, *args, **kwargs):
        """Override save to handle old image deletion when updating"""
        try:
            # Get the existing instance from database
            old_instance = self.__class__.objects.get(pk=self.pk)
            if old_instance.image and old_instance.image != self.image:
                # Delete the old image file if it exists
                if os.path.isfile(old_instance.image.path):
                    os.remove(old_instance.image.path)
        except self.__class__.DoesNotExist:
            pass  # New instance, no old image to delete
        super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        """Override delete to handle image deletion"""
        if self.image:
            if os.path.isfile(self.image.path):
                os.remove(self.image.path)
        super().delete(*args, **kwargs)


class Contact(BaseModel):
    """
    Model for contact form submissions
    """
    name = models.CharField(max_length=255, null=False, blank=False, help_text=_('name'))
    mail = models.EmailField(null=False, blank=False, unique=False, help_text=_('mail'))
    phone = models.CharField(null=False, blank=False, unique=False, help_text=_('phone'))
    message = models.CharField(max_length=1000, null=False, blank=False, help_text=_('message'))

    def __str__(self):
        return self.name


class BlogView(BaseModel):
    """
    Track individual page views for blogs
    """
    
    
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name='views')
    ip_address = models.GenericIPAddressField()
    user_agent = models.CharField(max_length=255, blank=True)
    session_id = models.CharField(max_length=100, blank=True)  # To identify unique sessions
    
    class Meta:
        indexes = [
            models.Index(fields=['blog', 'created_at']),
            models.Index(fields=['ip_address', 'session_id']),
        ]
    
    def __str__(self):
        return f"{self.blog.title} - {self.ip_address}"


class BlogAnalytics(BaseModel):
    """
    Aggregated daily analytics for blogs
    """
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name='analytics')
    date = models.DateField()
    views = models.IntegerField(default=0)
    unique_visitors = models.IntegerField(default=0)
    avg_reading_time = models.IntegerField(default=0)  # in seconds
    engagement_score = models.FloatField(default=0.0)  # Based on comments, shares, etc.
    
    class Meta:
        unique_together = ('blog', 'date')
        ordering = ['-date']
    
    def __str__(self):
        
        return f"{self.blog.title} - {self.date}"


class AuthorAnalytics(BaseModel):
    """
    Aggregated analytics for authors
    """
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='analytics')
    date = models.DateField()
    total_views = models.IntegerField(default=0)
    total_blogs = models.IntegerField(default=0)
    engagement_rate = models.FloatField(default=0.0)  # Percentage
    
    class Meta:
        unique_together = ('author', 'date')
        ordering = ['-date']
    
    def __str__(self):
        return f"{self.author.name} - {self.date}"



class PageView(BaseModel):
    """
    Tracks individual page views
    """
    date = models.DateField()
    page_name = models.CharField(max_length=1000, blank=True, null=True, default='')
    views = models.IntegerField(default=1)
    ip_address = models.CharField(max_length=45, blank=True, null=True)
    session_key = models.CharField(max_length=40, blank=True, null=True)
    
    class Meta:
        ordering = ['-date']
        unique_together = ['date', 'page_name', 'ip_address', 'session_key']
    
    def __str__(self):
        return f"{self.page_name} - {self.date} - {self.views} views"


class SiteAnalytics(BaseModel):
    """
    Overall site analytics (aggregated data)
    """
    date = models.DateField(unique=True)
    total_views = models.IntegerField(default=0)
    unique_visitors = models.IntegerField(default=0)
    
    class Meta:
        ordering = ['-date']
    
    def __str__(self):
        return f"Site Analytics - {self.date}"


User = get_user_model()

class Task(BaseModel):
    """
    Tasks for admin dashboard
    """
    PRIORITY_CHOICES = [
        ('high', 'High'),
        ('medium', 'Medium'),
        ('low', 'Low'),
    ]
    
    STATUS_CHOICES = [
        ('pending', 'Pending'),
        ('in_progress', 'In Progress'),
        ('completed', 'Completed'),
        ('cancelled', 'Cancelled'),
    ]
    
    title = models.CharField(max_length=255)
    description = models.TextField(blank=True)
    due_date = models.DateField()
    priority = models.CharField(max_length=20, choices=PRIORITY_CHOICES, default='medium')
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
    assigned_to = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, help_text=_('Assigned To User ID'))
    completed_at = models.DateTimeField(null=True, blank=True)
    response_from_team = models.TextField(max_length=3000, null=True, blank=True)
    
    class Meta:
        ordering = ['due_date', '-priority']
    
    def __str__(self):
        return f"{self.title} - {self.get_status_display()}"
    
    @property
    def is_overdue(self):
        """Check if task is past due date and not completed"""
        return self.due_date < now().date() and self.status != 'completed'
    
    @property
    def priority_class(self):
        """Return CSS classes for priority styling"""
        classes = {
            'high': 'bg-red-50 text-red-700',
            'medium': 'bg-yellow-50 text-yellow-700',
            'low': 'bg-green-50 text-green-700',
        }
        return classes.get(self.priority, 'bg-gray-50 text-gray-700')
    

class NewsletterTask(models.Model):

    STATUS_CHOICES = [
        ('pending', 'Pending'),
        ('in_progress', 'In Progress'),
        ('completed', 'Completed'),
        ('failed', 'Failed'),
    ]

    subject = models.CharField(max_length=200)
    message = models.TextField()
    total_subscribers = models.PositiveIntegerField(default=0)
    sent_subscribers = models.PositiveIntegerField(default=0)
    successful_sends = models.PositiveIntegerField(default=0)
    failed_sends = models.PositiveIntegerField(default=0)
    errors = models.TextField(blank=True, null=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    completed_at = models.DateTimeField(null=True, blank=True)
    is_completed = models.BooleanField(default=False)

    class Meta:
        ordering = ['-created_at']
        verbose_name = 'Newsletter Task'
        verbose_name_plural = 'Newsletter Tasks'

    def __str__(self):
        return f"Newsletter: {self.subject} ({self.get_status_display()})"

    def get_progress_percentage(self):
        if self.total_subscribers == 0:
            return 0
        return int((self.sent_subscribers / self.total_subscribers) * 100)

    def get_errors_list(self):
        if not self.errors:
            return []
        try:
            return json.loads(self.errors)
        except json.JSONDecodeError:
            return [self.errors]

    def add_error(self, error_message):
        errors = self.get_errors_list()
        errors.append(error_message)
        self.errors = json.dumps(errors[-100:])  # Keep only last 100 errors
        self.save()

    def mark_completed(self):
        self.status = 'completed'
        self.is_completed = True
        self.completed_at = timezone.now()
        self.save()

    def mark_failed(self):
        self.status = 'failed'
        self.is_completed = True
        self.completed_at = timezone.now()
        self.save()

    def update_progress(self, successful_count=None, failed_count=None):
        if successful_count is not None:
            self.successful_sends = successful_count
        if failed_count is not None:
            self.failed_sends = failed_count
        
        self.sent_subscribers = self.successful_sends + self.failed_sends
        
        if self.sent_subscribers > 0 and self.status == 'pending':
            self.status = 'in_progress'
        
        self.save()

    def get_summary(self):
        return {
            'subject': self.subject,
            'total': self.total_subscribers,
            'sent': self.sent_subscribers,
            'successful': self.successful_sends,
            'failed': self.failed_sends,
            'progress': self.get_progress_percentage(),
            'status': self.get_status_display(),
            'created_at': self.created_at.strftime('%Y-%m-%d %H:%M'),
            'completed_at': self.completed_at.strftime('%Y-%m-%d %H:%M') if self.completed_at else None,
            'errors': self.get_errors_list()[:20]  # Return first 20 errors for display
        }
    

class Issue(BaseWithSeo):
    """
    Issue model to organize multiple blogs together as a collection
    
    Fields:
        name: Name of the issue
        issue_number: Unique number for the issue
        description: Optional description of the issue
        publication_date: Date when the issue was/will be published
        cover_image: Cover image for the issue
        is_published: Flag to indicate if the issue is published
        blogs: Many-to-many relationship with Blog model via IssueItem
        slug: URL-friendly version of the name
    """
    class Status(models.IntegerChoices):
        PUBLISHED = 1, _('Published')
        DRAFT = 2, _('Draft')
        SCHEDULED = 3, _('Scheduled')
        ARCHIVED = 4, _('Archived')
    
    name = models.CharField(max_length=255, help_text=_('Name of the issue'))
    issue_number = models.CharField(max_length=50, unique=True, help_text=_('Unique number for the issue (e.g., "Vol. 1, Issue 2")'))
    description = models.TextField(blank=True, null=True, help_text=_('Optional description of the issue'))
    publication_date = models.DateField(blank=True, null=True, help_text=_('Date when the issue was/will be published'))
    cover_image = models.ImageField(upload_to='issues/covers/', blank=True, null=True, help_text=_('Cover image for the issue'))
    status = models.IntegerField(choices=Status.choices, default=Status.DRAFT)
    blogs = models.ManyToManyField(Blog, through='IssueItem', related_name='issues')
    slug = models.SlugField(max_length=255, unique=True, editable=False, help_text=_('URL-friendly version of the name'))

    def __str__(self):
        return f"{self.name} ({self.issue_number})"

    def clean(self):
        """Validate issue data before saving"""
        super().clean()

    def generate_slug(self):
        """Generate a unique slug from the name and issue number"""
        base_slug = slugify(f"{self.name}-{self.issue_number}")
        slug = base_slug
        counter = 1

        while Issue.objects.filter(slug=slug).exclude(pk=self.pk).exists():
            slug = f"{base_slug}-{counter}"
            counter += 1

        return slug

    def save(self, *args, **kwargs):
        """Override save to update slug when name or issue_number changes"""
        if not self.slug or self.pk:
            # Check if name or issue_number has changed
            old_issue = None
            if self.pk:
                old_issue = Issue.objects.filter(pk=self.pk).first()
            
            if not old_issue or old_issue.name != self.name or old_issue.issue_number != self.issue_number:
                self.slug = self.generate_slug()
        else:
            # New instance
            self.slug = self.generate_slug()

        super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
                """Override delete to handle image deletion"""
                if self.cover_image:
                    if os.path.isfile(self.cover_image.path):
                        os.remove(self.cover_image.path)
                super().delete(*args, **kwargs)


class IssueItem(BaseModel):
    """
    Through model for the many-to-many relationship between Issue and Blog
    
    Fields:
        issue: ForeignKey to Issue model
        blog: ForeignKey to Blog model
        order: Integer to determine the order of blogs in the issue
        featured: Flag to indicate if the blog is featured in the issue
        notes: Optional notes about the blog in this issue
    """
    issue = models.ForeignKey(Issue, on_delete=models.CASCADE, related_name='issue_items')
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name='issue_items')
    order = models.PositiveIntegerField(default=0, help_text=_('Order of the blog in the issue'))
    featured = models.BooleanField(default=False, help_text=_('Flag to indicate if the blog is featured in this issue'))
    notes = models.TextField(blank=True, null=True, help_text=_('Optional notes about the blog in this issue'))

    class Meta:
        ordering = ['order']
        unique_together = ['issue', 'blog']
        verbose_name = _('Issue Item')
        verbose_name_plural = _('Issue Items')

    def __str__(self):
        return f"{self.issue} - {self.blog} (Order: {self.order})"
    
    # This would go in your IssueItem model
    def save(self, *args, **kwargs):
        # If this is a new item or order is being changed
        if not self.pk or 'order' in kwargs.get('update_fields', []):
            # Temporarily set to None to avoid conflict
            with transaction.atomic():
                IssueItem.objects.filter(
                    issue=self.issue,  # assuming issue is the FK to Issue
                    order=self.order
                ).exclude(pk=self.pk if self.pk else None).update(order=None)

        super().save(*args, **kwargs)

    def clean(self):
        """Validate issue item data before saving"""
        if self.featured:
            featured_count = IssueItem.objects.filter(
                issue=self.issue, featured=True
            ).exclude(pk=self.pk).count()
            if featured_count >= 5:  # Limit of 5 featured blogs per issue
                raise ValidationError("Only five blogs can be featured in an issue at a time.")
        super().clean()
        