import hashlib
import os
import re
import secrets
import uuid

from django.conf import settings
from django.contrib.postgres.fields import ArrayField
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _

# Create your models here.


def validate_ticket_image_size(image):
    """Validate that ticket screenshot / attachment does not exceed 5 MB."""
    max_size = 5 * 1024 * 1024  # 5 MB
    if image and hasattr(image, "size") and image.size > max_size:
        raise ValidationError(_("Image file size must not exceed 5 MB."))


def _slugify_name(name):
    return re.sub(r'[^a-z0-9]+', '-', name.lower()).strip('-') or 'template'


def _safe_identifier(value):
    value = (value or '').strip()
    return value or uuid.uuid4().hex[:8]


def idcard_front_path(instance, filename):
    ext = filename.split('.')[-1].lower()
    school_id = _safe_identifier(getattr(getattr(instance, 'person', None), 'school_id', None))
    return f"id_cards/{school_id}_idcard_front.{ext}"


def idcard_back_path(instance, filename):
    ext = filename.split('.')[-1].lower()
    school_id = _safe_identifier(getattr(getattr(instance, 'person', None), 'school_id', None))
    return f"id_cards/{school_id}_idcard_back.{ext}"


def id_template_front_path(instance, filename):
    ext = filename.split('.')[-1].lower()
    slug = _slugify_name(instance.name)
    return f"id_templates/{slug}_template_front.{ext}"


def id_template_back_path(instance, filename):
    ext = filename.split('.')[-1].lower()
    slug = _slugify_name(instance.name)
    return f"id_templates/{slug}_template_back.{ext}"


def person_id_card_front_path(instance, filename):
    ext = filename.split('.')[-1].lower()
    school_id = _safe_identifier(getattr(instance, 'school_id', None))
    return f"persons/{school_id}_id_front.{ext}"


def person_id_card_back_path(instance, filename):
    ext = filename.split('.')[-1].lower()
    school_id = _safe_identifier(getattr(instance, 'school_id', None))
    return f"persons/{school_id}_id_back.{ext}"


def person_profile_path(instance, filename):
    ext = filename.split('.')[-1].lower()
    school_id = _safe_identifier(getattr(instance, 'school_id', None))
    return f"persons/{school_id}_profile.{ext}"


def person_signature_path(instance, filename):
    ext = filename.split('.')[-1].lower()
    school_id = _safe_identifier(getattr(instance, 'school_id', None))
    return f"persons/{school_id}_signature.{ext}"

class Department(models.Model):
    name = models.CharField(max_length=100)

    class Meta:
        verbose_name = "Department"
        verbose_name_plural = "Departments"
        ordering = ["name"]

    def __str__(self):
        return self.name



class Program(models.Model):
    code = models.CharField(max_length=100, unique=True)
    title = models.CharField(max_length=200, blank=True, null=True)
    department = models.ForeignKey(Department, on_delete=models.CASCADE, related_name='academic_programs')

    class Meta:
        verbose_name = 'Program'
        verbose_name_plural = 'Programs'
        ordering = ['code']

    def __str__(self):
        if self.title:
            return f"{self.code} - {self.title}"
        return self.code


