"""
Acco core models.

Everything lives in a single app ("core") as requested:
  - Category / CategoryField  -> schema-driven dynamic categories (DB-defined, no code changes needed)
  - Profile                   -> KYC + roles (Lister / Discoverer, Individual / Business)
  - Listing / ListingPhoto    -> the actual "places" people list & discover
  - Favorite / SavedSearch    -> discoverer tools
  - Conversation / Message    -> in-app messaging (safer than exposing phone numbers)
  - InquiryRequest            -> structured "I'm interested" requests
  - Review                    -> moderated reviews & ratings
  - Notification              -> new listings, price drops, messages, review replies
  - Report / PotentialDuplicate -> trust & safety / spam control
"""

import uuid
from decimal import Decimal

from django.conf import settings
from django.core.validators import MinValueValidator, MaxValueValidator
from django.db import models
from django.db.models import Avg, Count
from django.urls import reverse
from django.utils import timezone
from django.utils.text import slugify


# =========================================================================
# Helpers
# =========================================================================

def listing_photo_path(instance, filename):
    ext = filename.split(".")[-1]
    return f"listings/{instance.listing.id}/{uuid.uuid4().hex}.{ext}"


def kyc_document_path(instance, filename):
    ext = filename.split(".")[-1]
    return f"kyc/{instance.user_id}/{uuid.uuid4().hex}.{ext}"


def avatar_path(instance, filename):
    ext = filename.split(".")[-1]
    return f"avatars/{instance.user_id}/{uuid.uuid4().hex}.{ext}"


