"""
Data structures for the unified map service.
"""

from dataclasses import dataclass, field
from enum import Enum
from typing import Any

from django.contrib.gis.geos import Polygon


class LocationType(Enum):
    """Types of locations supported by the map service."""

    PARK = "park"
    RIDE = "ride"
    COMPANY = "company"
    GENERIC = "generic"


@dataclass
class GeoBounds:
    """Geographic boundary box for spatial queries."""

    north: float
    south: float
    east: float
    west: float

    def __post_init__(self):
        """Validate bounds after initialization."""
        if self.north < self.south:
            raise ValueError("North bound must be greater than south bound")
        if self.east < self.west:
            raise ValueError("East bound must be greater than west bound")
        if not (-90 <= self.south <= 90 and -90 <= self.north <= 90):
            raise ValueError("Latitude bounds must be between -90 and 90")
        if not (-180 <= self.west <= 180 and -180 <= self.east <= 180):
            raise ValueError("Longitude bounds must be between -180 and 180")

    def to_polygon(self) -> Polygon:
        """Convert bounds to PostGIS Polygon for database queries."""
        return Polygon.from_bbox((self.west, self.south, self.east, self.north))

    def expand(self, factor: float = 1.1) -> "GeoBounds":
        """Expand bounds by factor for buffer queries."""
        center_lat = (self.north + self.south) / 2
        center_lng = (self.east + self.west) / 2

        lat_range = (self.north - self.south) * factor / 2
        lng_range = (self.east - self.west) * factor / 2

        return GeoBounds(
            north=min(90, center_lat + lat_range),
            south=max(-90, center_lat - lat_range),
            east=min(180, center_lng + lng_range),
            west=max(-180, center_lng - lng_range),
        )

    def contains_point(self, lat: float, lng: float) -> bool:
        """Check if a point is within these bounds."""
        return self.south <= lat <= self.north and self.west <= lng <= self.east

    def to_dict(self) -> dict[str, float]:
        """Convert to dictionary for JSON serialization."""
        return {
            "north": self.north,
            "south": self.south,
            "east": self.east,
            "west": self.west,
        }


@dataclass
class MapFilters:
    """Filtering options for map queries."""

    location_types: set[LocationType] | None = None
    park_status: set[str] | None = None  # OPERATING, CLOSED_TEMP, etc.
    ride_types: set[str] | None = None
    company_roles: set[str] | None = None  # OPERATOR, MANUFACTURER, etc.
    search_query: str | None = None
    min_rating: float | None = None
    has_coordinates: bool = True
    country: str | None = None
    state: str | None = None
    city: str | None = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for caching and serialization."""
        return {
            "location_types": ([t.value for t in self.location_types] if self.location_types else None),
            "park_status": (list(self.park_status) if self.park_status else None),
            "ride_types": list(self.ride_types) if self.ride_types else None,
            "company_roles": (list(self.company_roles) if self.company_roles else None),
            "search_query": self.search_query,
            "min_rating": self.min_rating,
            "has_coordinates": self.has_coordinates,
            "country": self.country,
            "state": self.state,
            "city": self.city,
        }


@dataclass
class UnifiedLocation:
    """Unified location interface for all location types."""

    id: str  # Composite: f"{type}_{id}"
    type: LocationType
    name: str
    coordinates: list[float]  # [lat, lng]
    address: str | None = None
    metadata: dict[str, Any] = field(default_factory=dict)
    type_data: dict[str, Any] = field(default_factory=dict)
    cluster_weight: int = 1
    cluster_category: str = "default"

    @property
    def latitude(self) -> float:
        """Get latitude from coordinates."""
        return self.coordinates[0]

    @property
    def longitude(self) -> float:
        """Get longitude from coordinates."""
        return self.coordinates[1]

    def to_geojson_feature(self) -> dict[str, Any]:
        """Convert to GeoJSON feature for mapping libraries."""
        return {
            "type": "Feature",
            "properties": {
                "id": self.id,
                "type": self.type.value,
                "name": self.name,
                "address": self.address,
                "metadata": self.metadata,
                "type_data": self.type_data,
                "cluster_weight": self.cluster_weight,
                "cluster_category": self.cluster_category,
            },
            "geometry": {
                "type": "Point",
                # GeoJSON uses lng, lat
                "coordinates": [self.longitude, self.latitude],
            },
        }

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for JSON responses."""
        return {
            "id": self.id,
            "type": self.type.value,
            "name": self.name,
            "coordinates": list(self.coordinates),
            "address": self.address,
            "metadata": self.metadata,
            "type_data": self.type_data,
            "cluster_weight": self.cluster_weight,
            "cluster_category": self.cluster_category,
        }


@dataclass
class ClusterData:
    """Represents a cluster of locations for map display."""

    id: str
    coordinates: list[float]  # [lat, lng]
    count: int
    types: set[LocationType]
    bounds: GeoBounds
    representative_location: UnifiedLocation | None = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for JSON responses."""
        return {
            "id": self.id,
            "coordinates": list(self.coordinates),
            "count": self.count,
            "types": [t.value for t in self.types],
            "bounds": self.bounds.to_dict(),
            "representative": (self.representative_location.to_dict() if self.representative_location else None),
        }


@dataclass
class MapResponse:
    """Response structure for map API calls."""

    locations: list[UnifiedLocation] = field(default_factory=list)
    clusters: list[ClusterData] = field(default_factory=list)
    bounds: GeoBounds | None = None
    total_count: int = 0
    filtered_count: int = 0
    zoom_level: int | None = None
    clustered: bool = False
    cache_hit: bool = False
    query_time_ms: int | None = None
    filters_applied: list[str] = field(default_factory=list)

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for JSON responses."""
        return {
            "status": "success",
            "data": {
                "locations": [loc.to_dict() for loc in self.locations],
                "clusters": [cluster.to_dict() for cluster in self.clusters],
                "bounds": self.bounds.to_dict() if self.bounds else None,
                "total_count": self.total_count,
                "filtered_count": self.filtered_count,
                "zoom_level": self.zoom_level,
                "clustered": self.clustered,
            },
            "meta": {
                "cache_hit": self.cache_hit,
                "query_time_ms": self.query_time_ms,
                "filters_applied": self.filters_applied,
                "pagination": {
                    "has_more": False,  # TODO(THRILLWIKI-102): Implement pagination for map data
                    "total_pages": 1,
                },
            },
        }


@dataclass
class QueryPerformanceMetrics:
    """Performance metrics for query optimization."""

    query_time_ms: int
    db_query_count: int
    cache_hit: bool
    result_count: int
    bounds_used: bool
    clustering_used: bool

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for logging."""
        return {
            "query_time_ms": self.query_time_ms,
            "db_query_count": self.db_query_count,
            "cache_hit": self.cache_hit,
            "result_count": self.result_count,
            "bounds_used": self.bounds_used,
            "clustering_used": self.clustering_used,
        }