class Person(models.Model):
    PERSON_TYPE = (
        ('student', 'Student'),
        ('employee', 'Employee'),
    )

    SEX_CHOICES = (
        ('Male', 'Male'),
        ('Female', 'Female'),
    )

    type = models.CharField(max_length=10, choices=PERSON_TYPE)
    school_id = models.CharField(max_length=50, blank=True, null=True, unique=True)
    card_number = models.CharField(max_length=50, unique=True)
    firstname = models.CharField(max_length=150, blank=True, null=True)
    middlename = models.CharField(max_length=150, blank=True, null=True)
    lastname = models.CharField(max_length=150, blank=True, null=True)
    sex = models.CharField(max_length=10, choices=SEX_CHOICES, blank=True, null=True)
    department = models.ForeignKey(Department, on_delete=models.SET_DEFAULT, default=1)
    program = models.ForeignKey(Program, on_delete=models.SET_NULL, blank=True, null=True, related_name='persons')
    birthdate = models.DateField(blank=True, null=True)
    mobile_number = models.CharField(max_length=20, blank=True, null=True)
    position = models.CharField(max_length=100, blank=True, null=True)
    image = models.ImageField(upload_to=person_profile_path, blank=True, null=True)
    signature = models.ImageField(upload_to=person_signature_path, blank=True, null=True)
    id_image = models.ImageField(upload_to=person_id_card_front_path, blank=True, null=True)
    id_image_back = models.ImageField(upload_to=person_id_card_back_path, blank=True, null=True)
    active_card = models.ForeignKey('IDCard', on_delete=models.SET_NULL, blank=True, null=True, related_name='active_person')
    is_archived = models.BooleanField(default=False)
    
    # Emergency & ID Registration Fields
    emergency_name = models.CharField(max_length=150, blank=True, null=True)
    emergency_number = models.CharField(max_length=20, blank=True, null=True)
    emergency_address = models.TextField(blank=True, null=True)
    address = models.TextField(blank=True, null=True)
    tin_no = models.CharField(max_length=30, blank=True, null=True)
    sss_no = models.CharField(max_length=30, blank=True, null=True)
    philhealth_no = models.CharField(max_length=30, blank=True, null=True)
    pagibig_no = models.CharField(max_length=30, blank=True, null=True)

    def _compose_structured_name(self):
        first = (self.firstname or "").strip()
        middle = (self.middlename or "").strip()
        last = (self.lastname or "").strip()
        middle_initial = f"{middle[0].upper()}." if middle else ""
        return " ".join(part for part in [first, middle_initial, last] if part).strip()

    def get_display_name(self):
        composed = self._compose_structured_name()
        if composed:
            return composed

        return (self.school_id or self.card_number or "Unknown").strip()

    @property
    def display_name(self):
        return self.get_display_name()

    class Meta:
        permissions = [
            ("import_persons", "Can import persons"),
            ("scan_persons", "Can scan persons"),  # Note: Used only to grant access to the scan person page.
            ("person_id_registration", "Can register person ID"),
            ("migrate_card_numbers", "Can migrate card numbers"),
            ("view_person_sensitive_media", "Can view person sensitive media"),
            ("view_person_government_ids", "Can view person government IDs"),
        ]

    def sync_active_id_card(self, template_id=None, save_person=True):
        """
        Synchronizes active IDCard for the Person and updates person.active_card.
        """
        from core.models import IDCard

        if not self.pk and save_person:
            self.save()

        card_num = (self.card_number or "").strip()

        if not card_num:
            active_cards = IDCard.objects.filter(person=self, status="active")
            for card in active_cards:
                card.status = "expired"
                card.save(update_fields=["status"])

            old_active = self.active_card
            self.active_card = None
            if save_person and (old_active is not None or self.pk):
                self.save(update_fields=["active_card"])
            return None

        # Expire active cards for this person with a different card_number
        other_active_cards = IDCard.objects.filter(person=self, status="active").exclude(card_number=card_num)
        for card in other_active_cards:
            card.status = "expired"
            card.save(update_fields=["status"])

        card = (
            IDCard.objects.filter(person=self, card_number=card_num)
            .order_by("-created_at")
            .first()
        )

        if card is None:
            card = IDCard(person=self, card_number=card_num, status="active")
        else:
            if card.status != "active":
                card.status = "active"

        if template_id:
            card.template_id = template_id

        id_image_name = self.id_image.name if getattr(self, "id_image", None) and self.id_image else None
        id_image_back_name = self.id_image_back.name if getattr(self, "id_image_back", None) and self.id_image_back else None
        if id_image_name:
            card.id_image = id_image_name
        if id_image_back_name:
            card.id_image_back = id_image_back_name

        card.save()

        if self.active_card_id != card.id:
            self.active_card = card
            if save_person:
                self.save(update_fields=["active_card"])

        return card

    def save(self, *args, **kwargs):
        # Keep legacy `name` untouched during deprecation; use display_name for rendering.
        super().save(*args, **kwargs)

    def __str__(self):
        return f"{self.display_name} ({self.type})"



class Area(models.Model):
    name = models.CharField(max_length=100)
    notes = models.TextField(blank=True, null=True)

    def __str__(self):
        return self.name