class TimeStampedModel(models.Model):
    """Abstract base with created/updated timestamps."""
    created_at = models.DateTimeField(auto_now_add=True, db_index=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True


# =========================================================================
# Categories — schema-driven, stored in DB (not hardcoded)
# =========================================================================

class Category(models.Model):
    """
    A listing category or sub-category, e.g. Property > House, Places > Restaurant.
    New categories can be added from the admin without touching code.
    """

    TYPE_PROPERTY = "property"
    TYPE_PLACE = "place"
    TYPE_CHOICES = [
        (TYPE_PROPERTY, "Real Estate / Property"),
        (TYPE_PLACE, "Place / Point of Interest"),
    ]

    name = models.CharField(max_length=100)
    slug = models.SlugField(max_length=120, unique=True, blank=True)
    category_type = models.CharField(max_length=20, choices=TYPE_CHOICES, default=TYPE_PLACE)
    parent = models.ForeignKey(
        "self", null=True, blank=True, related_name="children", on_delete=models.CASCADE,
        help_text="Leave blank for a top-level category (e.g. 'Property'). "
                   "Set for a sub-category (e.g. 'House' under 'Property').",
    )
    description = models.TextField(blank=True)
    icon = models.CharField(
        max_length=50, blank=True,
        help_text="Bootstrap icon class, e.g. 'bi-house-door'.",
    )
    order = models.PositiveIntegerField(default=0)
    is_active = models.BooleanField(default=True)

    class Meta:
        ordering = ["order", "name"]
        verbose_name_plural = "Categories"

    def __str__(self):
        if self.parent:
            return f"{self.parent.name} → {self.name}"
        return self.name

    def save(self, *args, **kwargs):
        if not self.slug:
            base_slug = slugify(self.name)
            slug = base_slug
            i = 1
            while Category.objects.filter(slug=slug).exclude(pk=self.pk).exists():
                i += 1
                slug = f"{base_slug}-{i}"
            self.slug = slug
        super().save(*args, **kwargs)

    @property
    def is_leaf(self):
        return not self.children.exists()

    def get_absolute_url(self):
        return reverse("core:category_browse", kwargs={"slug": self.slug})


class CategoryField(models.Model):
    """
    Defines one dynamic field that should appear on the listing form when a
    given category is selected. This is what makes the listing form
    schema-driven instead of hardcoded per category.
    """

    TEXT = "text"
    TEXTAREA = "textarea"
    NUMBER = "number"
    DECIMAL = "decimal"
    BOOLEAN = "boolean"
    CHOICE = "choice"
    MULTICHOICE = "multichoice"
    DATE = "date"
    TIME = "time"
    DATETIME = "datetime"
    URL = "url"

    FIELD_TYPE_CHOICES = [
        (TEXT, "Short text"),
        (TEXTAREA, "Long text"),
        (NUMBER, "Whole number"),
        (DECIMAL, "Decimal number"),
        (BOOLEAN, "Yes / No"),
        (CHOICE, "Single choice"),
        (MULTICHOICE, "Multiple choice"),
        (DATE, "Date"),
        (TIME, "Time"),
        (DATETIME, "Date & time"),
        (URL, "Link / URL"),
    ]

    category = models.ForeignKey(Category, related_name="fields", on_delete=models.CASCADE)
    name = models.SlugField(
        max_length=60,
        help_text="Internal key used to store the value, e.g. 'bedrooms'. No spaces.",
    )
    label = models.CharField(max_length=100, help_text="Label shown to the user, e.g. 'Bedrooms'.")
    field_type = models.CharField(max_length=20, choices=FIELD_TYPE_CHOICES, default=TEXT)
    choices = models.CharField(
        max_length=500, blank=True,
        help_text="Comma-separated options, only used for 'Single choice' / 'Multiple choice' "
                   "e.g. 'Water,Electricity,WiFi,Parking'.",
    )
    unit = models.CharField(max_length=20, blank=True, help_text="e.g. 'm²', 'acres', 'people'.")
    help_text = models.CharField(max_length=200, blank=True)
    required = models.BooleanField(default=False)
    order = models.PositiveIntegerField(default=0)

    class Meta:
        ordering = ["order", "id"]
        unique_together = [("category", "name")]

    def __str__(self):
        return f"{self.category.name} · {self.label}"

    def choice_list(self):
        return [c.strip() for c in self.choices.split(",") if c.strip()]


# =========================================================================
# Profile — roles, KYC & verification
# =========================================================================

class Profile(models.Model):
    """
    Extends the built-in User. A single account can be BOTH a lister and a
    discoverer at once — role flags are independent, not mutually exclusive.
    """

    INDIVIDUAL = "individual"
    BUSINESS = "business"
    ACCOUNT_TYPE_CHOICES = [
        (INDIVIDUAL, "Individual Owner"),
        (BUSINESS, "Business Owner"),
    ]

    UNVERIFIED = "unverified"
    PENDING = "pending"
    VERIFIED = "verified"
    REJECTED = "rejected"
    VERIFICATION_STATUS_CHOICES = [
        (UNVERIFIED, "Unverified"),
        (PENDING, "Pending Review"),
        (VERIFIED, "Verified"),
        (REJECTED, "Rejected"),
    ]

    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="profile")

    # Contact
    phone = models.CharField(max_length=20, blank=True)
    whatsapp = models.CharField(max_length=20, blank=True)
    avatar = models.ImageField(upload_to=avatar_path, blank=True, null=True)
    bio = models.TextField(blank=True, max_length=500)

    # Roles (independent flags — one account, multiple roles)
    is_lister = models.BooleanField(default=False)
    is_discoverer = models.BooleanField(default=True)

    # Lister KYC
    account_type = models.CharField(max_length=20, choices=ACCOUNT_TYPE_CHOICES, blank=True)

    # Individual KYC
    id_number = models.CharField(max_length=50, blank=True)
    id_photo = models.ImageField(upload_to=kyc_document_path, blank=True, null=True)

    # Business KYC
    business_name = models.CharField(max_length=150, blank=True)
    business_registration_number = models.CharField(max_length=100, blank=True)
    business_address = models.CharField(max_length=255, blank=True)
    representative_id_photo = models.ImageField(upload_to=kyc_document_path, blank=True, null=True)
    proof_of_ownership = models.FileField(upload_to=kyc_document_path, blank=True, null=True)

    verification_status = models.CharField(
        max_length=20, choices=VERIFICATION_STATUS_CHOICES, default=UNVERIFIED,
    )
    verification_notes = models.TextField(
        blank=True, help_text="Internal admin notes, e.g. reason for rejection.",
    )
    verified_at = models.DateTimeField(null=True, blank=True)

    # Discoverer preferences
    preferred_intent = models.ForeignKey(
        Category, null=True, blank=True, on_delete=models.SET_NULL, related_name="+",
        help_text="What the user first said they were looking for, e.g. 'Houses for rent'.",
    )
    preferred_language = models.CharField(
        max_length=5, choices=[("en", "English"), ("ny", "Chichewa")], default="en",
    )
    preferred_currency = models.CharField(
        max_length=5, choices=[("MWK", "MWK"), ("USD", "USD")], default="MWK",
    )

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"Profile<{self.user.username}>"

    @property
    def display_name(self):
        if self.account_type == self.BUSINESS and self.business_name:
            return self.business_name
        return self.user.get_full_name() or self.user.username

    @property
    def verification_badge(self):
        """Human readable badge text used across templates."""
        if self.verification_status != self.VERIFIED:
            return None
        if self.account_type == self.BUSINESS:
            return "Verified Business"
        if self.account_type == self.INDIVIDUAL:
            return "Verified Individual"
        return "Verified"

    @property
    def is_kyc_complete_for_submission(self):
        if self.account_type == self.INDIVIDUAL:
            return bool(self.id_number and self.id_photo)
        if self.account_type == self.BUSINESS:
            return bool(
                self.business_name and self.business_registration_number
                and self.business_address and self.representative_id_photo
            )
        return False

    @property
    def average_rating_as_lister(self):
        agg = Review.objects.filter(
            listing__lister=self.user, is_approved=True
        ).aggregate(avg=Avg("rating"), count=Count("id"))
        return agg["avg"], agg["count"]


