from django.db import models
from django.db import models
from django.conf import settings
from django.utils import timezone
from django.utils.text import slugify

import hashlib
import uuid

import markdown
from location_field.models.plain import PlainLocationField as LocationField

from utils.models import BaseModel


class Event(BaseModel):
    class TypeChoices(models.TextChoices):
        ONLINE = 'online', 'آنلاین'
        ON_SITE = 'on_site', 'حضوری'
        HYBRID = 'hybrid', 'آنلاین/حضوری'

    class StatusChoices(models.TextChoices):
        DRAFT = 'draft', 'Draft'
        PUBLISHED = 'published', 'Published'
        CANCELLED = 'cancelled', 'Cancelled'
        COMPLETED = 'completed', 'Completed'

    title = models.CharField(max_length=255)
    slug = models.SlugField(max_length=255, unique=True, blank=True)
    description = models.TextField(help_text="Event description in Markdown format")

    start_time = models.DateTimeField()
    end_time = models.DateTimeField()

    address = models.CharField(max_length=255, blank=True, null=True, help_text="Physical address or venue name")
    location = LocationField(based_fields=['address'], zoom=15, blank=True, null=True,
                             help_text="Select location on map")

    event_type = models.CharField(max_length=10, choices=TypeChoices.choices, default=TypeChoices.ON_SITE)
    online_link = models.URLField(max_length=500, blank=True, null=True,
                                  help_text="Link for online events (e.g., Zoom, Google Meet)")

    status = models.CharField(max_length=10, choices=StatusChoices.choices, default=StatusChoices.DRAFT)
    capacity = models.PositiveIntegerField(null=True, blank=True,
                                           help_text="Maximum number of attendees (leave blank for unlimited)")

    price = models.IntegerField(default=0, help_text="Price of the event. Leave blank for free events.")

    registration_start_date = models.DateTimeField(null=True, blank=True)
    registration_end_date = models.DateTimeField(null=True, blank=True)
    featured_image = models.ImageField(upload_to='events/featured/', null=True, blank=True)
    gallery_images = models.ManyToManyField('gallery.Gallery', blank=True, related_name='event_galleries',
                                            help_text="Images taken during or related to the event.")

    registration_success_markdown = models.TextField(
        blank=True, null=True,
        help_text="Optional markdown shown to users after a successful registration."
    )

    class Meta:
        ordering = ['-start_time']
        indexes = [
            models.Index(fields=['status', 'start_time']),
            models.Index(fields=['event_type']),
        ]

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.title)
        super().save(*args, **kwargs)

    @property
    def description_html(self):
        """Convert markdown description to HTML"""
        return markdown.markdown(
            self.description,
            extensions=[
                'markdown.extensions.extra',
                'markdown.extensions.toc',
            ]
        )

    @property
    def is_registration_open(self):
        now = timezone.now()
        return (self.registration_start_date is None or now >= self.registration_start_date) and \
            (self.registration_end_date is None or now <= self.registration_end_date)

    @property
    def current_attendees_count(self):
        """Count confirmed attendees"""
        return self.registrations.filter(status__in=[Registration.StatusChoices.CONFIRMED, Registration.StatusChoices.ATTENDED], is_deleted=False).count()

    @property
    def has_available_slots(self):
        """Check whether registration slots are available, treating None as unlimited capacity."""
        if self.capacity is None:
            return True
        return self.current_attendees_count < self.capacity


class Registration(BaseModel):
    class StatusChoices(models.TextChoices):
        PENDING = 'pending', 'Pending'
        CONFIRMED = 'confirmed', 'Confirmed'
        CANCELLED = 'cancelled', 'Cancelled'
        ATTENDED = 'attended', 'Attended'

    event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='registrations')
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='event_registrations')
    registered_at = models.DateTimeField(auto_now_add=True)
    status = models.CharField(max_length=10, choices=StatusChoices.choices,
                              default=StatusChoices.PENDING)
    ticket_id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)

    confirmation_email_sent_at = models.DateTimeField(null=True, blank=True)
    cancellation_email_sent_at = models.DateTimeField(null=True, blank=True)
    discount_code = models.ForeignKey(
        "payments.DiscountCode",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="registrations",
    )
    discount_amount = models.PositiveIntegerField(default=0)
    final_price = models.PositiveIntegerField(null=True, blank=True)

    class Meta:
        ordering = ['-registered_at']
        indexes = [
            models.Index(fields=['event', 'status']),
            models.Index(fields=['user']),
        ]

    def __str__(self):
        return f"{self.user.username} registered for {self.event.title}"

    @property
    def status_label(self):
        """Human-readable label for the current registration status."""
        return self.get_status_display()

    def save(self, *args, **kwargs):
            # detect create vs update
            is_create = self._state.adding
            old_status = None

            if not is_create and self.pk:
                old_status = (
                    self.__class__.objects.only("status").get(pk=self.pk).status
                )

            # save first (so we have a pk + final values)
            super().save(*args, **kwargs)

            # 1) on create -> send confirmation if pending/confirmed (and not sent before)
            if is_create and self.status == self.StatusChoices.CONFIRMED and not self.confirmation_email_sent_at:
                # lazy import to avoid circular import
                from events.tasks import send_registration_confirmation_email
                send_registration_confirmation_email.delay(str(self.pk))
                self.confirmation_email_sent_at = timezone.now()
                super().save(update_fields=["confirmation_email_sent_at"])

            # 2) status changed -> cancelled
            if (not is_create) and (old_status != self.StatusChoices.CANCELLED) and (self.status == self.StatusChoices.CANCELLED) and (not self.cancellation_email_sent_at):
                from events.tasks import send_registration_cancellation_email
                send_registration_cancellation_email.delay(str(self.pk))
                self.cancellation_email_sent_at = timezone.now()
                super().save(update_fields=["cancellation_email_sent_at"])

            # 3) status changed -> confirmed (if not sent before)
            if (not is_create) and (old_status != self.StatusChoices.CONFIRMED) and (self.status == self.StatusChoices.CONFIRMED) and (not self.confirmation_email_sent_at):
                from events.tasks import send_registration_confirmation_email
                send_registration_confirmation_email.delay(str(self.pk))
                self.confirmation_email_sent_at = timezone.now()
                super().save(update_fields=["confirmation_email_sent_at"])