class Device(models.Model):
    device_id = models.CharField(max_length=100, primary_key=True)
    area = models.ForeignKey(Area, on_delete=models.SET_NULL, blank=True, null=True)

    class Meta:
        permissions = [
            ("update_device_id", "Can update device ID"),
            ("update_server_url", "Can update server URL"),
            ("restart_service", "Can restart device service"),
            ("pull_updates", "Can pull updates on device"),
            ("reboot_node", "Can reboot device node"),
        ]

    def __str__(self):
        return f"{self.device_id} ({self.area})"
    

class Record(models.Model):
    timestamp = models.DateTimeField(auto_now_add=True)
    card_number = models.CharField(max_length=50)
    device = models.ForeignKey(Device, on_delete=models.SET_NULL, null=True, blank=True)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='recorded_records')
    class_obj = models.ForeignKey('Class', on_delete=models.SET_NULL, null=True, blank=True, related_name='records')
    event = models.ForeignKey('Event', on_delete=models.SET_NULL, null=True, blank=True, related_name='records')
    area = models.ForeignKey(Area, on_delete=models.SET_NULL, null=True, blank=True)

    class Meta:
        permissions = [
            ("can_present_live", "Can present live"),
        ]

    def __str__(self):
        try:
            from core.card_resolution import resolve_person_by_card_number
            person = resolve_person_by_card_number(self.card_number)
            return f"{person.display_name} ({self.card_number}) @ {self.timestamp}"
        except Exception:
            return f"Unknown ({self.card_number}) @ {self.timestamp}"


class DeviceLog(models.Model):
    timestamp = models.DateTimeField(auto_now_add=True)
    device = models.ForeignKey(Device, on_delete=models.CASCADE)
    device_ip = models.GenericIPAddressField()
    script_version = models.CharField(max_length=50)
    
    class Meta:
        ordering = ['-timestamp']
    
    def __str__(self):
        return f"{self.device.device_id} - {self.device_ip} @ {self.timestamp}"


class UserLoginEvent(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='login_events')
    timestamp = models.DateTimeField(auto_now_add=True)
    ip_address = models.GenericIPAddressField(null=True, blank=True)
    user_agent = models.TextField(blank=True, null=True)
    source = models.CharField(max_length=50, default='admin', help_text="Login source (e.g., admin, api)")

    class Meta:
        ordering = ['-timestamp']
        indexes = [
            models.Index(fields=['-timestamp']),
            models.Index(fields=['user', '-timestamp']),
        ]
        verbose_name = 'User Login Event'
        verbose_name_plural = 'User Login Events'

    def __str__(self):
        return f"{self.user.username} @ {self.timestamp}"


class UserActivity(models.Model):
    """Event log of all user page visits (keeps full history)."""
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='activity_events')
    timestamp = models.DateTimeField(auto_now_add=True)
    path = models.CharField(max_length=255, blank=True, null=True)
    ip_address = models.GenericIPAddressField(null=True, blank=True)
    user_agent = models.TextField(blank=True, null=True)

    class Meta:
        ordering = ['-timestamp']
        indexes = [
            models.Index(fields=['-timestamp']),
            models.Index(fields=['user', '-timestamp']),
        ]
        verbose_name = 'User Activity'
        verbose_name_plural = 'User Activities'

    def __str__(self):
        return f"{self.user.username} @ {self.timestamp}"


class Semester(models.Model):
    title = models.CharField(max_length=200, default='Untitled Semester')
    start_date = models.DateField()
    end_date = models.DateField()

    class Meta:
        ordering = ['-start_date']

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


class Weekday(models.Model):
    class DayCode(models.IntegerChoices):
        MONDAY = 0, "Monday"
        TUESDAY = 1, "Tuesday"
        WEDNESDAY = 2, "Wednesday"
        THURSDAY = 3, "Thursday"
        FRIDAY = 4, "Friday"
        SATURDAY = 5, "Saturday"
        SUNDAY = 6, "Sunday"

    code = models.PositiveSmallIntegerField(choices=DayCode.choices, unique=True)

    class Meta:
        ordering = ["code"]

    def __str__(self):
        return self.get_code_display()