# =========================================================================
# Listings
# =========================================================================

class Listing(TimeStampedModel):

    STATUS_DRAFT = "draft"
    STATUS_PENDING = "pending_review"
    STATUS_ACTIVE = "active"
    STATUS_SOLD = "sold"
    STATUS_RENTED = "rented"
    STATUS_CLOSED = "closed"
    STATUS_REJECTED = "rejected"
    STATUS_CHOICES = [
        (STATUS_DRAFT, "Draft"),
        (STATUS_PENDING, "Pending Review"),
        (STATUS_ACTIVE, "Active"),
        (STATUS_SOLD, "Sold"),
        (STATUS_RENTED, "Rented"),
        (STATUS_CLOSED, "Closed"),
        (STATUS_REJECTED, "Rejected"),
    ]

    PURPOSE_SALE = "sale"
    PURPOSE_RENT = "rent"
    PURPOSE_SERVICE = "service"  # for places: restaurants, hospitals, etc. (no sale/rent concept)
    PURPOSE_CHOICES = [
        (PURPOSE_SALE, "For Sale"),
        (PURPOSE_RENT, "For Rent"),
        (PURPOSE_SERVICE, "Service / Visit"),
    ]

    PRICE_PERIOD_NIGHTLY = "nightly"
    PRICE_PERIOD_MONTHLY = "monthly"
    PRICE_PERIOD_YEARLY = "yearly"
    PRICE_PERIOD_ONE_TIME = "one_time"
    PRICE_PERIOD_CHOICES = [
        (PRICE_PERIOD_NIGHTLY, "Per Night"),
        (PRICE_PERIOD_MONTHLY, "Per Month"),
        (PRICE_PERIOD_YEARLY, "Per Year"),
        (PRICE_PERIOD_ONE_TIME, "One-time"),
    ]

    CURRENCY_CHOICES = [("MWK", "Malawian Kwacha (MWK)"), ("USD", "US Dollar (USD)")]

    lister = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="listings", on_delete=models.CASCADE)
    category = models.ForeignKey(Category, related_name="listings", on_delete=models.PROTECT)

    title = models.CharField(max_length=150)
    slug = models.SlugField(max_length=180, unique=True, blank=True)
    description = models.TextField()

    purpose = models.CharField(max_length=20, choices=PURPOSE_CHOICES, default=PURPOSE_SERVICE)

    # Location
    address = models.CharField(max_length=255)
    latitude = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)
    longitude = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)

    # Pricing (optional — points of interest like ATMs may have none)
    price = models.DecimalField(max_digits=14, decimal_places=2, null=True, blank=True,
                                 validators=[MinValueValidator(Decimal("0"))])
    price_period = models.CharField(max_length=20, choices=PRICE_PERIOD_CHOICES, blank=True)
    currency = models.CharField(max_length=5, choices=CURRENCY_CHOICES, default="MWK")
    application_fee = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)

    # Contact
    contact_phone = models.CharField(max_length=20, blank=True)
    contact_email = models.EmailField(blank=True)
    contact_whatsapp = models.CharField(max_length=20, blank=True)

    # Dynamic, category-specific fields — schema defined by CategoryField, stored flexibly here
    attributes = models.JSONField(default=dict, blank=True)

    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=STATUS_PENDING, db_index=True)
    rejection_reason = models.CharField(max_length=255, blank=True)

    is_verified_listing = models.BooleanField(
        default=False, help_text="Mirrors the lister's KYC verification at time of posting.",
    )
    is_featured = models.BooleanField(default=False)
    featured_until = models.DateTimeField(null=True, blank=True)

    views_count = models.PositiveIntegerField(default=0)
    contact_clicks_count = models.PositiveIntegerField(default=0)

    # Duplicate detection support
    location_fingerprint = models.CharField(max_length=64, blank=True, db_index=True)

    class Meta:
        ordering = ["-is_featured", "-created_at"]
        indexes = [
            models.Index(fields=["status", "category"]),
            models.Index(fields=["latitude", "longitude"]),
        ]

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):
        if not self.slug:
            base_slug = slugify(self.title)[:150]
            slug = base_slug
            i = 1
            while Listing.objects.filter(slug=slug).exclude(pk=self.pk).exists():
                i += 1
                slug = f"{base_slug}-{i}"
            self.slug = slug
        if self.latitude is not None and self.longitude is not None:
            self.location_fingerprint = f"{round(float(self.latitude), 4)}:{round(float(self.longitude), 4)}"
        super().save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse("core:listing_detail", kwargs={"slug": self.slug})

    @property
    def is_live(self):
        return self.status == self.STATUS_ACTIVE

    @property
    def cover_photo(self):
        photo = self.photos.filter(is_cover=True).first()
        return photo or self.photos.first()

    @property
    def rating_summary(self):
        agg = self.reviews.filter(is_approved=True).aggregate(avg=Avg("rating"), count=Count("id"))
        return {"average": round(agg["avg"], 1) if agg["avg"] else None, "count": agg["count"]}

    def register_view(self):
        Listing.objects.filter(pk=self.pk).update(views_count=models.F("views_count") + 1)

    def register_contact_click(self):
        Listing.objects.filter(pk=self.pk).update(contact_clicks_count=models.F("contact_clicks_count") + 1)


