# ================================================================
# services/data/app/external/apis/traffic.py
# ================================================================
"""
Traffic API abstraction layer for multiple cities
"""

import asyncio
from abc import ABC, abstractmethod
from datetime import datetime
from enum import Enum
from typing import Dict, List, Any, Optional, Tuple
import structlog

logger = structlog.get_logger()


class SupportedCity(Enum):
    """Supported cities for traffic data collection"""
    MADRID = "madrid"
    BARCELONA = "barcelona"
    VALENCIA = "valencia"


class BaseTrafficClient(ABC):
    """
    Abstract base class for city-specific traffic clients
    Defines the contract that all traffic clients must implement
    """
    
    def __init__(self, city: SupportedCity):
        self.city = city
        self.logger = structlog.get_logger().bind(city=city.value)
    
    @abstractmethod
    async def get_current_traffic(self, latitude: float, longitude: float) -> Optional[Dict[str, Any]]:
        """Get current traffic data for location"""
        pass
    
    @abstractmethod
    async def get_historical_traffic(self, latitude: float, longitude: float, 
                                   start_date: datetime, end_date: datetime) -> List[Dict[str, Any]]:
        """Get historical traffic data"""
        pass
    
    @abstractmethod
    async def get_events(self, latitude: float, longitude: float, radius_km: float = 5.0) -> List[Dict[str, Any]]:
        """Get traffic incidents and events"""
        pass
    
    @abstractmethod
    def supports_location(self, latitude: float, longitude: float) -> bool:
        """Check if this client supports the given location"""
        pass


class TrafficAPIClientFactory:
    """
    Factory class to create appropriate traffic clients based on location
    """
    
    # City geographical bounds
    CITY_BOUNDS = {
        SupportedCity.MADRID: {
            'lat_min': 40.31, 'lat_max': 40.56,
            'lon_min': -3.89, 'lon_max': -3.51
        },
        SupportedCity.BARCELONA: {
            'lat_min': 41.32, 'lat_max': 41.47,
            'lon_min': 2.05, 'lon_max': 2.25
        },
        SupportedCity.VALENCIA: {
            'lat_min': 39.42, 'lat_max': 39.52,
            'lon_min': -0.42, 'lon_max': -0.32
        }
    }
    
    @classmethod
    def get_client_for_location(cls, latitude: float, longitude: float) -> Optional[BaseTrafficClient]:
        """
        Get appropriate traffic client for given location
        
        Args:
            latitude: Query location latitude
            longitude: Query location longitude
            
        Returns:
            BaseTrafficClient instance or None if location not supported
        """
        try:
            # Check each city's bounds
            for city, bounds in cls.CITY_BOUNDS.items():
                if (bounds['lat_min'] <= latitude <= bounds['lat_max'] and 
                    bounds['lon_min'] <= longitude <= bounds['lon_max']):
                    
                    logger.info("Location matched to city", 
                              city=city.value, lat=latitude, lon=longitude)
                    return cls._create_client(city)
            
            # If no specific city matches, try to find closest supported city
            closest_city = cls._find_closest_city(latitude, longitude)
            if closest_city:
                logger.info("Using closest city for location", 
                          closest_city=closest_city.value, lat=latitude, lon=longitude)
                return cls._create_client(closest_city)
            
            logger.warning("No traffic client available for location", 
                         lat=latitude, lon=longitude)
            return None
            
        except Exception as e:
            logger.error("Error getting traffic client for location", 
                        lat=latitude, lon=longitude, error=str(e))
            return None
    
    @classmethod
    def _create_client(cls, city: SupportedCity) -> BaseTrafficClient:
        """Create traffic client for specific city"""
        if city == SupportedCity.MADRID:
            from .madrid_traffic_client import MadridTrafficClient
            return MadridTrafficClient()
        elif city == SupportedCity.BARCELONA:
            # Future implementation
            raise NotImplementedError(f"Traffic client for {city.value} not yet implemented")
        elif city == SupportedCity.VALENCIA:
            # Future implementation
            raise NotImplementedError(f"Traffic client for {city.value} not yet implemented")
        else:
            raise ValueError(f"Unsupported city: {city}")
    
    @classmethod
    def _find_closest_city(cls, latitude: float, longitude: float) -> Optional[SupportedCity]:
        """Find closest supported city to given coordinates"""
        import math
        
        def distance(lat1, lon1, lat2, lon2):
            """Calculate distance between two coordinates"""
            R = 6371  # Earth's radius in km
            dlat = math.radians(lat2 - lat1)
            dlon = math.radians(lon2 - lon1)
            a = (math.sin(dlat/2) * math.sin(dlat/2) +
                 math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) *
                 math.sin(dlon/2) * math.sin(dlon/2))
            c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
            return R * c
        
        min_distance = float('inf')
        closest_city = None
        
        # City centers for distance calculation
        city_centers = {
            SupportedCity.MADRID: (40.4168, -3.7038),
            SupportedCity.BARCELONA: (41.3851, 2.1734),
            SupportedCity.VALENCIA: (39.4699, -0.3763)
        }
        
        for city, (city_lat, city_lon) in city_centers.items():
            dist = distance(latitude, longitude, city_lat, city_lon)
            if dist < min_distance and dist < 100:  # Within 100km
                min_distance = dist
                closest_city = city
        
        return closest_city
    
    @classmethod
    def get_supported_cities(cls) -> List[Dict[str, Any]]:
        """Get list of supported cities with their bounds"""
        cities = []
        for city, bounds in cls.CITY_BOUNDS.items():
            cities.append({
                "city": city.value,
                "bounds": bounds,
                "status": "active" if city == SupportedCity.MADRID else "planned"
            })
        return cities


