from django.db import models
from django.contrib.auth.models import User
from django.utils.crypto import get_random_string
from django.db.models import Manager, Q
from django.conf import settings
import math
from users.models import Organization, Agent
from django.db.models import Count, Q
from django.utils import timezone
from django.contrib.auth import get_user_model

def haversine(lat1, lon1, lat2, lon2):
    """
    Calculate the great circle distance in kilometers between two points 
    on the earth specified in decimal degrees
    """
    # convert decimal degrees to radians
    lat1, lon1, lat2, lon2 = map(math.radians, [lat1, lon1, lat2, lon2])
    
    # haversine formula
    dlon = lon2 - lon1 
    dlat = lat2 - lat1 
    a = math.sin(dlat/2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon/2)**2
    c = 2 * math.asin(math.sqrt(a)) 
    
    km = 6371 * c  # Radius of earth in kilometers
    return km

class PropertyManager(Manager):
    def available(self):
        return self.filter(property_status='available')

    def for_rent(self):
        return self.filter(post_type='rent')

    def for_buy(self):
        return self.filter(post_type='buy')

    def search(self, keyword):
        # Simple search across title, description, city, and area_address
        return self.filter(
            Q(title__icontains=keyword) |
            Q(description__icontains=keyword) |
            Q(city__icontains=keyword) |
            Q(area_address__icontains=keyword)
        )
    # Existing methods...

    def search_with_filters(self, post_type=None, property_type=None, latitude=None, longitude=None, radius_km=5):
        qs = self.get_queryset()

        lat, lon = None, None
        if latitude is not None and longitude is not None:
            lat, lon = float(latitude), float(longitude)

        # Base filter: only available properties
        qs = qs.filter(property_status='available')

        # Filter by post_type and property_type
        if post_type:
            qs = qs.filter(post_type=post_type)
        if property_type:
            qs = qs.filter(property_type=property_type)

        # Bounding box filter for normal properties if lat/lon given
        if lat is not None and lon is not None:
            lat_delta = radius_km / 111  # approx degrees latitude
            lon_delta = radius_km / (111 * math.cos(math.radians(lat)))

            min_lat = lat - lat_delta
            max_lat = lat + lat_delta
            min_lon = lon - lon_delta
            max_lon = lon + lon_delta

            qs = qs.filter(latitude__gte=min_lat, latitude__lte=max_lat,
                        longitude__gte=min_lon, longitude__lte=max_lon)

        # Prepare boosted properties queryset
        boosted_qs = PropertyBoost.objects.filter(
            expires_at__gt=timezone.now(),
            property__property_status='available'
        )

        if post_type:
            boosted_qs = boosted_qs.filter(property__post_type=post_type)
        if property_type:
            boosted_qs = boosted_qs.filter(property__property_type=property_type)

        # Bounding box for boosted with fixed 20km radius if lat/lon provided
        if lat is not None and lon is not None:
            boost_radius_km = 20
            lat_delta_boost = boost_radius_km / 111
            lon_delta_boost = boost_radius_km / (111 * math.cos(math.radians(lat)))

            min_lat_boost = lat - lat_delta_boost
            max_lat_boost = lat + lat_delta_boost
            min_lon_boost = lon - lon_delta_boost
            max_lon_boost = lon + lon_delta_boost

            boosted_qs = boosted_qs.filter(
                property__latitude__gte=min_lat_boost,
                property__latitude__lte=max_lat_boost,
                property__longitude__gte=min_lon_boost,
                property__longitude__lte=max_lon_boost,
            )

        boosted_qs = boosted_qs.select_related('property').order_by('-boosted_at')

        boosted_props = []
        boosted_prop_ids = set()
        if lat is not None and lon is not None:
            for boost in boosted_qs:
                p = boost.property
                if p.latitude is not None and p.longitude is not None:
                    dist = haversine(lat, lon, float(p.latitude), float(p.longitude))
                    if dist <= 20:
                        boosted_props.append(p)
                        boosted_prop_ids.add(p.id)
        else:
            boosted_props = [boost.property for boost in boosted_qs]
            boosted_prop_ids = {p.id for p in boosted_props}

        # Exclude boosted properties from normal queryset
        qs = qs.exclude(id__in=boosted_prop_ids).order_by('-created_at')

        filtered_props = []
        if lat is not None and lon is not None:
            for prop in qs:
                if prop.latitude is not None and prop.longitude is not None:
                    distance = haversine(lat, lon, float(prop.latitude), float(prop.longitude))
                    if distance <= radius_km:
                        filtered_props.append(prop)
        else:
            filtered_props = list(qs)

        # Final list: boosted first, then filtered
        result = boosted_props + filtered_props

        return result