class ListingPhoto(models.Model):
    listing = models.ForeignKey(Listing, related_name="photos", on_delete=models.CASCADE)
    image = models.ImageField(upload_to=listing_photo_path)
    caption = models.CharField(max_length=150, blank=True)
    is_cover = models.BooleanField(default=False)
    order = models.PositiveIntegerField(default=0)
    uploaded_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["order", "id"]

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


# =========================================================================
# Discoverer tools
# =========================================================================

class Favorite(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="favorites", on_delete=models.CASCADE)
    listing = models.ForeignKey(Listing, related_name="favorited_by", on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = [("user", "listing")]
        ordering = ["-created_at"]

    def __str__(self):
        return f"{self.user} ♥ {self.listing}"


class SavedSearch(models.Model):
    """Powers the 'notify me about new matches / price drops' feature."""
    user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="saved_searches", on_delete=models.CASCADE)
    label = models.CharField(max_length=100, blank=True)
    category = models.ForeignKey(Category, null=True, blank=True, on_delete=models.SET_NULL)
    keyword = models.CharField(max_length=150, blank=True)
    min_price = models.DecimalField(max_digits=14, decimal_places=2, null=True, blank=True)
    max_price = models.DecimalField(max_digits=14, decimal_places=2, null=True, blank=True)
    location_text = models.CharField(max_length=255, blank=True)
    latitude = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)
    longitude = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)
    radius_km = models.PositiveIntegerField(default=10)
    verified_only = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    last_notified_at = models.DateTimeField(null=True, blank=True)

    def __str__(self):
        return self.label or f"Saved search #{self.pk}"


# =========================================================================
# Messaging
# =========================================================================

class Conversation(models.Model):
    listing = models.ForeignKey(Listing, null=True, blank=True, related_name="conversations", on_delete=models.SET_NULL)
    participants = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="conversations")
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-updated_at"]

    def __str__(self):
        names = ", ".join(u.username for u in self.participants.all()[:3])
        return f"Conversation({names})"

    def other_participant(self, user):
        return self.participants.exclude(pk=user.pk).first()

    def unread_count_for(self, user):
        return self.messages.exclude(sender=user).filter(is_read=False).count()


class Message(models.Model):
    conversation = models.ForeignKey(Conversation, related_name="messages", on_delete=models.CASCADE)
    sender = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="sent_messages", on_delete=models.CASCADE)
    body = models.TextField(max_length=3000)
    is_read = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["created_at"]

    def __str__(self):
        return f"{self.sender}: {self.body[:30]}"


# =========================================================================
# Inquiries / bookings
# =========================================================================