class Class(models.Model):
    title = models.CharField(max_length=200, default='Untitled Class')
    days = models.ManyToManyField(Weekday, related_name='classes', blank=True)
    start_time = models.TimeField(null=True, blank=True, help_text="Class start time (HH:MM)")
    end_time = models.TimeField(null=True, blank=True, help_text="Class end time (HH:MM)")
    area = models.ForeignKey(Area, on_delete=models.SET_NULL, null=True, blank=True)
    semester = models.ForeignKey(Semester, on_delete=models.SET_NULL, null=True, blank=True)
    department = models.ForeignKey(Department, on_delete=models.SET_NULL, null=True, blank=True)
    co_instructors = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        blank=True,
        related_name='classes_as_co_instructor',
    )
    instructor = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='classes_as_instructor',
    )
    students = models.ManyToManyField(Person, related_name='classes_as_student', blank=True)

    def clean(self):
        from django.core.exceptions import ValidationError
        if self.start_time and self.end_time and self.end_time <= self.start_time:
            raise ValidationError(
                {'end_time': 'End time must be after start time.'}
            )

    def __str__(self):
        return f"{self.title} (Class #{self.pk})"

    class Meta:
        permissions = [
            ("view_all_classes", "Can view all classes"),
        ]


class Event(models.Model):
    title = models.CharField(max_length=200, default='Untitled Event')
    participants = models.ManyToManyField(Person, related_name='events_as_participant', blank=True)
    facilitators = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        blank=True,
        related_name='events_as_facilitator',
    )
    area = models.ForeignKey(Area, on_delete=models.SET_NULL, null=True, blank=True)
    start_datetime = models.DateTimeField()
    end_datetime = models.DateTimeField()
    department = models.ForeignKey(Department, on_delete=models.SET_NULL, null=True, blank=True)
    semester = models.ForeignKey(Semester, on_delete=models.SET_NULL, null=True, blank=True)

    def clean(self):
        from django.core.exceptions import ValidationError
        if self.start_datetime and self.end_datetime and self.end_datetime <= self.start_datetime:
            raise ValidationError(
                {'end_datetime': 'End datetime must be after start datetime.'}
            )

    def __str__(self):
        return f"{self.title} (Event #{self.pk})"

    class Meta:
        permissions = [
            ("view_all_events", "Can view all events"),
        ]


class AIAgent(models.Model):
    class Meta:
        managed = False
        default_permissions = ()
        permissions = [
            ("access_ai_agent", "Can access AI Agent"),
        ]


def default_fields_config():
    return {}


class IDTemplate(models.Model):
    name = models.CharField(max_length=100, unique=True)
    description = models.TextField(blank=True, null=True)
    background_image = models.ImageField(upload_to=id_template_front_path, blank=True, null=True)
    background_image_back = models.ImageField(upload_to=id_template_back_path, blank=True, null=True)
    width = models.FloatField(default=86.0, help_text="Card width in mm")
    height = models.FloatField(default=54.0, help_text="Card height in mm")
    fields_config = models.JSONField(
        default=default_fields_config,
        blank=True,
        help_text="Stores placement of fields: x, y, alignment, font_size, width, height, etc."
    )
    is_active = models.BooleanField(default=True)
    default_departments = models.ManyToManyField(
        "Department",
        blank=True,
        related_name="default_templates",
        help_text="Departments this template is intended for (used for auto-selection on ID registration)."
    )
    default_person_type = models.CharField(
        max_length=20,
        blank=True,
        choices=[("student", "Student"), ("employee", "Employee")],
        help_text="Person type this template targets (leave blank for any type)."
    )

    def __str__(self):
        return self.name


