"""
Entity models for ThrillWiki Django backend.

This module contains the core entity models:
- Company: Manufacturers, operators, designers
- RideModel: Specific ride models from manufacturers
- Park: Theme parks, amusement parks, water parks, FECs
- Ride: Individual rides and roller coasters
"""
from django.db import models
from django.conf import settings
from django.contrib.contenttypes.fields import GenericRelation
from django.utils.text import slugify
from django_lifecycle import hook, AFTER_CREATE, AFTER_UPDATE, BEFORE_SAVE

from apps.core.models import VersionedModel, BaseModel

# Conditionally import GIS models only if using PostGIS backend
# This allows migrations to run on SQLite during local development
_using_postgis = (
    'postgis' in settings.DATABASES['default']['ENGINE']
)

if _using_postgis:
    from django.contrib.gis.db import models as gis_models
    from django.contrib.gis.geos import Point
    from django.contrib.postgres.search import SearchVectorField


class Company(VersionedModel):
    """
    Represents a company in the amusement industry.
    Can be a manufacturer, operator, designer, or combination.
    """
    
    COMPANY_TYPE_CHOICES = [
        ('manufacturer', 'Manufacturer'),
        ('operator', 'Operator'),
        ('designer', 'Designer'),
        ('supplier', 'Supplier'),
        ('contractor', 'Contractor'),
    ]
    
    # Basic Info
    name = models.CharField(
        max_length=255,
        unique=True,
        db_index=True,
        help_text="Official company name"
    )
    slug = models.SlugField(
        max_length=255,
        unique=True,
        db_index=True,
        help_text="URL-friendly identifier"
    )
    description = models.TextField(
        blank=True,
        help_text="Company description and history"
    )
    
    # Company Types (can be multiple)
    company_types = models.JSONField(
        default=list,
        help_text="List of company types (manufacturer, operator, etc.)"
    )
    
    # Location
    location = models.ForeignKey(
        'core.Locality',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='companies',
        help_text="Company headquarters location"
    )
    
    # Dates with precision tracking
    founded_date = models.DateField(
        null=True,
        blank=True,
        help_text="Company founding date"
    )
    founded_date_precision = models.CharField(
        max_length=20,
        default='day',
        choices=[
            ('year', 'Year'),
            ('month', 'Month'),
            ('day', 'Day'),
        ],
        help_text="Precision of founded date"
    )
    
    closed_date = models.DateField(
        null=True,
        blank=True,
        help_text="Company closure date (if applicable)"
    )
    closed_date_precision = models.CharField(
        max_length=20,
        default='day',
        choices=[
            ('year', 'Year'),
            ('month', 'Month'),
            ('day', 'Day'),
        ],
        help_text="Precision of closed date"
    )
    
    # External Links
    website = models.URLField(
        blank=True,
        help_text="Official company website"
    )
    
    # CloudFlare Images
    logo_image_id = models.CharField(
        max_length=255,
        blank=True,
        help_text="CloudFlare image ID for company logo"
    )
    logo_image_url = models.URLField(
        blank=True,
        help_text="CloudFlare image URL for company logo"
    )
    
    # Cached statistics
    park_count = models.IntegerField(
        default=0,
        help_text="Number of parks operated (for operators)"
    )
    ride_count = models.IntegerField(
        default=0,
        help_text="Number of rides manufactured (for manufacturers)"
    )
    
    # Generic relation to photos
    photos = GenericRelation(
        'media.Photo',
        related_query_name='company'
    )
    
    # Full-text search vector (PostgreSQL only)
    # Populated automatically via signals or database triggers
    # Includes: name (weight A) + description (weight B)
    
    class Meta:
        verbose_name = 'Company'
        verbose_name_plural = 'Companies'
        ordering = ['name']
        indexes = [
            models.Index(fields=['name']),
            models.Index(fields=['slug']),
        ]
    
    def __str__(self):
        return self.name
    
    @hook(BEFORE_SAVE, when='slug', is_now=None)
    def auto_generate_slug(self):
        """Auto-generate slug from name if not provided."""
        if not self.slug and self.name:
            base_slug = slugify(self.name)
            slug = base_slug
            counter = 1
            while Company.objects.filter(slug=slug).exists():
                slug = f"{base_slug}-{counter}"
                counter += 1
            self.slug = slug
    
    def update_counts(self):
        """Update cached park and ride counts."""
        self.park_count = self.operated_parks.count()
        self.ride_count = self.manufactured_rides.count()
        self.save(update_fields=['park_count', 'ride_count'])
    
    def get_photos(self, photo_type=None, approved_only=True):
        """Get photos for this company."""
        from apps.media.services import PhotoService
        service = PhotoService()
        return service.get_entity_photos(self, photo_type=photo_type, approved_only=approved_only)
    
    @property
    def main_photo(self):
        """Get the main photo."""
        photos = self.photos.filter(photo_type='main', moderation_status='approved').first()
        return photos
    
    @property
    def logo_photo(self):
        """Get the logo photo."""
        photos = self.photos.filter(photo_type='logo', moderation_status='approved').first()
        return photos