class InquiryRequest(models.Model):
    STATUS_PENDING = "pending"
    STATUS_RESPONDED = "responded"
    STATUS_CLOSED = "closed"
    STATUS_CHOICES = [
        (STATUS_PENDING, "Pending"),
        (STATUS_RESPONDED, "Responded"),
        (STATUS_CLOSED, "Closed"),
    ]

    listing = models.ForeignKey(Listing, related_name="inquiries", on_delete=models.CASCADE)
    requester = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="inquiries", on_delete=models.CASCADE)
    move_in_date = models.DateField(null=True, blank=True)
    budget = models.DecimalField(max_digits=14, decimal_places=2, null=True, blank=True)
    message = models.TextField(max_length=1000, blank=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=STATUS_PENDING)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return f"Inquiry for {self.listing.title} by {self.requester}"


# =========================================================================
# Reviews & ratings (moderated)
# =========================================================================

class Review(models.Model):
    listing = models.ForeignKey(Listing, related_name="reviews", on_delete=models.CASCADE)
    reviewer = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="reviews", on_delete=models.CASCADE)
    rating = models.PositiveSmallIntegerField(validators=[MinValueValidator(1), MaxValueValidator(5)])
    comment = models.TextField(max_length=1500, blank=True)
    is_approved = models.BooleanField(default=True)
    is_flagged = models.BooleanField(default=False)
    flag_reason = models.CharField(max_length=255, blank=True)
    lister_reply = models.TextField(max_length=1000, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = [("listing", "reviewer")]
        ordering = ["-created_at"]

    def __str__(self):
        return f"{self.rating}★ on {self.listing.title} by {self.reviewer}"


# =========================================================================
# Notifications
# =========================================================================

class Notification(models.Model):
    TYPE_NEW_MATCH = "new_match"
    TYPE_PRICE_DROP = "price_drop"
    TYPE_NEW_MESSAGE = "new_message"
    TYPE_REVIEW_REPLY = "review_reply"
    TYPE_KYC_STATUS = "kyc_status"
    TYPE_LISTING_STATUS = "listing_status"
    TYPE_INQUIRY = "inquiry"
    TYPE_EVENT_STATUS = "event_status"
    TYPE_TICKET_BOOKED = "ticket_booked"
    TYPE_SYSTEM = "system"
    TYPE_CHOICES = [
        (TYPE_NEW_MATCH, "New Listing Match"),
        (TYPE_PRICE_DROP, "Price Drop"),
        (TYPE_NEW_MESSAGE, "New Message"),
        (TYPE_REVIEW_REPLY, "Review Reply"),
        (TYPE_KYC_STATUS, "KYC Status Update"),
        (TYPE_LISTING_STATUS, "Listing Status Update"),
        (TYPE_INQUIRY, "New Inquiry"),
        (TYPE_EVENT_STATUS, "Event Status Update"),
        (TYPE_TICKET_BOOKED, "Ticket Booked"),
        (TYPE_SYSTEM, "System"),
    ]

    user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="notifications", on_delete=models.CASCADE)
    notification_type = models.CharField(max_length=30, choices=TYPE_CHOICES, default=TYPE_SYSTEM)
    title = models.CharField(max_length=150)
    body = models.CharField(max_length=300, blank=True)
    link = models.CharField(max_length=255, blank=True)
    is_read = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True, db_index=True)

    class Meta:
        ordering = ["-created_at"]

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


# =========================================================================
# Trust & safety
# =========================================================================

class Report(models.Model):
    REASON_SCAM = "scam"
    REASON_DUPLICATE = "duplicate"
    REASON_WRONG_INFO = "wrong_info"
    REASON_OFFENSIVE = "offensive"
    REASON_SOLD_ALREADY = "sold_already"
    REASON_OTHER = "other"
    REASON_CHOICES = [
        (REASON_SCAM, "Looks like a scam"),
        (REASON_DUPLICATE, "Duplicate listing"),
        (REASON_WRONG_INFO, "Incorrect information"),
        (REASON_OFFENSIVE, "Offensive content"),
        (REASON_SOLD_ALREADY, "Already sold / rented / closed"),
        (REASON_OTHER, "Other"),
    ]

    STATUS_PENDING = "pending"
    STATUS_REVIEWED = "reviewed"
    STATUS_ACTION_TAKEN = "action_taken"
    STATUS_DISMISSED = "dismissed"
    STATUS_CHOICES = [
        (STATUS_PENDING, "Pending"),
        (STATUS_REVIEWED, "Reviewed"),
        (STATUS_ACTION_TAKEN, "Action Taken"),
        (STATUS_DISMISSED, "Dismissed"),
    ]

    reporter = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="reports_filed", on_delete=models.CASCADE)
    listing = models.ForeignKey(Listing, null=True, blank=True, related_name="reports", on_delete=models.CASCADE)
    event = models.ForeignKey(
        "Event", null=True, blank=True, related_name="reports", on_delete=models.CASCADE,
    )
    reported_user = models.ForeignKey(
        settings.AUTH_USER_MODEL, null=True, blank=True, related_name="reports_received", on_delete=models.CASCADE,
    )
    reason = models.CharField(max_length=30, choices=REASON_CHOICES, default=REASON_OTHER)
    details = models.TextField(max_length=1000, blank=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=STATUS_PENDING)
    admin_notes = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return f"Report #{self.pk} ({self.get_reason_display()})"