class IDCard(models.Model):
    STATUS_CHOICES = (
        ('draft', 'Draft'),
        ('active', 'Active'),
        ('lost', 'Lost'),
        ('damaged', 'Damaged'),
        ('expired', 'Expired'),
    )

    person = models.ForeignKey(Person, on_delete=models.CASCADE, related_name='cards')
    card_number = models.CharField(max_length=50, db_index=True)
    template = models.ForeignKey(IDTemplate, on_delete=models.SET_NULL, blank=True, null=True, related_name='cards')
    status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
    id_image = models.ImageField(upload_to=idcard_front_path, blank=True, null=True)
    id_image_back = models.ImageField(upload_to=idcard_back_path, blank=True, null=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['-created_at']
        constraints = [
            models.UniqueConstraint(
                condition=models.Q(status='active'),
                fields=['card_number'],
                name='uniq_active_card_number',
            )
        ]

    def __str__(self):
        return f"{self.card_number} ({self.status})"


class IDCardPrintLog(models.Model):
    REASON_CHOICES = (
        ('first_issue', 'First Issue'),
        ('lost_replacement', 'Lost Replacement'),
        ('damaged_replacement', 'Damaged Replacement'),
        ('reprint_error', 'Reprint (Error)'),
        ('info_update', 'Info Update'),
        ('test_print', 'Test Print'),
    )

    id_card = models.ForeignKey(IDCard, on_delete=models.CASCADE, related_name='print_logs')
    printed_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, related_name='card_print_logs')
    reason = models.CharField(max_length=30, choices=REASON_CHOICES, default='first_issue')
    notes = models.TextField(blank=True, null=True)
    printed_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['-printed_at']
        indexes = [
            models.Index(fields=['-printed_at'], name='idx_printlog_printed_at'),
            models.Index(fields=['reason'], name='idx_printlog_reason'),
            models.Index(fields=['printed_by'], name='idx_printlog_printed_by'),
        ]

    def __str__(self):
        return f"Print log for {self.id_card} by {self.printed_by} @ {self.printed_at}"


class MCPAPIKey(models.Model):
    name = models.CharField(max_length=100)
    prefix = models.CharField(max_length=16, db_index=True)
    key_hash = models.CharField(max_length=71, unique=True, db_index=True)
    scopes = models.JSONField(default=list, blank=True)
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    expires_at = models.DateTimeField(blank=True, null=True)
    last_used_at = models.DateTimeField(blank=True, null=True)
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        related_name='mcp_api_keys',
    )

    class Meta:
        verbose_name = "MCP API Key"
        verbose_name_plural = "MCP API Keys"
        ordering = ["-created_at"]

    @staticmethod
    def generate_raw_key():
        raw_key = f"sk_mcp_live_{secrets.token_hex(32)}"
        prefix = raw_key[:12]
        key_hash = f"sha256:{hashlib.sha256(raw_key.encode('utf-8')).hexdigest()}"
        return raw_key, prefix, key_hash

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


class NotificationQuerySet(models.QuerySet):
    def for_user(self, user):
        if not user or not user.is_authenticated or not getattr(user, "is_active", True):
            return self.none()
        if user.is_superuser:
            return self

        user_perms = user.get_all_permissions()
        perm_filter = (
            models.Q(required_permission__isnull=True)
            | models.Q(required_permission="")
            | models.Q(required_permission__in=user_perms)
        )
        audience_filter = (
            models.Q(is_public=True)
            | models.Q(groups__in=user.groups.all())
            | models.Q(from_user=user)
            | models.Q(recipient_user=user)
        )
        return self.filter(perm_filter & audience_filter).distinct()

    def unread_for(self, user):
        return self.for_user(user).exclude(read_by=user).distinct()

    def read_for(self, user):
        return self.for_user(user).filter(read_by=user).distinct()


