import re
import uuid

from django.conf import settings
from django.db import models
from django.urls import reverse
from django.utils import timezone


def slugify_tag(value):
    """Normalize a user-entered tag name to its canonical form."""
    value = (value or '').strip().lower()
    value = re.sub(r'\s+', '_', value)
    value = re.sub(r'[^a-z0-9_.+-]', '', value)
    return value.strip('_')


class FileIndex(models.Model):
    """Tracks every user file so media stays organized and served only
    through the gated FileRequest endpoint (never direct /media/ access)."""

    uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
    owner = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='files',
    )
    kind = models.CharField(max_length=64, default='file', db_index=True)
    stored_path = models.CharField(max_length=1024)
    original_filename = models.CharField(max_length=255, blank=True, default='')
    content_type = models.CharField(max_length=128, blank=True, default='')
    size = models.IntegerField(default=0)
    md5 = models.CharField(max_length=32, db_index=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        indexes = [
            models.Index(fields=['owner', 'kind']),
        ]

    def __str__(self):
        return f'{self.kind} {self.uuid} ({self.owner})'

    @property
    def file_path(self):
        return self.stored_path


class TagCategory(models.Model):
    """Tag namespaces: Loader, Version, General, Creator, Content, Species, NoN Version."""
    slug = models.SlugField(max_length=64, unique=True)
    name = models.CharField(max_length=64)
    color = models.CharField(max_length=7, default='#666666')

    def __str__(self):
        return self.name


class Tag(models.Model):
    """A single tag within a category. Auto-created on first use (e621-style)."""
    name = models.CharField(max_length=64)
    category = models.ForeignKey(
        TagCategory,
        on_delete=models.CASCADE,
        related_name='tags',
    )
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='created_tags',
    )
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=['name', 'category'], name='uniq_tag_name_category'),
        ]

    def __str__(self):
        return f'{self.category.slug}:{self.name}'


class Project(models.Model):
    """A UGC page: versioned downloads (tracked) + gallery media (untracked)."""
    CATEGORY_CHOICES = [
        ('skin', 'Skin'),
        ('non_pack', 'NoN Pack'),
        ('model', 'Model'),
        ('guide', 'Guide'),
        ('mod', 'Mod'),
        ('modpack', 'Modpack'),
    ]

    slug = models.SlugField(max_length=128, unique=True)
    title = models.CharField(max_length=128)
    summary = models.CharField(max_length=255, blank=True, default='')
    description = models.TextField(blank=True, default='')
    category = models.CharField(max_length=32, choices=CATEGORY_CHOICES, default='mod')
    owner = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='projects',
    )
    thumbnail = models.OneToOneField(
        FileIndex,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='project_thumb',
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    rating_score = models.IntegerField(default=0)
    rating_count = models.PositiveIntegerField(default=0)

    def __str__(self):
        return self.title

    @property
    def thumbnail_url(self):
        if self.thumbnail_id is None:
            return None
        return reverse('library:file_request', args=[self.thumbnail.uuid])

    @property
    def downloads(self):
        return self.versions.aggregate(total=models.Sum('downloads'))['total'] or 0

    def can_edit(self, user):
        if not user.is_authenticated:
            return False
        if user.pk == self.owner_id:
            return True
        return self.contributors.filter(user=user).exists()

    def sync_creator_tags(self, actor=None):
        """Ensure Creator tags exist + are attached for the owner and contributors."""
        creator_cat = TagCategory.objects.filter(slug='creator').first()
        if creator_cat is None:
            return
        actor = actor or self.owner
        user_ids = {self.owner_id}
        user_ids.update(self.contributors.values_list('user_id', flat=True))
        for user_id in user_ids:
            user = self.owner.__class__.objects.filter(pk=user_id).first()
            if user is None:
                continue
            name = slugify_tag(user.username)
            if not name:
                continue
            tag, _ = Tag.objects.get_or_create(
                name=name, category=creator_cat, defaults={'created_by': user},
            )
            TagList.objects.get_or_create(project=self, tag=tag, defaults={'added_by': actor})

    def detach_creator_tag(self, user):
        creator_cat = TagCategory.objects.filter(slug='creator').first()
        if creator_cat is None:
            return
        TagList.objects.filter(
            project=self, tag__category=creator_cat, tag__name=slugify_tag(user.username),
        ).delete()


class ProjectContributor(models.Model):
    project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='contributors')
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='ugc_contributions',
    )
    added_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, related_name='+',
    )
    added_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=['project', 'user'], name='uniq_project_contributor'),
        ]

    def __str__(self):
        return f'{self.user.username} on {self.project.title}'


class TagList(models.Model):
    """Tag <-> Project link. Also records who applied the tag."""
    project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='tag_links')
    tag = models.ForeignKey(Tag, on_delete=models.CASCADE, related_name='project_links')
    added_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, related_name='+',
    )
    auto = models.BooleanField(default=False)
    added_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=['project', 'tag'], name='uniq_project_tag'),
        ]

    def __str__(self):
        return f'{self.project.title} #{self.tag}'