class RideModel(VersionedModel):
    """
    Represents a specific ride model from a manufacturer.
    E.g., "B&M Inverted Coaster", "Vekoma Boomerang", "Zamperla Family Gravity Coaster"
    """
    
    MODEL_TYPE_CHOICES = [
        ('coaster_model', 'Roller Coaster Model'),
        ('flat_ride_model', 'Flat Ride Model'),
        ('water_ride_model', 'Water Ride Model'),
        ('dark_ride_model', 'Dark Ride Model'),
        ('transport_ride_model', 'Transport Ride Model'),
    ]
    
    # Basic Info
    name = models.CharField(
        max_length=255,
        db_index=True,
        help_text="Model name (e.g., 'Inverted Coaster', 'Boomerang')"
    )
    slug = models.SlugField(
        max_length=255,
        unique=True,
        db_index=True,
        help_text="URL-friendly identifier"
    )
    description = models.TextField(
        blank=True,
        help_text="Model description and technical details"
    )
    
    # Manufacturer
    manufacturer = models.ForeignKey(
        'Company',
        on_delete=models.CASCADE,
        related_name='ride_models',
        help_text="Manufacturer of this ride model"
    )
    
    # Model Type
    model_type = models.CharField(
        max_length=50,
        choices=MODEL_TYPE_CHOICES,
        db_index=True,
        help_text="Type of ride model"
    )
    
    # Technical Specifications (common to most instances)
    typical_height = models.DecimalField(
        max_digits=6,
        decimal_places=1,
        null=True,
        blank=True,
        help_text="Typical height in feet"
    )
    typical_speed = models.DecimalField(
        max_digits=6,
        decimal_places=1,
        null=True,
        blank=True,
        help_text="Typical speed in mph"
    )
    typical_capacity = models.IntegerField(
        null=True,
        blank=True,
        help_text="Typical hourly capacity"
    )
    
    # CloudFlare Images
    image_id = models.CharField(
        max_length=255,
        blank=True,
        help_text="CloudFlare image ID"
    )
    image_url = models.URLField(
        blank=True,
        help_text="CloudFlare image URL"
    )
    
    # Cached statistics
    installation_count = models.IntegerField(
        default=0,
        help_text="Number of installations worldwide"
    )
    
    # Generic relation to photos
    photos = GenericRelation(
        'media.Photo',
        related_query_name='ride_model'
    )
    
    class Meta:
        verbose_name = 'Ride Model'
        verbose_name_plural = 'Ride Models'
        ordering = ['manufacturer__name', 'name']
        unique_together = [['manufacturer', 'name']]
        indexes = [
            models.Index(fields=['manufacturer', 'name']),
            models.Index(fields=['model_type']),
        ]
    
    def __str__(self):
        return f"{self.manufacturer.name} {self.name}"
    
    @hook(BEFORE_SAVE, when='slug', is_now=None)
    def auto_generate_slug(self):
        """Auto-generate slug from manufacturer and name if not provided."""
        if not self.slug and self.manufacturer and self.name:
            base_slug = slugify(f"{self.manufacturer.name} {self.name}")
            slug = base_slug
            counter = 1
            while RideModel.objects.filter(slug=slug).exists():
                slug = f"{base_slug}-{counter}"
                counter += 1
            self.slug = slug
    
    def update_installation_count(self):
        """Update cached installation count."""
        self.installation_count = self.rides.count()
        self.save(update_fields=['installation_count'])
    
    def get_photos(self, photo_type=None, approved_only=True):
        """Get photos for this ride model."""
        from apps.media.services import PhotoService
        service = PhotoService()
        return service.get_entity_photos(self, photo_type=photo_type, approved_only=approved_only)
    
    @property
    def main_photo(self):
        """Get the main photo."""
        photos = self.photos.filter(photo_type='main', moderation_status='approved').first()
        return photos