class Notification(models.Model):
    class NotificationType(models.TextChoices):
        INFO = "info", "Info"
        SUCCESS = "success", "Success"
        WARNING = "warning", "Warning"
        ALERT = "alert", "Alert"

    title = models.CharField(max_length=255)
    description = models.TextField(help_text="Notification description in Markdown format")
    from_user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="sent_notifications",
    )
    recipient_user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        related_name="direct_notifications",
        help_text="Optional direct recipient user for private notifications",
    )
    is_public = models.BooleanField(
        default=False,
        db_index=True,
        help_text="Public notifications are visible to all users",
    )
    groups = models.ManyToManyField(
        "auth.Group",
        blank=True,
        related_name="notifications",
        help_text="Target groups that can view this notification",
    )
    required_permission = models.CharField(
        max_length=100,
        blank=True,
        null=True,
        default=None,
        db_index=True,
        help_text="Optional Django permission codename required to view this notification (e.g. 'core.view_device')",
    )
    type = models.CharField(
        max_length=20,
        choices=NotificationType.choices,
        default=NotificationType.INFO,
    )
    read_by = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        through="NotificationRead",
        related_name="read_notifications",
        blank=True,
    )
    created_at = models.DateTimeField(auto_now_add=True, db_index=True)
    updated_at = models.DateTimeField(auto_now=True)

    objects = NotificationQuerySet.as_manager()

    class Meta:
        verbose_name = "Notification"
        verbose_name_plural = "Notifications"
        ordering = ["-created_at"]

    def __str__(self):
        return self.title

    def clean(self):
        super().clean()
        if self.required_permission:
            self.required_permission = self.required_permission.strip().lower()
            if "." not in self.required_permission:
                from django.core.exceptions import ValidationError
                raise ValidationError({
                    "required_permission": "Permission must be in the format '<app_label>.<codename>' (e.g. 'core.view_device')."
                })

    def mark_as_read_for(self, user):
        if user and user.is_authenticated:
            NotificationRead.objects.get_or_create(user=user, notification=self)

    def mark_as_unread_for(self, user):
        if user and user.is_authenticated:
            NotificationRead.objects.filter(user=user, notification=self).delete()

    def is_read_by(self, user):
        if not user or not user.is_authenticated:
            return False
        if hasattr(self, "_prefetched_objects_cache") and "read_by" in self._prefetched_objects_cache:
            return user in self.read_by.all()
        return self.read_by.filter(pk=user.pk).exists()

    def rendered_description(self):
        """Render markdown description safely to HTML."""
        if not self.description:
            return ""
        try:
            import markdown
            return markdown.markdown(
                self.description,
                extensions=["extra", "nl2br", "sane_lists", "smarty"]
            )
        except Exception:
            from django.utils.html import linebreaks, escape
            return linebreaks(escape(self.description))


class NotificationRead(models.Model):
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="notification_reads",
    )
    notification = models.ForeignKey(
        Notification,
        on_delete=models.CASCADE,
        related_name="reads",
    )
    read_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = ("user", "notification")
        verbose_name = "Notification Read Receipt"
        verbose_name_plural = "Notification Read Receipts"
        indexes = [
            models.Index(fields=["user", "notification"]),
        ]

    def __str__(self):
        return f"{self.user} read '{self.notification}' @ {self.read_at}"


class Ticket(models.Model):
    class TicketType(models.TextChoices):
        BUG = "bug", _("Bug Report")
        FEATURE = "feature", _("Feature Request")
        IMPROVEMENT = "improvement", _("Improvement")
        SUPPORT = "support", _("Support / Question")

    class Priority(models.TextChoices):
        LOW = "low", _("Low")
        MEDIUM = "medium", _("Medium")
        HIGH = "high", _("High")
        URGENT = "urgent", _("Urgent")

    class Status(models.TextChoices):
        NEW = "new", _("New")
        IN_PROGRESS = "in_progress", _("In Progress")
        RESOLVED = "resolved", _("Resolved")
        CANCELED = "canceled", _("Canceled")

    title = models.CharField(max_length=255, verbose_name=_("Title"))
    ticket_type = models.CharField(
        max_length=32,
        choices=TicketType.choices,
        default=TicketType.BUG,
        verbose_name=_("Category"),
    )
    priority = models.CharField(
        max_length=32,
        choices=Priority.choices,
        default=Priority.MEDIUM,
        verbose_name=_("Priority"),
    )
    description = models.TextField(
        verbose_name=_("Description"),
        help_text=_("Detailed description in Markdown format"),
    )
    image = models.ImageField(
        upload_to="tickets/images/%Y/%m/",
        null=True,
        blank=True,
        validators=[validate_ticket_image_size],
        verbose_name=_("Attachment Image"),
        help_text=_("Optional screenshot or image (max 5MB)"),
    )
    submitted_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="submitted_tickets",
        verbose_name=_("Submitted By"),
    )
    assigned_to = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="assigned_tickets",
        verbose_name=_("Assigned To"),
    )
    status = models.CharField(
        max_length=32,
        choices=Status.choices,
        default=Status.NEW,
        db_index=True,
        verbose_name=_("Status"),
    )
    status_message = models.TextField(
        blank=True,
        verbose_name=_("Status / Resolution Notes"),
        help_text=_("Triage, progress updates, or resolution notes"),
    )
    created_at = models.DateTimeField(auto_now_add=True, db_index=True, verbose_name=_("Created At"))
    updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
    resolved_at = models.DateTimeField(null=True, blank=True, db_index=True, verbose_name=_("Resolved At"))

    class Meta:
        verbose_name = _("Ticket")
        verbose_name_plural = _("Tickets")
        ordering = ["-created_at"]
        permissions = [
            ("can_submit_ticket", "Can submit new ticket"),
            ("can_manage_tickets", "Can manage ticket status and assignment"),
        ]

    def __str__(self):
        return f"#{self.pk or '?'} {self.title} [{self.get_status_display()}]"

    def rendered_description(self):
        """Render markdown description safely to HTML."""
        if not self.description:
            return ""
        try:
            import markdown
            return markdown.markdown(
                self.description,
                extensions=["extra", "nl2br", "sane_lists", "smarty"]
            )
        except Exception:
            from django.utils.html import linebreaks, escape
            return linebreaks(escape(self.description))

    def rendered_status_message(self):
        """Render markdown status message safely to HTML."""
        if not self.status_message:
            return ""
        try:
            import markdown
            return markdown.markdown(
                self.status_message,
                extensions=["extra", "nl2br", "sane_lists", "smarty"]
            )
        except Exception:
            from django.utils.html import linebreaks, escape
            return linebreaks(escape(self.status_message))