class PotentialDuplicate(models.Model):
    """
    Flags near-identical listings (same coordinates + similar title) so
    moderators can review and merge/remove spam.
    """
    listing_a = models.ForeignKey(Listing, related_name="duplicate_flags_a", on_delete=models.CASCADE)
    listing_b = models.ForeignKey(Listing, related_name="duplicate_flags_b", on_delete=models.CASCADE)
    similarity_reason = models.CharField(max_length=255, blank=True)
    is_resolved = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = [("listing_a", "listing_b")]
        ordering = ["-created_at"]

    def __str__(self):
        return f"Possible duplicate: {self.listing_a_id} vs {self.listing_b_id}"


# =========================================================================
# Events — a dedicated content type, separate from Listings
#
#   EventCategory              -> event type taxonomy (Music, Sports, ...)
#   Event                      -> the event itself (schedule, venue/geo, etc.)
#   EventPhoto                 -> gallery images
#   EventTicketType            -> priced ticket tiers (every event has >= 1)
#   EventTicketOrder           -> a discoverer's ticket booking
#   EventFavorite              -> "interested" / saved events
# =========================================================================

def event_photo_path(instance, filename):
    ext = filename.split(".")[-1]
    return f"events/{instance.event_id}/{uuid.uuid4().hex}.{ext}"


class EventCategory(models.Model):
    """
    Event type taxonomy (Music, Sports, Business & Networking, Comedy, ...).
    Kept separate from the listing `Category` model since events are their
    own content type — new types can still be added from the admin without
    touching code.
    """

    name = models.CharField(max_length=80, unique=True)
    slug = models.SlugField(max_length=100, unique=True, blank=True)
    icon = models.CharField(
        max_length=50, blank=True, help_text="Bootstrap icon class, e.g. 'bi-music-note-beamed'.",
    )
    order = models.PositiveIntegerField(default=0)
    is_active = models.BooleanField(default=True)

    class Meta:
        ordering = ["order", "name"]
        verbose_name = "Event Type"
        verbose_name_plural = "Event Types"

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        if not self.slug:
            base_slug = slugify(self.name)
            slug = base_slug
            i = 1
            while EventCategory.objects.filter(slug=slug).exclude(pk=self.pk).exists():
                i += 1
                slug = f"{base_slug}-{i}"
            self.slug = slug
        super().save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse("core:event_list") + f"?event_type={self.pk}"