class Property(models.Model):
    # Choices
    POST_TYPE_CHOICES = [
        ('buy', 'Buy'),
        ('rent', 'Rent'),
    ]

    PROPERTY_TYPE_CHOICES = [
        ('new_home', 'New Home'),
        ('home', 'Home'),
        ('room', 'Room'),
        ('garage', 'Garage'),
        ('storage_room', 'Storage Room'),
        ('office', 'Office'),
        ('commercial_property', 'Commercial Property'),
        ('transfer', 'Transfer'),
        ('land', 'Land'),
        ('building', 'Building'),
        ('holiday', 'Holiday'),
    ]

    PROPERTY_STATUS_CHOICES = [
        ('available', 'Available'),
        ('unavailable', 'Unavailable'),
    ]

    AREA_UNIT_CHOICES = [
        ('sqft', 'Square Feet'),
        ('sqm', 'Square Meter'),
    ]

    PRICE_TERM_CHOICES = [
        ('daily', '/Day'),
        ('weekly', '/Week'),
        ('monthly', '/Month'),
        ('quarterly', '/3 Months'),
        ('biannually', '/6 Months'),
        ('annually', '/Year'),
    ]

    CURRENCY_CHOICES = [
        ('EUR', '€ - Euro'),
        ('USD', '$ - US Dollar'),
        ('GBP', '£ - British Pound'),
        ('JPY', '¥ - Japanese Yen'),
        ('AUD', 'A$ - Australian Dollar'),
        ('CAD', 'C$ - Canadian Dollar'),
        ('CHF', 'CHF - Swiss Franc'),
        ('CNY', '¥ - Chinese Yuan'),
        ('INR', '₹ - Indian Rupee'),

        # Additional currencies from the image
        ('TRY', '₺ - Turkish Lira'),
        ('SEK', 'kr. - Swedish Krona'),
        ('PLN', 'zł - Polish Złoty'),
        ('NOK', 'kr. - Norwegian Krone'),
        ('ISK', 'kr. - Icelandic Króna'),
        ('DKK', 'kr. - Danish Krone'),
        ('CZK', 'Kč - Czech Koruna'),
        ('HUF', 'Ft. - Hungarian Forint'),
        ('BDT', '৳ - Bangladeshi Taka'),
    ]

    # Fields
    post_type = models.CharField(max_length=10, choices=POST_TYPE_CHOICES)
    property_type = models.CharField(max_length=20, choices=PROPERTY_TYPE_CHOICES)
    property_status = models.CharField(max_length=12, choices=PROPERTY_STATUS_CHOICES)
    available_from = models.DateField()
    bedrooms = models.PositiveIntegerField()
    bathrooms = models.PositiveIntegerField()
    area = models.FloatField()
    area_unit = models.CharField(max_length=5, choices=AREA_UNIT_CHOICES)
    description = models.TextField()

    # Address fields
    apartment_no = models.CharField(max_length=50, blank=True, null=True)
    area_address = models.CharField(max_length=255)
    house_no = models.CharField(max_length=50)
    street = models.CharField(max_length=255)
    city = models.CharField(max_length=100)
    state = models.CharField(max_length=100)
    country = models.CharField(max_length=100)
    zip_code = models.CharField(max_length=20)
    latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
    longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)

    # Property details
    property_id = models.CharField(max_length=50, unique=True, blank=True)
    floor_no = models.IntegerField()
    built_year = models.PositiveIntegerField(null=True, blank=True)
    garages_count = models.PositiveIntegerField(null=True, blank=True, default=0)
    garage_size = models.FloatField(null=True, blank=True)

    # Pricing
    price = models.DecimalField(max_digits=15, decimal_places=2)
    price_term = models.CharField(max_length=10, choices=PRICE_TERM_CHOICES)
    currency = models.CharField(max_length=3, choices=CURRENCY_CHOICES, default='USD')

    # Media
    thumbnail = models.ImageField(upload_to='properties/thumbnails/', null=True, blank=True)
    featured_pictures = models.ManyToManyField('PropertyImage', blank=True, related_name='properties')
    video = models.FileField(upload_to='properties/videos/', null=True, blank=True)
    view_360 = models.FileField(upload_to='properties/360_views/', null=True, blank=True)

    # Contact options
    contact_details_hidden = models.BooleanField(default=False)
    hide_address = models.BooleanField(default=False)
    allow_direct_chat = models.BooleanField(default=True)

    # Relations
    posted_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    agent = models.ForeignKey(Agent, on_delete=models.SET_NULL, null=True, blank=True, related_name='properties')

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

    # Attach custom manager
    objects = PropertyManager()

    def save(self, *args, **kwargs):
        # Auto-generate property_id if blank: e.g. "PR-<timestamp>"
        if not self.property_id:
            import time
            timestamp = int(time.time() * 1000)
            random_part = get_random_string(length=4).upper()
            self.property_id = f"{random_part}-{timestamp}"
        super().save(*args, **kwargs)

    # Helper methods
    def formatted_price(self):
        symbol = dict(self.CURRENCY_CHOICES).get(self.currency, '')
        term = dict(self.PRICE_TERM_CHOICES).get(self.price_term, '')
        return f"{symbol}{self.price:,.2f} {term}"

    def full_address(self):
        parts = [
            self.house_no,
            self.street,
            self.area_address,
            self.city,
            self.state,
            self.country,
            self.zip_code
        ]
        return ', '.join(filter(None, parts))

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

    class Meta:
        ordering = ['-created_at']
        verbose_name = 'Property'
        verbose_name_plural = 'Properties'