class Dataset(models.Model):
    class Format(models.TextChoices):
        CSV = "csv", _("CSV (.csv)")
        EXCEL = "excel", _("Excel (.xlsx)")
        SQL = "sql", _("SQL Dump (.sql)")

    class Status(models.TextChoices):
        PENDING = "pending", _("Pending")
        PROCESSING = "processing", _("Processing")
        READY = "ready", _("Ready")
        FAILED = "failed", _("Failed")
        EXPIRED = "expired", _("Expired")

    title = models.CharField(max_length=255, verbose_name=_("Title"))
    description = models.TextField(blank=True, null=True, verbose_name=_("Description"))
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="created_datasets",
        verbose_name=_("Created By"),
    )
    config = models.JSONField(default=dict, blank=True, verbose_name=_("Export Configuration"))
    format = models.CharField(max_length=10, choices=Format.choices, default=Format.CSV, verbose_name=_("Format"))
    status = models.CharField(
        max_length=20,
        choices=Status.choices,
        default=Status.PENDING,
        db_index=True,
        verbose_name=_("Status"),
    )
    file = models.FileField(upload_to="datasets/exports/%Y/%m/", blank=True, null=True, verbose_name=_("Export File"))
    file_size = models.BigIntegerField(default=0, verbose_name=_("File Size (Bytes)"))
    total_records = models.IntegerField(default=0, verbose_name=_("Total Records"))
    manifest = models.JSONField(default=dict, blank=True, verbose_name=_("Manifest"))
    error_message = models.TextField(blank=True, null=True, verbose_name=_("Error Message"))
    created_at = models.DateTimeField(auto_now_add=True, db_index=True, verbose_name=_("Created At"))
    updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
    completed_at = models.DateTimeField(null=True, blank=True, verbose_name=_("Completed At"))
    expires_at = models.DateTimeField(null=True, blank=True, db_index=True, verbose_name=_("Expires At"))

    class Meta:
        verbose_name = _("Dataset")
        verbose_name_plural = _("Datasets")
        ordering = ["-created_at"]
        permissions = [
            ("can_export_unmasked_pii", "Can export datasets with unmasked PII"),
        ]

    def __str__(self):
        return f"{self.title} [{self.get_status_display()}]"

    @property
    def file_size_display(self):
        if not self.file_size:
            return "0 B"
        size = float(self.file_size)
        for unit in ["B", "KB", "MB", "GB"]:
            if size < 1024.0:
                return f"{size:.1f} {unit}"
            size /= 1024.0
        return f"{size:.1f} TB"

    def rendered_description(self):
        if not self.description:
            return ""
        try:
            import markdown
            return markdown.markdown(
                self.description,
                extensions=["extra", "nl2br", "sane_lists", "smarty"]
            )
        except Exception:
            from django.utils.html import linebreaks, escape
            return linebreaks(escape(self.description))