class Event(TimeStampedModel):

    STATUS_DRAFT = "draft"
    STATUS_PENDING = "pending_review"
    STATUS_PUBLISHED = "published"
    STATUS_POSTPONED = "postponed"
    STATUS_CANCELLED = "cancelled"
    STATUS_COMPLETED = "completed"
    STATUS_REJECTED = "rejected"
    STATUS_CHOICES = [
        (STATUS_DRAFT, "Draft"),
        (STATUS_PENDING, "Pending Review"),
        (STATUS_PUBLISHED, "Published"),
        (STATUS_POSTPONED, "Postponed"),
        (STATUS_CANCELLED, "Cancelled"),
        (STATUS_COMPLETED, "Completed"),
        (STATUS_REJECTED, "Rejected"),
    ]

    CURRENCY_CHOICES = [("MWK", "Malawian Kwacha (MWK)"), ("USD", "US Dollar (USD)")]

    organizer = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="events", on_delete=models.CASCADE)
    event_type = models.ForeignKey(EventCategory, related_name="events", on_delete=models.PROTECT)

    title = models.CharField(max_length=150)
    slug = models.SlugField(max_length=180, unique=True, blank=True)
    description = models.TextField()

    # Location — geocoordinates + human address, or a virtual link
    venue_name = models.CharField(max_length=150, blank=True, help_text="e.g. 'Bingu International Conference Centre'.")
    address = models.CharField(max_length=255, blank=True)
    latitude = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)
    longitude = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)
    is_online = models.BooleanField(default=False, help_text="Check if this is a virtual / online event.")
    online_url = models.URLField(blank=True, help_text="Link to join — shown to people with a confirmed booking.")

    # Schedule
    start_datetime = models.DateTimeField(help_text="When the event starts.")
    end_datetime = models.DateTimeField(help_text="When the event ends.")
    doors_open_time = models.TimeField(null=True, blank=True, help_text="Optional — gates / doors open time.")

    # Pricing summary — denormalised from ticket types for fast list/card display
    currency = models.CharField(max_length=5, choices=CURRENCY_CHOICES, default="MWK")
    is_free = models.BooleanField(default=False)
    min_ticket_price = models.DecimalField(max_digits=14, decimal_places=2, null=True, blank=True)
    max_ticket_price = models.DecimalField(max_digits=14, decimal_places=2, null=True, blank=True)

    # Capacity & extras
    total_capacity = models.PositiveIntegerField(null=True, blank=True, help_text="Leave blank for unlimited.")
    age_restriction = models.CharField(max_length=50, blank=True, help_text="e.g. '18+', 'All ages', 'Family friendly'.")
    tags = models.CharField(max_length=255, blank=True, help_text="Comma-separated keywords, e.g. 'live band, outdoor, food'.")

    # Contact
    contact_phone = models.CharField(max_length=20, blank=True)
    contact_email = models.EmailField(blank=True)
    contact_whatsapp = models.CharField(max_length=20, blank=True)

    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=STATUS_PENDING, db_index=True)
    rejection_reason = models.CharField(max_length=255, blank=True)

    is_verified_event = models.BooleanField(
        default=False, help_text="Mirrors the organizer's KYC verification at time of posting.",
    )
    is_featured = models.BooleanField(default=False)
    featured_until = models.DateTimeField(null=True, blank=True)

    views_count = models.PositiveIntegerField(default=0)

    class Meta:
        ordering = ["start_datetime"]
        indexes = [
            models.Index(fields=["status", "event_type"]),
            models.Index(fields=["start_datetime"]),
            models.Index(fields=["latitude", "longitude"]),
        ]

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):
        if not self.slug:
            base_slug = slugify(self.title)[:150]
            slug = base_slug
            i = 1
            while Event.objects.filter(slug=slug).exclude(pk=self.pk).exists():
                i += 1
                slug = f"{base_slug}-{i}"
            self.slug = slug
        super().save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse("core:event_detail", kwargs={"slug": self.slug})

    @property
    def is_live(self):
        return self.status == self.STATUS_PUBLISHED

    @property
    def is_past(self):
        return self.end_datetime < timezone.now()

    @property
    def is_happening_now(self):
        now = timezone.now()
        return self.start_datetime <= now <= self.end_datetime and self.status == self.STATUS_PUBLISHED

    @property
    def cover_photo(self):
        photo = self.photos.filter(is_cover=True).first()
        return photo or self.photos.first()

    @property
    def tag_list(self):
        return [t.strip() for t in self.tags.split(",") if t.strip()]

    @property
    def tickets_remaining(self):
        """None means unlimited / not capacity-tracked."""
        agg = self.ticket_types.aggregate(
            total=Sum("quantity_available"), sold=Sum("quantity_sold"),
        )
        if agg["total"] is None:
            return None
        return max(agg["total"] - (agg["sold"] or 0), 0)

    @property
    def is_sold_out(self):
        remaining = self.tickets_remaining
        return remaining is not None and remaining <= 0

    def refresh_price_summary(self):
        """Recompute is_free / min / max ticket price from this event's ticket tiers."""
        prices = list(self.ticket_types.values_list("price", flat=True))
        if not prices:
            is_free, min_price, max_price = True, None, None
        else:
            is_free = all(p == 0 for p in prices)
            min_price, max_price = min(prices), max(prices)
        Event.objects.filter(pk=self.pk).update(
            is_free=is_free, min_ticket_price=min_price, max_ticket_price=max_price,
        )
        self.is_free, self.min_ticket_price, self.max_ticket_price = is_free, min_price, max_price

    def register_view(self):
        Event.objects.filter(pk=self.pk).update(views_count=models.F("views_count") + 1)