class PropertyImage(models.Model):
    image = models.ImageField(upload_to='properties/featured_pictures/')
    uploaded_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"Image {self.id} for property"

class PropertyBookmarkManager(models.Manager):
    def for_user(self, user):
        return self.filter(user=user).select_related('property')

    def is_bookmarked(self, user, property):
        return self.filter(user=user, property=property).exists()

class PropertyBookmark(models.Model):
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="property_bookmarks"
    )
    property = models.ForeignKey(
        'Property',  # or your app name: 'yourapp.Property'
        on_delete=models.CASCADE,
        related_name="bookmarked_by"
    )
    created_at = models.DateTimeField(auto_now_add=True)

    objects = PropertyBookmarkManager()

    class Meta:
        unique_together = ('user', 'property')
        ordering = ['-created_at']

    def __str__(self):
        return f"{self.user} bookmarked {self.property}"

    # Optional: method to toggle bookmark
    @classmethod
    def toggle(cls, user, property):
        bookmark, created = cls.objects.get_or_create(user=user, property=property)
        if not created:
            bookmark.delete()
            return False  # Unbookmarked
        return True  # Bookmarked

# --- PropertyView Analytics Manager ---
class PropertyAnalyticsManager(models.Manager):
    def total_views(self, property):
        return self.filter(property=property).count()

    def unique_user_views(self, property):
        # Unique by user if logged in, else session_key (for guests)
        return self.filter(property=property).values('user', 'session_key').distinct().count()

    def skip_publisher_views(self, property):
        return self.filter(property=property, is_publisher=False).count()

    def total_logged_in_user_views(self, property):
        return self.filter(property=property, user__isnull=False, is_publisher=False).count()

    def total_guest_user_views(self, property):
        return self.filter(property=property, user__isnull=True, is_publisher=False).count()

    def by_location(self, property):
        return self.filter(property=property, is_publisher=False)\
                   .values('location').annotate(count=Count('id')).order_by('-count')

    def by_device(self, property):
        return self.filter(property=property, is_publisher=False)\
                   .values('device').annotate(count=Count('id')).order_by('-count')

    def publisher_views(self, property):
        return self.filter(property=property, is_publisher=True).count()

    def analytics_summary(self, property):
        return {
            'total_views': self.total_views(property),
            'unique_user_views': self.unique_user_views(property),
            'total_logged_in_user_views': self.total_logged_in_user_views(property),
            'total_guest_user_views': self.total_guest_user_views(property),
            'skip_publisher_views': self.skip_publisher_views(property),
            'publisher_views': self.publisher_views(property),
            'by_location': list(self.by_location(property)),
            'by_device': list(self.by_device(property)),
        }

# --- The PropertyView Model ---
class PropertyView(models.Model):
    property = models.ForeignKey('Property', on_delete=models.CASCADE, related_name='views')
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True)
    session_key = models.CharField(max_length=64, blank=True, null=True, db_index=True)
    ip_address = models.GenericIPAddressField(null=True, blank=True)
    location = models.CharField(max_length=255, blank=True)  # e.g., 'Dhaka, Bangladesh' or lat/long string
    device = models.CharField(max_length=64, blank=True)     # e.g., 'Mobile', 'Desktop', etc.
    is_publisher = models.BooleanField(default=False)
    viewed_at = models.DateTimeField(default=timezone.now)

    objects = PropertyAnalyticsManager()

    class Meta:
        indexes = [
            models.Index(fields=["property", "user", "session_key"]),
            models.Index(fields=["property", "viewed_at"]),
        ]
        ordering = ["-viewed_at"]

    def __str__(self):
        who = "Publisher" if self.is_publisher else (self.user or self.session_key or 'Guest')
        return f"{self.property} viewed by {who} at {self.viewed_at:%Y-%m-%d %H:%M}"