class EventEmailLog(BaseModel):
    class KindChoices(models.TextChoices):
        INVITE_NON_REGISTERED = "invite_non_registered", "Invite non-registered users"
        SKYROOM_CREDENTIALS = "send_skyroom_credentials", "Skyroom credentials"
        EVENT_ANNOUNCEMENT = "send_event_announcement", "Event announcement"
        EVENT_ANNOUNCEMENT2 = "send_event_announcement2", "Event announcement 2"
        EVENT_ANNOUNCEMENT3 = "send_event_announcement3", "Event announcement 3"
        EVENT_REMINDER = "send_event_reminder", "Event reminder"

    class StatusChoices(models.TextChoices):
        PENDING = "pending", "Pending"
        SENT = "sent", "Sent"
        FAILED = "failed", "Failed"

    KIND_INVITE_NON_REGISTERED = KindChoices.INVITE_NON_REGISTERED
    KIND_SKYROOM_CREDENTIALS = KindChoices.SKYROOM_CREDENTIALS
    KIND_EVENT_ANNOUNCEMENT = KindChoices.EVENT_ANNOUNCEMENT
    KIND_EVENT_ANNOUNCEMENT2 = KindChoices.EVENT_ANNOUNCEMENT2
    KIND_EVENT_ANNOUNCEMENT3 = KindChoices.EVENT_ANNOUNCEMENT3
    KIND_EVENT_REMINDER = KindChoices.EVENT_REMINDER
    KIND_CHOICES = KindChoices.choices

    STATUS_PENDING = StatusChoices.PENDING
    STATUS_SENT = StatusChoices.SENT
    STATUS_FAILED = StatusChoices.FAILED
    STATUS_CHOICES = StatusChoices.choices

    event = models.ForeignKey('events.Event', on_delete=models.CASCADE, related_name='email_logs')
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='email_logs')
    kind = models.CharField(max_length=64, choices=KIND_CHOICES)
    status = models.CharField(max_length=16, choices=STATUS_CHOICES, default=STATUS_PENDING)
    error = models.TextField(blank=True, null=True)
    sent_at = models.DateTimeField(blank=True, null=True)
    context_hash = models.CharField(max_length=64, blank=True, null=True)

    class Meta:
        unique_together = ("event", "user", "kind", "context_hash")
        indexes = [
            models.Index(fields=["event", "kind", "status"]),
            models.Index(fields=["user", "kind", "status"]),
        ]

    def __str__(self):
        return f"{self.event.id} - {self.user.id} - {self.kind} - {self.status}"

    @staticmethod
    def _hash_context(context):
        if context is None:
            return None
        if not isinstance(context, str):
            context = str(context)
        return hashlib.sha256(context.encode("utf-8")).hexdigest()

    @classmethod
    def claim(cls, *, event_id, user_id, kind, context=None):
        context_hash = cls._hash_context(context)
        log, created = cls.objects.get_or_create(
            event_id=event_id,
            user_id=user_id,
            kind=kind,
            context_hash=context_hash,
            defaults={"status": cls.STATUS_PENDING},
        )
        if not created and log.status in (cls.STATUS_PENDING, cls.STATUS_SENT):
            return log, True
        if not created:
            log._commit_status(cls.STATUS_PENDING, error="")
        return log, False

    def _commit_status(self, status, *, error="", sent_at=None):
        self.status = status
        self.error = error
        update_fields = ["status", "error"]
        if status == self.STATUS_SENT:
            self.sent_at = sent_at or timezone.now()
            update_fields.append("sent_at")
        elif self.sent_at is not None:
            self.sent_at = None
            update_fields.append("sent_at")
        if hasattr(self, "updated_at"):
            update_fields.append("updated_at")
        self.save(update_fields=update_fields)

    def mark_sent(self):
        self._commit_status(self.STATUS_SENT)

    def mark_failed(self, error):
        self._commit_status(self.STATUS_FAILED, error=error)