class Version(models.Model):
    """A release holding one or more VersionFile entries. Latest = newest."""
    project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='versions')
    version_name = models.CharField(max_length=64)
    changelog = models.TextField(blank=True, default='')
    downloads = models.PositiveIntegerField(default=0)
    pack_format = models.IntegerField(null=True, blank=True)
    pack_description = models.TextField(blank=True, default='')
    mods_manifest = models.JSONField(default=dict, blank=True)
    plugins_manifest = models.JSONField(default=dict, blank=True)
    skins_manifest = models.JSONField(default=dict, blank=True)
    mod_manifest = models.JSONField(default=dict, blank=True)
    animation_id = models.CharField(max_length=128, blank=True, default='', db_index=True)
    animation_manifest = models.JSONField(default=dict, blank=True)
    models_manifest = models.JSONField(default=dict, blank=True)
    logical_manifest = models.JSONField(default=dict, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['-created_at', '-pk']

    def __str__(self):
        return f'{self.project.title} {self.version_name}'


class VersionFile(models.Model):
    """One file belonging to a Version release. A version can hold several
    files (e.g. multiple skins), each with its own download."""
    KIND_CHOICES = [
        ('release', 'Release'),
        ('markdown', 'Markdown'),
        ('png', 'PNG'),
    ]
    version = models.ForeignKey(Version, on_delete=models.CASCADE, related_name='files')
    file = models.ForeignKey(FileIndex, on_delete=models.CASCADE, related_name='version_file')
    kind = models.CharField(max_length=16, choices=KIND_CHOICES, default='release')
    note = models.CharField(max_length=255, blank=True, default='')
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['created_at', 'pk']

    def __str__(self):
        return f'{self.version} — {self.file.original_filename}'

    @property
    def filename(self):
        return self.file.original_filename

    @property
    def is_markdown(self):
        return self.kind == 'markdown'

    @property
    def is_png(self):
        return self.kind == 'png'


class ProjectAsset(models.Model):
    """Untracked gallery media (image/video/gif) displayed on the project page."""
    project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='assets')
    file = models.ForeignKey(FileIndex, on_delete=models.CASCADE, related_name='project_asset')
    caption = models.CharField(max_length=128, blank=True, default='')
    uploaded_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, related_name='+',
    )
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['-created_at', '-pk']

    def __str__(self):
        return f'{self.project.title} asset'

    @property
    def file_url(self):
        return reverse('library:file_request', args=[self.file.uuid])

    @property
    def is_video(self):
        return self.file.content_type.startswith('video/')

    @property
    def is_gif(self):
        return self.file.content_type == 'image/gif'


class TempUpload(models.Model):
    """J621-style draft upload. Stored under media/uploads/user_<id>/<uuid><ext>
    and tracked here until the project/gallery accepts it (status 'used') or it
    is discarded. Previews are served through the gated file_request endpoint."""

    KIND_CHOICES = [
        ('thumbnail', 'Thumbnail'),
        ('version', 'Version file'),
        ('media', 'Gallery media'),
    ]
    STATUS_CHOICES = [
        ('pending', 'Pending'),
        ('used', 'Used'),
        ('discarded', 'Discarded'),
    ]

    uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='temp_uploads',
    )
    kind = models.CharField(max_length=16, choices=KIND_CHOICES, db_index=True)
    file = models.ForeignKey(FileIndex, on_delete=models.CASCADE, related_name='temp_upload')
    status = models.CharField(max_length=16, choices=STATUS_CHOICES, default='pending', db_index=True)
    draft_uuid = models.UUIDField(null=True, blank=True, db_index=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        indexes = [
            models.Index(fields=['user', 'status', 'kind']),
        ]

    def __str__(self):
        return f'{self.kind} {self.uuid} ({self.status})'

    @property
    def url(self):
        return reverse('library:file_request', args=[self.file.uuid])

    @property
    def is_video(self):
        return self.file.content_type.startswith('video/')


class ProjectDraft(models.Model):
    """Autosaved 'new project' form state. Many per user, each keyed by a UUID;
    there is no explicit Save Draft button — every keystroke is persisted."""
    uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='project_drafts',
    )
    data = models.JSONField(default=dict, blank=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['-updated_at', '-pk']

    def __str__(self):
        return f'Draft {self.uuid} for {self.user.username}'

    @property
    def title(self):
        return self.data.get('title') or 'Untitled draft'


class Rating(models.Model):
    """One user's up/down vote on a project (Steam-style overall rating).

    A user may hold a single vote per project; changing it edits this row.
    ``rating_score``/``rating_count`` on Project mirror the aggregate."""
    project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='ratings')
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='ratings',
    )
    value = models.BooleanField(default=True)  # True=positive/up, False=negative/down
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=['project', 'user'], name='uniq_rating_project_user'),
        ]

    def __str__(self):
        return f'{"+" if self.value else "-"}{self.user.username} on {self.project.slug}'


class Comment(models.Model):
    """A forum-style comment on a project. Unlimited; body is Markdown."""
    project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='comments')
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='comments',
    )
    body = models.TextField()
    created_at = models.DateTimeField(default=timezone.now)
    updated_at = models.DateTimeField(default=timezone.now)

    def save(self, *args, **kwargs):
        if self._state.adding:
            now = timezone.now()
            self.created_at = now
            self.updated_at = now
        else:
            self.updated_at = timezone.now()
        super().save(*args, **kwargs)

    def __str__(self):
        return f'{self.user.username} on {self.project.slug}: {self.body[:40]}'