class UniversalTrafficClient:
    """
    Universal traffic client that delegates to appropriate city-specific clients
    This is the main interface that external services should use
    """
    
    def __init__(self):
        self.factory = TrafficAPIClientFactory()
        self.client_cache = {}  # Cache clients for performance
        
    async def get_current_traffic(self, latitude: float, longitude: float) -> Optional[Dict[str, Any]]:
        """Get current traffic data for any supported location"""
        try:
            client = self._get_client_for_location(latitude, longitude)
            if client:
                return await client.get_current_traffic(latitude, longitude)
            else:
                logger.warning("No traffic data available for location", 
                             lat=latitude, lon=longitude)
                return None
        except Exception as e:
            logger.error("Error getting current traffic", 
                        lat=latitude, lon=longitude, error=str(e))
            return None
    
    async def get_historical_traffic(self, latitude: float, longitude: float, 
                                   start_date: datetime, end_date: datetime) -> List[Dict[str, Any]]:
        """Get historical traffic data for any supported location"""
        try:
            client = self._get_client_for_location(latitude, longitude)
            if client:
                return await client.get_historical_traffic(latitude, longitude, start_date, end_date)
            else:
                logger.warning("No historical traffic data available for location", 
                             lat=latitude, lon=longitude)
                return []
        except Exception as e:
            logger.error("Error getting historical traffic", 
                        lat=latitude, lon=longitude, error=str(e))
            return []
    
    async def get_events(self, latitude: float, longitude: float, radius_km: float = 5.0) -> List[Dict[str, Any]]:
        """Get traffic events for any supported location"""
        try:
            client = self._get_client_for_location(latitude, longitude)
            if client:
                return await client.get_events(latitude, longitude, radius_km)
            else:
                return []
        except Exception as e:
            logger.error("Error getting traffic events", 
                        lat=latitude, lon=longitude, error=str(e))
            return []
    
    def _get_client_for_location(self, latitude: float, longitude: float) -> Optional[BaseTrafficClient]:
        """Get cached or create new client for location"""
        cache_key = f"{latitude:.4f},{longitude:.4f}"
        
        if cache_key not in self.client_cache:
            client = self.factory.get_client_for_location(latitude, longitude)
            self.client_cache[cache_key] = client
        
        return self.client_cache[cache_key]
    
    def get_location_info(self, latitude: float, longitude: float) -> Dict[str, Any]:
        """Get information about traffic data availability for location"""
        client = self._get_client_for_location(latitude, longitude)
        if client:
            return {
                "supported": True,
                "city": client.city.value,
                "features": ["current_traffic", "historical_traffic", "events"]
            }
        else:
            return {
                "supported": False,
                "city": None,
                "features": [],
                "message": "No traffic data available for this location"
            }