class Park(VersionedModel):
    """
    Represents an amusement park, theme park, water park, or FEC.
    
    Note: Geographic coordinates are stored differently based on database backend:
    - Production (PostGIS): Uses location_point PointField with full GIS capabilities
    - Local Dev (SQLite): Uses latitude/longitude DecimalFields (no spatial queries)
    """
    
    PARK_TYPE_CHOICES = [
        ('theme_park', 'Theme Park'),
        ('amusement_park', 'Amusement Park'),
        ('water_park', 'Water Park'),
        ('family_entertainment_center', 'Family Entertainment Center'),
        ('traveling_park', 'Traveling Park'),
        ('zoo', 'Zoo'),
        ('aquarium', 'Aquarium'),
    ]
    
    STATUS_CHOICES = [
        ('operating', 'Operating'),
        ('closed', 'Closed'),
        ('sbno', 'Standing But Not Operating'),
        ('under_construction', 'Under Construction'),
        ('planned', 'Planned'),
    ]
    
    # Basic Info
    name = models.CharField(
        max_length=255,
        db_index=True,
        help_text="Official park name"
    )
    slug = models.SlugField(
        max_length=255,
        unique=True,
        db_index=True,
        help_text="URL-friendly identifier"
    )
    description = models.TextField(
        blank=True,
        help_text="Park description and history"
    )
    
    # Type & Status
    park_type = models.CharField(
        max_length=50,
        choices=PARK_TYPE_CHOICES,
        db_index=True,
        help_text="Type of park"
    )
    status = models.CharField(
        max_length=50,
        choices=STATUS_CHOICES,
        default='operating',
        db_index=True,
        help_text="Current operational status"
    )
    
    # Dates with precision tracking
    opening_date = models.DateField(
        null=True,
        blank=True,
        db_index=True,
        help_text="Park opening date"
    )
    opening_date_precision = models.CharField(
        max_length=20,
        default='day',
        choices=[
            ('year', 'Year'),
            ('month', 'Month'),
            ('day', 'Day'),
        ],
        help_text="Precision of opening date"
    )
    
    closing_date = models.DateField(
        null=True,
        blank=True,
        help_text="Park closing date (if closed)"
    )
    closing_date_precision = models.CharField(
        max_length=20,
        default='day',
        choices=[
            ('year', 'Year'),
            ('month', 'Month'),
            ('day', 'Day'),
        ],
        help_text="Precision of closing date"
    )
    
    # Location
    location = models.ForeignKey(
        'core.Locality',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='parks',
        help_text="Park location"
    )
    
    # Precise coordinates for mapping
    # Primary in local dev (SQLite), deprecated in production (PostGIS)
    latitude = models.DecimalField(
        max_digits=10,
        decimal_places=7,
        null=True,
        blank=True,
        help_text="Latitude coordinate. Primary in local dev, use location_point in production."
    )
    longitude = models.DecimalField(
        max_digits=10,
        decimal_places=7,
        null=True,
        blank=True,
        help_text="Longitude coordinate. Primary in local dev, use location_point in production."
    )
    
    # NOTE: location_point PointField is added conditionally below if using PostGIS
    
    # Relationships
    operator = models.ForeignKey(
        'Company',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='operated_parks',
        help_text="Current park operator"
    )
    
    # External Links
    website = models.URLField(
        blank=True,
        help_text="Official park website"
    )
    
    # CloudFlare Images
    banner_image_id = models.CharField(
        max_length=255,
        blank=True,
        help_text="CloudFlare image ID for park banner"
    )
    banner_image_url = models.URLField(
        blank=True,
        help_text="CloudFlare image URL for park banner"
    )
    logo_image_id = models.CharField(
        max_length=255,
        blank=True,
        help_text="CloudFlare image ID for park logo"
    )
    logo_image_url = models.URLField(
        blank=True,
        help_text="CloudFlare image URL for park logo"
    )
    
    # Cached statistics (for performance)
    ride_count = models.IntegerField(
        default=0,
        help_text="Total number of rides"
    )
    coaster_count = models.IntegerField(
        default=0,
        help_text="Number of roller coasters"
    )
    
    # Custom fields for flexible data
    custom_fields = models.JSONField(
        default=dict,
        blank=True,
        help_text="Additional park-specific data"
    )
    
    # Generic relation to photos
    photos = GenericRelation(
        'media.Photo',
        related_query_name='park'
    )
    
    class Meta:
        verbose_name = 'Park'
        verbose_name_plural = 'Parks'
        ordering = ['name']
        indexes = [
            models.Index(fields=['name']),
            models.Index(fields=['slug']),
            models.Index(fields=['status']),
            models.Index(fields=['park_type']),
            models.Index(fields=['opening_date']),
            models.Index(fields=['location']),
        ]
    
    def __str__(self):
        return self.name
    
    @hook(BEFORE_SAVE, when='slug', is_now=None)
    def auto_generate_slug(self):
        """Auto-generate slug from name if not provided."""
        if not self.slug and self.name:
            base_slug = slugify(self.name)
            slug = base_slug
            counter = 1
            while Park.objects.filter(slug=slug).exists():
                slug = f"{base_slug}-{counter}"
                counter += 1
            self.slug = slug
    
    def update_counts(self):
        """Update cached ride counts."""
        self.ride_count = self.rides.count()
        self.coaster_count = self.rides.filter(is_coaster=True).count()
        self.save(update_fields=['ride_count', 'coaster_count'])
    
    def set_location(self, longitude, latitude):
        """
        Set park location from coordinates.
        
        Args:
            longitude: Longitude coordinate (X)
            latitude: Latitude coordinate (Y)
        
        Note: Works in both PostGIS and non-PostGIS modes.
        - PostGIS: Sets location_point and syncs to lat/lng
        - SQLite: Sets lat/lng directly
        """
        if longitude is not None and latitude is not None:
            # Always update lat/lng fields
            self.longitude = longitude
            self.latitude = latitude
            
            # If using PostGIS, also update location_point
            if _using_postgis and hasattr(self, 'location_point'):
                self.location_point = Point(float(longitude), float(latitude), srid=4326)
    
    @property
    def coordinates(self):
        """
        Get coordinates as (longitude, latitude) tuple.
        
        Returns:
            tuple: (longitude, latitude) or None if no location set
        """
        # Try PostGIS field first if available
        if _using_postgis and hasattr(self, 'location_point') and self.location_point:
            return (self.location_point.x, self.location_point.y)
        # Fall back to lat/lng fields
        elif self.longitude and self.latitude:
            return (float(self.longitude), float(self.latitude))
        return None
    
    @property
    def latitude_value(self):
        """Get latitude value (from location_point if PostGIS, else from latitude field)."""
        if _using_postgis and hasattr(self, 'location_point') and self.location_point:
            return self.location_point.y
        return float(self.latitude) if self.latitude else None
    
    @property
    def longitude_value(self):
        """Get longitude value (from location_point if PostGIS, else from longitude field)."""
        if _using_postgis and hasattr(self, 'location_point') and self.location_point:
            return self.location_point.x
        return float(self.longitude) if self.longitude else None
    
    def get_photos(self, photo_type=None, approved_only=True):
        """Get photos for this park."""
        from apps.media.services import PhotoService
        service = PhotoService()
        return service.get_entity_photos(self, photo_type=photo_type, approved_only=approved_only)
    
    @property
    def main_photo(self):
        """Get the main photo."""
        photos = self.photos.filter(photo_type='main', moderation_status='approved').first()
        return photos
    
    @property
    def banner_photo(self):
        """Get the banner photo."""
        photos = self.photos.filter(photo_type='banner', moderation_status='approved').first()
        return photos
    
    @property
    def logo_photo(self):
        """Get the logo photo."""
        photos = self.photos.filter(photo_type='logo', moderation_status='approved').first()
        return photos
    
    @property
    def gallery_photos(self):
        """Get gallery photos."""
        return self.photos.filter(photo_type='gallery', moderation_status='approved').order_by('display_order')