class EventPhoto(models.Model):
    event = models.ForeignKey(Event, related_name="photos", on_delete=models.CASCADE)
    image = models.ImageField(upload_to=event_photo_path)
    caption = models.CharField(max_length=150, blank=True)
    is_cover = models.BooleanField(default=False)
    order = models.PositiveIntegerField(default=0)
    uploaded_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["order", "id"]

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


class EventTicketType(models.Model):
    """A priced ticket tier for an event, e.g. 'Regular', 'VIP', 'Early Bird'."""

    event = models.ForeignKey(Event, related_name="ticket_types", on_delete=models.CASCADE)
    name = models.CharField(max_length=80)
    description = models.CharField(max_length=255, blank=True, help_text="What's included, e.g. 'Front row + free drink'.")
    price = models.DecimalField(
        max_digits=12, decimal_places=2, default=Decimal("0"), validators=[MinValueValidator(Decimal("0"))],
    )
    quantity_available = models.PositiveIntegerField(null=True, blank=True, help_text="Leave blank for unlimited.")
    quantity_sold = models.PositiveIntegerField(default=0)
    sales_end = models.DateTimeField(null=True, blank=True, help_text="Optional — stop selling this tier after this time.")
    order = models.PositiveIntegerField(default=0)

    class Meta:
        ordering = ["order", "price"]

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

    @property
    def is_ticket_free(self):
        return self.price == 0

    @property
    def remaining(self):
        if self.quantity_available is None:
            return None
        return max(self.quantity_available - self.quantity_sold, 0)

    @property
    def is_available(self):
        if self.sales_end and timezone.now() > self.sales_end:
            return False
        remaining = self.remaining
        return remaining is None or remaining > 0

    def mark_sold(self, quantity):
        EventTicketType.objects.filter(pk=self.pk).update(quantity_sold=models.F("quantity_sold") + quantity)

    def unmark_sold(self, quantity):
        EventTicketType.objects.filter(pk=self.pk).update(
            quantity_sold=models.Case(
                models.When(quantity_sold__gte=quantity, then=models.F("quantity_sold") - quantity),
                default=0,
            )
        )


class EventTicketOrder(models.Model):
    """
    A discoverer's booking against a ticket tier. No payment processor is
    wired up — this records the reservation so organizers can see who is
    coming and manage capacity; a real payment gateway can be plugged in
    later against this same schema (e.g. before flipping status to
    'confirmed').
    """

    STATUS_PENDING = "pending"
    STATUS_CONFIRMED = "confirmed"
    STATUS_CANCELLED = "cancelled"
    STATUS_CHOICES = [
        (STATUS_PENDING, "Pending"),
        (STATUS_CONFIRMED, "Confirmed"),
        (STATUS_CANCELLED, "Cancelled"),
    ]

    event = models.ForeignKey(Event, related_name="ticket_orders", on_delete=models.CASCADE)
    ticket_type = models.ForeignKey(EventTicketType, related_name="orders", on_delete=models.CASCADE)
    buyer = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="event_ticket_orders", on_delete=models.CASCADE)
    quantity = models.PositiveIntegerField(default=1, validators=[MinValueValidator(1)])
    total_price = models.DecimalField(max_digits=14, decimal_places=2, default=Decimal("0"))
    attendee_name = models.CharField(max_length=150, blank=True)
    attendee_phone = models.CharField(max_length=20, blank=True)
    reference_code = models.CharField(max_length=12, unique=True, blank=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=STATUS_CONFIRMED)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return f"{self.quantity}x {self.ticket_type.name} for {self.event.title} ({self.buyer})"

    def save(self, *args, **kwargs):
        if not self.reference_code:
            code = uuid.uuid4().hex[:10].upper()
            while EventTicketOrder.objects.filter(reference_code=code).exists():
                code = uuid.uuid4().hex[:10].upper()
            self.reference_code = code
        if not self.total_price:
            self.total_price = self.ticket_type.price * self.quantity
        super().save(*args, **kwargs)


class EventFavorite(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="favorite_events", on_delete=models.CASCADE)
    event = models.ForeignKey(Event, related_name="favorited_by", on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = [("user", "event")]
        ordering = ["-created_at"]

    def __str__(self):
        return f"{self.user} ♥ {self.event}"