class PropertyAddonsFee(models.Model):
    hide_address_fee = models.DecimalField(max_digits=6, decimal_places=2, default=1.99)
    hide_contact_fee = models.DecimalField(max_digits=6, decimal_places=2, default=1.55)
    hide_address_and_contact_combined_fee = models.DecimalField(max_digits=6, decimal_places=2, default=2.99)

    def __str__(self):
        return f"Addons Fee (ID: {self.id})"

class PropertyBoost(models.Model):
    property = models.ForeignKey('Property', on_delete=models.CASCADE, related_name='boosts')
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='property_boosts')
    boosted_at = models.DateTimeField(default=timezone.now)
    expires_at = models.DateTimeField()

    def is_active(self):
        return timezone.now() < self.expires_at

    def time_left(self):
        if self.is_active():
            return self.expires_at - timezone.now()
        return None

    def __str__(self):
        return f"{self.property} boosted by {self.user} until {self.expires_at:%Y-%m-%d %H:%M}"

    class Meta:
        ordering = ['-boosted_at']
        indexes = [
            models.Index(fields=['property', 'expires_at']),
        ]
        unique_together = ('property', 'user', 'boosted_at')  # Optional: prevent duplicate boosts at the same time

class PropertyBoostPrice(models.Model):
    price = models.DecimalField(max_digits=8, decimal_places=2, default=4.99)  # Set your default boost price

    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"Boost Price: {self.price} (last updated {self.updated_at:%Y-%m-%d %H:%M})"

    class Meta:
        verbose_name = "Property Boost Price"
        verbose_name_plural = "Property Boost Prices"

class TempProperty(models.Model):
    user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='temp_properties')
    agent = models.ForeignKey(Agent, on_delete=models.SET_NULL, null=True, blank=True, related_name='temp_properties')
    post_type = models.CharField(max_length=50, blank=True)
    property_type = models.CharField(max_length=50, blank=True)
    property_status = models.CharField(max_length=50, blank=True)
    available_from = models.CharField(max_length=50, blank=True)
    bedrooms = models.CharField(max_length=10, blank=True)
    bathrooms = models.CharField(max_length=10, blank=True)
    area = models.CharField(max_length=20, blank=True)
    area_unit = models.CharField(max_length=10, blank=True)
    area_address = models.CharField(max_length=255, blank=True)
    house_no = models.CharField(max_length=50, blank=True)
    street = models.CharField(max_length=255, blank=True)
    city = models.CharField(max_length=100, blank=True)
    state = models.CharField(max_length=100, blank=True)
    country = models.CharField(max_length=100, blank=True)
    zip_code = models.CharField(max_length=20, blank=True)
    latitude = models.CharField(max_length=50, blank=True)
    longitude = models.CharField(max_length=50, blank=True)
    floor_no = models.CharField(max_length=10, blank=True)
    built_year = models.CharField(max_length=10, blank=True)
    garages_count = models.CharField(max_length=10, blank=True)
    garage_size = models.CharField(max_length=10, blank=True)
    price = models.CharField(max_length=20, blank=True)
    price_term = models.CharField(max_length=20, blank=True)
    currency = models.CharField(max_length=10, blank=True)
    contact_details_hidden = models.BooleanField(default=False)
    allow_direct_chat = models.BooleanField(default=True)
    apartment_no = models.CharField(max_length=50, blank=True)
    hide_address = models.BooleanField(default=False)
    thumbnail = models.ImageField(upload_to='temp_property_thumbnails/', blank=True, null=True)
    video = models.FileField(upload_to='temp_property_videos/', blank=True, null=True)
    view_360 = models.FileField(upload_to='temp_property_360/', blank=True, null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    
    TMP_TYPE_CHOICES = [
        ('add', 'Add'),
        ('edit', 'Edit'),
    ]
    tmp_type = models.CharField(max_length=10, choices=TMP_TYPE_CHOICES, default='add')
    property = models.ForeignKey('Property', on_delete=models.SET_NULL, null=True, blank=True, related_name='temp_properties')


class TempPropertyImage(models.Model):
    temp_property = models.ForeignKey(TempProperty, on_delete=models.CASCADE, related_name='images')
    image = models.ImageField(upload_to='temp_property_images/')