# Conditionally add PostGIS PointField to Park model if using PostGIS backend
if _using_postgis:
    Park.add_to_class(
        'location_point',
        gis_models.PointField(
            geography=True,
            null=True,
            blank=True,
            srid=4326,
            help_text="Geographic coordinates (PostGIS Point). Production only."
        )
    )


class Ride(VersionedModel):
    """
    Represents an individual ride or roller coaster.
    """
    
    RIDE_CATEGORY_CHOICES = [
        ('roller_coaster', 'Roller Coaster'),
        ('flat_ride', 'Flat Ride'),
        ('water_ride', 'Water Ride'),
        ('dark_ride', 'Dark Ride'),
        ('transport_ride', 'Transport Ride'),
        ('other', 'Other'),
    ]
    
    STATUS_CHOICES = [
        ('operating', 'Operating'),
        ('closed', 'Closed'),
        ('sbno', 'Standing But Not Operating'),
        ('relocated', 'Relocated'),
        ('under_construction', 'Under Construction'),
        ('planned', 'Planned'),
    ]
    
    # Basic Info
    name = models.CharField(
        max_length=255,
        db_index=True,
        help_text="Ride name"
    )
    slug = models.SlugField(
        max_length=255,
        unique=True,
        db_index=True,
        help_text="URL-friendly identifier"
    )
    description = models.TextField(
        blank=True,
        help_text="Ride description and history"
    )
    
    # Park Relationship
    park = models.ForeignKey(
        'Park',
        on_delete=models.CASCADE,
        related_name='rides',
        db_index=True,
        help_text="Park where ride is located"
    )
    
    # Ride Classification
    ride_category = models.CharField(
        max_length=50,
        choices=RIDE_CATEGORY_CHOICES,
        db_index=True,
        help_text="Broad ride category"
    )
    ride_type = models.CharField(
        max_length=100,
        blank=True,
        db_index=True,
        help_text="Specific ride type (e.g., 'Inverted Coaster', 'Drop Tower')"
    )
    
    # Quick coaster identification
    is_coaster = models.BooleanField(
        default=False,
        db_index=True,
        help_text="Is this ride a roller coaster?"
    )
    
    # Status
    status = models.CharField(
        max_length=50,
        choices=STATUS_CHOICES,
        default='operating',
        db_index=True,
        help_text="Current operational status"
    )
    
    # Dates with precision tracking
    opening_date = models.DateField(
        null=True,
        blank=True,
        db_index=True,
        help_text="Ride opening date"
    )
    opening_date_precision = models.CharField(
        max_length=20,
        default='day',
        choices=[
            ('year', 'Year'),
            ('month', 'Month'),
            ('day', 'Day'),
        ],
        help_text="Precision of opening date"
    )
    
    closing_date = models.DateField(
        null=True,
        blank=True,
        help_text="Ride closing date (if closed)"
    )
    closing_date_precision = models.CharField(
        max_length=20,
        default='day',
        choices=[
            ('year', 'Year'),
            ('month', 'Month'),
            ('day', 'Day'),
        ],
        help_text="Precision of closing date"
    )
    
    # Manufacturer & Model
    manufacturer = models.ForeignKey(
        'Company',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='manufactured_rides',
        help_text="Ride manufacturer"
    )
    model = models.ForeignKey(
        'RideModel',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='rides',
        help_text="Specific ride model"
    )
    
    # Statistics
    height = models.DecimalField(
        max_digits=6,
        decimal_places=1,
        null=True,
        blank=True,
        help_text="Height in feet"
    )
    speed = models.DecimalField(
        max_digits=6,
        decimal_places=1,
        null=True,
        blank=True,
        help_text="Top speed in mph"
    )
    length = models.DecimalField(
        max_digits=8,
        decimal_places=1,
        null=True,
        blank=True,
        help_text="Track/ride length in feet"
    )
    duration = models.IntegerField(
        null=True,
        blank=True,
        help_text="Ride duration in seconds"
    )
    inversions = models.IntegerField(
        null=True,
        blank=True,
        help_text="Number of inversions (for coasters)"
    )
    capacity = models.IntegerField(
        null=True,
        blank=True,
        help_text="Hourly capacity (riders per hour)"
    )
    
    # CloudFlare Images
    image_id = models.CharField(
        max_length=255,
        blank=True,
        help_text="CloudFlare image ID for main photo"
    )
    image_url = models.URLField(
        blank=True,
        help_text="CloudFlare image URL for main photo"
    )
    
    # Custom fields for flexible data
    custom_fields = models.JSONField(
        default=dict,
        blank=True,
        help_text="Additional ride-specific data"
    )
    
    # Generic relation to photos
    photos = GenericRelation(
        'media.Photo',
        related_query_name='ride'
    )
    
    class Meta:
        verbose_name = 'Ride'
        verbose_name_plural = 'Rides'
        ordering = ['park__name', 'name']
        indexes = [
            models.Index(fields=['park', 'name']),
            models.Index(fields=['slug']),
            models.Index(fields=['status']),
            models.Index(fields=['is_coaster']),
            models.Index(fields=['ride_category']),
            models.Index(fields=['opening_date']),
            models.Index(fields=['manufacturer']),
        ]
    
    def __str__(self):
        return f"{self.name} ({self.park.name})"
    
    @hook(BEFORE_SAVE, when='slug', is_now=None)
    def auto_generate_slug(self):
        """Auto-generate slug from park and name if not provided."""
        if not self.slug and self.park and self.name:
            base_slug = slugify(f"{self.park.name} {self.name}")
            slug = base_slug
            counter = 1
            while Ride.objects.filter(slug=slug).exists():
                slug = f"{base_slug}-{counter}"
                counter += 1
            self.slug = slug
    
    @hook(BEFORE_SAVE)
    def set_is_coaster_flag(self):
        """Auto-set is_coaster flag based on ride_category."""
        self.is_coaster = (self.ride_category == 'roller_coaster')
    
    @hook(AFTER_CREATE)
    @hook(AFTER_UPDATE, when='park', has_changed=True)
    def update_park_counts(self):
        """Update parent park's ride counts when ride is created or moved."""
        if self.park:
            self.park.update_counts()
    
    def get_photos(self, photo_type=None, approved_only=True):
        """Get photos for this ride."""
        from apps.media.services import PhotoService
        service = PhotoService()
        return service.get_entity_photos(self, photo_type=photo_type, approved_only=approved_only)
    
    @property
    def main_photo(self):
        """Get the main photo."""
        photos = self.photos.filter(photo_type='main', moderation_status='approved').first()
        return photos
    
    @property
    def gallery_photos(self):
        """Get gallery photos."""
        return self.photos.filter(photo_type='gallery', moderation_status='approved').order_by('display_order')


# Add SearchVectorField to all models for full-text search (PostgreSQL only)
# Must be at the very end after ALL class definitions
if _using_postgis:
    Company.add_to_class(
        'search_vector',
        SearchVectorField(
            null=True,
            blank=True,
            help_text="Pre-computed search vector for full-text search. Auto-updated via signals."
        )
    )
    
    RideModel.add_to_class(
        'search_vector',
        SearchVectorField(
            null=True,
            blank=True,
            help_text="Pre-computed search vector for full-text search. Auto-updated via signals."
        )
    )
    
    Park.add_to_class(
        'search_vector',
        SearchVectorField(
            null=True,
            blank=True,
            help_text="Pre-computed search vector for full-text search. Auto-updated via signals."
        )
    )
    
    Ride.add_to_class(
        'search_vector',
        SearchVectorField(
            null=True,
            blank=True,
            help_text="Pre-computed search vector for full-text search. Auto-updated via signals."
        )
    )
