# ================================================================
# services/data/app/external/aemet.py - REFACTORED VERSION
# ================================================================
"""AEMET (Spanish Weather Service) API client with improved modularity"""

import math
from typing import List, Dict, Any, Optional, Tuple
from datetime import datetime, timedelta
from dataclasses import dataclass
from enum import Enum
import structlog

from app.external.base_client import BaseAPIClient
from app.core.config import settings

logger = structlog.get_logger()


class WeatherSource(Enum):
    """Weather data source types"""
    AEMET = "aemet"
    SYNTHETIC = "synthetic"
    DEFAULT = "default"


@dataclass
class WeatherStation:
    """Weather station data"""
    id: str
    name: str
    latitude: float
    longitude: float


@dataclass
class GeographicBounds:
    """Geographic boundary definition"""
    min_lat: float
    max_lat: float
    min_lon: float
    max_lon: float
    
    def contains(self, latitude: float, longitude: float) -> bool:
        """Check if coordinates are within bounds"""
        return (self.min_lat <= latitude <= self.max_lat and 
                self.min_lon <= longitude <= self.max_lon)


class AEMETConstants:
    """AEMET API constants and configuration"""
    
    # API Configuration
    MAX_DAYS_PER_REQUEST = 30
    MADRID_MUNICIPALITY_CODE = "28079"
    
    # Madrid geographic bounds
    MADRID_BOUNDS = GeographicBounds(
        min_lat=40.3, max_lat=40.6,
        min_lon=-3.9, max_lon=-3.5
    )
    
    # Weather stations in Madrid area
    MADRID_STATIONS = [
        WeatherStation("3195", "Madrid Centro", 40.4117, -3.6780),
        WeatherStation("3129", "Madrid Norte", 40.4677, -3.5552),
        WeatherStation("3197", "Madrid Sur", 40.2987, -3.7216),
    ]
    
    # Climate simulation parameters
    BASE_TEMPERATURE_SEASONAL = 5.0
    TEMPERATURE_SEASONAL_MULTIPLIER = 2.5
    DAILY_TEMPERATURE_AMPLITUDE = 8.0
    EARTH_RADIUS_KM = 6371.0


class WeatherDataParser:
    """Handles parsing of different weather data formats"""
    
    @staticmethod
    def safe_float(value: Any, default: Optional[float] = None) -> Optional[float]:
        """Safely convert value to float with fallback"""
        try:
            if value is None:
                return default
            return float(value)
        except (ValueError, TypeError):
            return default
    
    @staticmethod
    def extract_temperature_value(temp_data: Any) -> Optional[float]:
        """Extract temperature value from AEMET complex temperature structure"""
        if temp_data is None:
            return None
            
        if isinstance(temp_data, (int, float)):
            return float(temp_data)
            
        if isinstance(temp_data, str):
            try:
                return float(temp_data)
            except ValueError:
                return None
                
        if isinstance(temp_data, dict) and 'valor' in temp_data:
            return WeatherDataParser.safe_float(temp_data['valor'])
            
        if isinstance(temp_data, list) and len(temp_data) > 0:
            first_item = temp_data[0]
            if isinstance(first_item, dict) and 'valor' in first_item:
                return WeatherDataParser.safe_float(first_item['valor'])
        
        return None
    
    @staticmethod
    def generate_weather_description(temperature: Optional[float], 
                                   precipitation: Optional[float], 
                                   humidity: Optional[float]) -> str:
        """Generate weather description based on conditions"""
        if precipitation and precipitation > 5.0:
            return "Lluvioso"
        elif precipitation and precipitation > 0.1:
            return "Nuboso con lluvia"
        elif humidity and humidity > 80:
            return "Nuboso"
        elif temperature and temperature > 25:
            return "Soleado y cálido"
        elif temperature and temperature < 5:
            return "Frío"
        else:
            return "Variable"
    
    def parse_current_weather(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """Parse AEMET current weather data format"""
        if not isinstance(data, dict):
            logger.warning("Weather data is not a dictionary", data_type=type(data))
            return self._get_default_weather_data()
        
        try:
            # Log the raw data to understand the structure
            logger.debug("Parsing current weather data", data_keys=list(data.keys()) if isinstance(data, dict) else "not_dict")
            
            # Enhanced parsing for AEMET station data
            result = {
                "date": datetime.now(),
                "temperature": self.safe_float(data.get("ta"), self.safe_float(data.get("temperature"))),
                "precipitation": self.safe_float(data.get("prec"), self.safe_float(data.get("precipitation"), 0.0)),
                "humidity": self.safe_float(data.get("hr"), self.safe_float(data.get("humidity"))),
                "wind_speed": self.safe_float(data.get("vv"), self.safe_float(data.get("wind_speed"))),
                "pressure": self.safe_float(data.get("pres"), self.safe_float(data.get("pressure"))),
                "description": str(data.get("descripcion", data.get("description", "Partly cloudy"))),
                "source": WeatherSource.AEMET.value
            }
            
            # Set fallback values for required fields that are None
            if result["temperature"] is None:
                result["temperature"] = 15.0
            if result["humidity"] is None:
                result["humidity"] = 50.0
            if result["wind_speed"] is None:
                result["wind_speed"] = 10.0
            if result["pressure"] is None:
                result["pressure"] = 1013.0
                
            logger.debug("Parsed current weather successfully", 
                        temperature=result["temperature"], 
                        source=result["source"])
            
            return result
        except Exception as e:
            logger.error("Error parsing weather data", error=str(e), data=data)
            return self._get_default_weather_data()
    
    def parse_historical_data(self, data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Parse AEMET historical weather data"""
        parsed_data = []
        
        try:
            for record in data:
                if not isinstance(record, dict):
                    continue
                
                parsed_record = self._parse_single_historical_record(record)
                if parsed_record:
                    parsed_data.append(parsed_record)
                    
        except Exception as e:
            logger.error("Error parsing historical weather data", error=str(e))
        
        return parsed_data
    
    def parse_forecast_data(self, data: List[Dict[str, Any]], days: int) -> List[Dict[str, Any]]:
        """Parse AEMET forecast data"""
        forecast = []
        base_date = datetime.now().date()
        
        if not isinstance(data, list):
            logger.warning("Forecast data is not a list", data_type=type(data))
            return []
        
        try:
            if len(data) > 0 and isinstance(data[0], dict):
                aemet_data = data[0]
                dias = aemet_data.get('prediccion', {}).get('dia', [])
                
                if isinstance(dias, list) and len(dias) > 0:
                    forecast = self._parse_forecast_days(dias, days, base_date)
            
            # Fill remaining days with synthetic data if needed
            forecast = self._ensure_forecast_completeness(forecast, days)
                
        except Exception as e:
            logger.error("Error parsing AEMET forecast data", error=str(e))
            forecast = []
        
        return forecast
    
    def parse_hourly_forecast_data(self, data: List[Dict[str, Any]], hours: int) -> List[Dict[str, Any]]:
        """Parse AEMET hourly forecast data"""
        hourly_forecast = []
        base_datetime = datetime.now()
        
        if not isinstance(data, list):
            logger.warning("Hourly forecast data is not a list", data_type=type(data))
            return []
        
        try:
            # AEMET hourly forecast has different structure than daily
            logger.debug("Processing AEMET hourly forecast data", data_length=len(data))
            
            if len(data) > 0 and isinstance(data[0], dict):
                aemet_data = data[0]
                prediccion = aemet_data.get('prediccion', {})
                
                # AEMET hourly structure: prediccion -> dia[] -> each day has hourly arrays
                dias = prediccion.get('dia', [])
                
                if isinstance(dias, list):
                    hourly_forecast = self._parse_real_hourly_forecast_days(dias, hours, base_datetime)
                    logger.info("Successfully parsed AEMET hourly forecast", count=len(hourly_forecast))
                    
            if len(hourly_forecast) == 0:
                logger.warning("No hourly data parsed from AEMET response")
                
        except Exception as e:
            logger.error("Error parsing AEMET hourly forecast data", error=str(e))
            hourly_forecast = []
        
        return hourly_forecast[:hours]
    
    
    def _parse_single_historical_record(self, record: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        """Parse a single historical weather record"""
        fecha_str = record.get('fecha')
        if not fecha_str:
            return None
            
        try:
            record_date = datetime.strptime(fecha_str, '%Y-%m-%d')
        except ValueError:
            logger.warning("Invalid date format in historical data", fecha=fecha_str)
            return None
        
        # Extract and calculate temperature
        temp_max = self.safe_float(record.get('tmax'))
        temp_min = self.safe_float(record.get('tmin'))
        temperature = self._calculate_average_temperature(temp_max, temp_min)
        
        # Extract other weather parameters
        precipitation = self.safe_float(record.get('prec'), 0.0)
        humidity = self.safe_float(record.get('hr'))
        wind_speed = self.safe_float(record.get('velmedia'))
        pressure = self._extract_pressure(record)
        
        return {
            "date": record_date,
            "temperature": temperature,
            "precipitation": precipitation,
            "humidity": humidity,
            "wind_speed": wind_speed,
            "pressure": pressure,
            "description": self.generate_weather_description(temperature, precipitation, humidity),
            "source": WeatherSource.AEMET.value
        }
    
    def _calculate_average_temperature(self, temp_max: Optional[float], temp_min: Optional[float]) -> Optional[float]:
        """Calculate average temperature from max and min values"""
        if temp_max and temp_min:
            return (temp_max + temp_min) / 2
        elif temp_max:
            return temp_max - 5  # Estimate average from max
        elif temp_min:
            return temp_min + 5  # Estimate average from min
        return None
    
    def _extract_pressure(self, record: Dict[str, Any]) -> Optional[float]:
        """Extract pressure from historical record"""
        pressure = self.safe_float(record.get('presMax'))
        if not pressure:
            pressure = self.safe_float(record.get('presMin'))
        return pressure
    
    def _parse_forecast_days(self, dias: List[Dict[str, Any]], days: int, base_date: datetime.date) -> List[Dict[str, Any]]:
        """Parse forecast days from AEMET data"""
        forecast = []
        
        for i, dia in enumerate(dias[:days]):
            if not isinstance(dia, dict):
                continue
                
            forecast_date = base_date + timedelta(days=i)
            forecast_day = self._parse_single_forecast_day(dia, forecast_date, i)
            forecast.append(forecast_day)
            
        return forecast
    
    def _parse_single_forecast_day(self, dia: Dict[str, Any], forecast_date: datetime.date, day_index: int) -> Dict[str, Any]:
        """Parse a single forecast day"""
        # Extract temperature
        temp_data = dia.get('temperatura', {})
        avg_temp = self._extract_forecast_temperature(temp_data)
        
        # Extract precipitation probability
        precip_prob = self._extract_precipitation_probability(dia.get('probPrecipitacion', []))
        
        # Extract wind speed
        wind_speed = self._extract_wind_speed(dia.get('viento', []))
        
        # Generate description
        description = self._generate_forecast_description(precip_prob)
        
        return {
            "forecast_date": datetime.combine(forecast_date, datetime.min.time()),
            "generated_at": datetime.now(),
            "temperature": round(avg_temp, 1),
            "precipitation": precip_prob / 10,  # Convert percentage to mm estimate
            "humidity": 50.0 + (day_index % 20),  # Estimate
            "wind_speed": round(wind_speed, 1),
            "description": description,
            "source": WeatherSource.AEMET.value
        }
    
    def _extract_forecast_temperature(self, temp_data: Dict[str, Any]) -> float:
        """Extract temperature from forecast temperature data"""
        if isinstance(temp_data, dict):
            temp_max = self.extract_temperature_value(temp_data.get('maxima'))
            temp_min = self.extract_temperature_value(temp_data.get('minima'))
            if temp_max and temp_min:
                return (temp_max + temp_min) / 2
        return 15.0
    
    def _extract_precipitation_probability(self, precip_data: List[Dict[str, Any]]) -> float:
        """Extract precipitation probability from forecast data"""
        precip_prob = 0.0
        if isinstance(precip_data, list):
            for precip_item in precip_data:
                if isinstance(precip_item, dict) and 'value' in precip_item:
                    precip_prob = max(precip_prob, self.safe_float(precip_item.get('value'), 0.0))
        return precip_prob
    
    def _extract_wind_speed(self, viento_data: List[Dict[str, Any]]) -> float:
        """Extract wind speed from forecast data"""
        wind_speed = 10.0
        if isinstance(viento_data, list):
            for viento_item in viento_data:
                if isinstance(viento_item, dict) and 'velocidad' in viento_item:
                    speed_values = viento_item.get('velocidad', [])
                    if isinstance(speed_values, list) and len(speed_values) > 0:
                        wind_speed = self.safe_float(speed_values[0], 10.0)
                        break
        return wind_speed
    
    def _generate_forecast_description(self, precip_prob: float) -> str:
        """Generate description based on precipitation probability"""
        if precip_prob > 70:
            return "Lluvioso"
        elif precip_prob > 30:
            return "Parcialmente nublado"
        else:
            return "Soleado"
    
    def _parse_real_hourly_forecast_days(self, dias: List[Dict[str, Any]], hours: int, base_datetime: datetime) -> List[Dict[str, Any]]:
        """Parse real AEMET hourly forecast days"""
        hourly_forecast = []
        current_hour = 0
        
        for day_index, day_data in enumerate(dias):
            if current_hour >= hours:
                break
                
            if not isinstance(day_data, dict):
                continue
            
            # Parse hourly data from this day using real AEMET structure
            day_hourly = self._parse_real_aemet_hourly_day(day_data, base_datetime, day_index, current_hour, hours)
            hourly_forecast.extend(day_hourly)
            current_hour += len(day_hourly)
            
        return hourly_forecast[:hours]
    
    def _parse_real_aemet_hourly_day(self, day_data: Dict[str, Any], base_datetime: datetime, day_index: int, start_hour: int, max_hours: int) -> List[Dict[str, Any]]:
        """Parse hourly data for a single day from real AEMET hourly API response"""
        hourly_data = []
        
        # Extract fecha (date) for this day
        fecha_str = day_data.get('fecha', '')
        
        try:
            if fecha_str:
                day_date = datetime.strptime(fecha_str, '%Y-%m-%d')
            else:
                day_date = base_datetime.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=day_index)
        except ValueError:
            day_date = base_datetime.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=day_index)
        
        # Extract hourly arrays from AEMET structure
        temperatura = day_data.get('temperatura', [])
        precipitacion = day_data.get('precipitacion', [])
        humedadRelativa = day_data.get('humedadRelativa', [])
        viento = day_data.get('viento', [])
        
        # Process available hourly data
        max_available_hours = min(
            len(temperatura) if isinstance(temperatura, list) else 0,
            len(precipitacion) if isinstance(precipitacion, list) else 0,
            24,  # Max 24 hours per day
            max_hours - start_hour
        )
        
        if max_available_hours == 0:
            logger.warning("No hourly data arrays found in day data", fecha=fecha_str)
            return []
        
        for hour_idx in range(max_available_hours):
            forecast_datetime = day_date + timedelta(hours=hour_idx)
            
            # Extract hourly values safely
            temp = self._extract_aemet_hourly_value(temperatura, hour_idx)
            precip = self._extract_aemet_hourly_value(precipitacion, hour_idx, default=0.0)
            humidity = self._extract_aemet_hourly_value(humedadRelativa, hour_idx)
            wind_data = viento[hour_idx] if isinstance(viento, list) and hour_idx < len(viento) else {}
            wind_speed = self._extract_wind_from_aemet_data(wind_data)
            
            hourly_data.append({
                "forecast_datetime": forecast_datetime,
                "generated_at": datetime.now(),
                "temperature": temp if temp is not None else 15.0,
                "precipitation": precip if precip is not None else 0.0,
                "humidity": humidity if humidity is not None else 50.0,
                "wind_speed": wind_speed if wind_speed is not None else 10.0,
                "description": self._generate_hourly_description(temp, precip),
                "source": WeatherSource.AEMET.value,
                "hour": forecast_datetime.hour
            })
        
        logger.debug("Parsed AEMET hourly day", fecha=fecha_str, hours_parsed=len(hourly_data))
        return hourly_data
    
    def _extract_aemet_hourly_value(self, data_array: Any, hour_idx: int, default: Optional[float] = None) -> Optional[float]:
        """Extract hourly value from AEMET hourly data array"""
        if not isinstance(data_array, list) or hour_idx >= len(data_array):
            return default
            
        value_data = data_array[hour_idx]
        
        # Handle different AEMET data formats
        if isinstance(value_data, (int, float)):
            return float(value_data)
        elif isinstance(value_data, dict):
            # Try common AEMET field names
            for field in ['value', 'valor', 'v']:
                if field in value_data:
                    return self.safe_float(value_data[field], default)
        elif isinstance(value_data, str):
            return self.safe_float(value_data, default)
            
        return default
    
    def _extract_wind_from_aemet_data(self, wind_data: Any) -> Optional[float]:
        """Extract wind speed from AEMET wind data structure"""
        if isinstance(wind_data, dict):
            # Try common wind speed fields
            for field in ['velocidad', 'speed', 'v']:
                if field in wind_data:
                    velocidad = wind_data[field]
                    if isinstance(velocidad, list) and len(velocidad) > 0:
                        return self.safe_float(velocidad[0])
                    else:
                        return self.safe_float(velocidad)
        elif isinstance(wind_data, (int, float)):
            return float(wind_data)
            
        return None
    
    def _parse_hourly_forecast_days(self, dias: List[Dict[str, Any]], hours: int, base_datetime: datetime) -> List[Dict[str, Any]]:
        """Parse hourly forecast days from AEMET data (legacy method)"""
        hourly_forecast = []
        current_hour = 0
        
        for day_data in dias:
            if current_hour >= hours:
                break
                
            if not isinstance(day_data, dict):
                continue
            
            # Parse hourly data from this day
            day_hourly = self._parse_single_day_hourly_forecast(day_data, base_datetime, current_hour, hours)
            hourly_forecast.extend(day_hourly)
            current_hour += len(day_hourly)
            
        return hourly_forecast[:hours]
    
    def _parse_single_day_hourly_forecast(self, day_data: Dict[str, Any], base_datetime: datetime, start_hour: int, max_hours: int) -> List[Dict[str, Any]]:
        """Parse hourly data for a single day"""
        hourly_data = []
        
        # Extract hourly temperature data
        temperatura = day_data.get('temperatura', [])
        if not isinstance(temperatura, list):
            temperatura = []
        
        # Extract hourly precipitation data
        precipitacion = day_data.get('precipitacion', [])
        if not isinstance(precipitacion, list):
            precipitacion = []
        
        # Extract hourly wind data
        viento = day_data.get('viento', [])
        if not isinstance(viento, list):
            viento = []
        
        # Extract hourly humidity data
        humedadRelativa = day_data.get('humedadRelativa', [])
        if not isinstance(humedadRelativa, list):
            humedadRelativa = []
        
        # Process up to 24 hours for this day
        hours_in_day = min(24, max_hours - start_hour)
        
        for hour in range(hours_in_day):
            forecast_datetime = base_datetime + timedelta(hours=start_hour + hour)
            
            # Extract hourly values
            temp = self._extract_hourly_value(temperatura, hour)
            precip = self._extract_hourly_value(precipitacion, hour)
            wind = self._extract_hourly_value(viento, hour, 'velocidad')
            humidity = self._extract_hourly_value(humedadRelativa, hour)
            
            hourly_data.append({
                "forecast_datetime": forecast_datetime,
                "generated_at": datetime.now(),
                "temperature": temp if temp is not None else 15.0 + hour * 0.5,
                "precipitation": precip if precip is not None else 0.0,
                "humidity": humidity if humidity is not None else 50.0 + (hour % 20),
                "wind_speed": wind if wind is not None else 10.0 + (hour % 10),
                "description": self._generate_hourly_description(temp, precip),
                "source": WeatherSource.AEMET.value,
                "hour": forecast_datetime.hour
            })
        
        return hourly_data
    
    def _extract_hourly_value(self, data: List[Dict[str, Any]], hour: int, key: str = 'value') -> Optional[float]:
        """Extract hourly value from AEMET hourly data structure"""
        if not data or hour >= len(data):
            return None
        
        hour_data = data[hour] if hour < len(data) else {}
        if not isinstance(hour_data, dict):
            return None
        
        if key == 'velocidad' and 'velocidad' in hour_data:
            velocidad_list = hour_data.get('velocidad', [])
            if isinstance(velocidad_list, list) and len(velocidad_list) > 0:
                return self.safe_float(velocidad_list[0])
        
        return self.safe_float(hour_data.get(key))
    
    def _generate_hourly_description(self, temp: Optional[float], precip: Optional[float]) -> str:
        """Generate description for hourly forecast"""
        if precip and precip > 0.5:
            return "Lluvia"
        elif precip and precip > 0.1:
            return "Llovizna"
        elif temp and temp > 25:
            return "Soleado"
        elif temp and temp < 5:
            return "Frío"
        else:
            return "Variable"
    
    def _ensure_forecast_completeness(self, forecast: List[Dict[str, Any]], days: int) -> List[Dict[str, Any]]:
        """Return forecast as is - no synthetic data filling"""
        return forecast[:days]
    
    
    def _ensure_hourly_forecast_completeness(self, forecast: List[Dict[str, Any]], hours: int) -> List[Dict[str, Any]]:
        """Return hourly forecast as is - no synthetic data filling"""
        return forecast[:hours]
    
    def _get_default_weather_data(self) -> Dict[str, Any]:
        """Get default weather data structure"""
        return {
            "date": datetime.now(),
            "temperature": 15.0,
            "precipitation": 0.0,
            "humidity": 50.0,
            "wind_speed": 10.0,
            "pressure": 1013.0,
            "description": "Data not available",
            "source": WeatherSource.DEFAULT.value
        }




class LocationService:
    """Handles location-related operations"""
    
    @staticmethod
    def find_nearest_station(latitude: float, longitude: float) -> Optional[str]:
        """Find nearest weather station to given coordinates"""
        try:
            # Check if coordinates are reasonable (not extreme values)
            if not (-90 <= latitude <= 90 and -180 <= longitude <= 180):
                logger.warning("Invalid coordinate range", lat=latitude, lon=longitude)
                return None
            
            # Check if coordinates are too far from Madrid area (more than 1000km away)
            madrid_center = (40.4168, -3.7038)
            distance_to_madrid = LocationService.calculate_distance(
                latitude, longitude, madrid_center[0], madrid_center[1]
            )
            
            if distance_to_madrid > 1000:  # More than 1000km from Madrid
                logger.warning("Coordinates too far from Madrid", 
                            lat=latitude, lon=longitude, distance_km=distance_to_madrid)
                return None
            
            closest_station = None
            min_distance = float('inf')
            
            for station in AEMETConstants.MADRID_STATIONS:
                distance = LocationService.calculate_distance(
                    latitude, longitude, station.latitude, station.longitude
                )
                if distance < min_distance:
                    min_distance = distance
                    closest_station = station.id
            
            return closest_station
            
        except Exception as e:
            logger.error("Failed to find nearest station", error=str(e))
            return None
    
    @staticmethod
    def get_municipality_code(latitude: float, longitude: float) -> Optional[str]:
        """Get municipality code for coordinates"""
        if AEMETConstants.MADRID_BOUNDS.contains(latitude, longitude):
            return AEMETConstants.MADRID_MUNICIPALITY_CODE
        return None
    
    @staticmethod
    def calculate_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
        """Calculate distance between two coordinates using Haversine formula"""
        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 AEMETConstants.EARTH_RADIUS_KM * c


class AEMETClient(BaseAPIClient):
    """AEMET (Spanish Weather Service) API client with improved modularity"""
    
    def __init__(self):
        super().__init__(
            base_url="https://opendata.aemet.es/opendata/api",
            api_key=settings.AEMET_API_KEY
        )
        # Override timeout with settings value
        import httpx
        self.timeout = httpx.Timeout(float(settings.AEMET_TIMEOUT))
        self.retries = settings.AEMET_RETRY_ATTEMPTS
        self.parser = WeatherDataParser()
        self.location_service = LocationService()
    
    async def get_current_weather(self, latitude: float, longitude: float) -> Optional[Dict[str, Any]]:
        """Get current weather for coordinates"""
        try:
            logger.info("🌤️ Getting current weather from AEMET", 
                       lat=latitude, lon=longitude, api_key_configured=bool(self.api_key))
            
            # Check if API key is configured
            if not self.api_key:
                logger.error("❌ AEMET API key not configured")
                return None
            
            station_id = self.location_service.find_nearest_station(latitude, longitude)
            if not station_id:
                logger.warning("❌ No weather station found for coordinates", 
                             lat=latitude, lon=longitude, 
                             madrid_bounds=f"{AEMETConstants.MADRID_BOUNDS.min_lat}-{AEMETConstants.MADRID_BOUNDS.max_lat}")
                return None
            
            logger.info("✅ Found nearest weather station", station_id=station_id)
            
            weather_data = await self._fetch_current_weather_data(station_id)
            if weather_data:
                logger.info("🎉 SUCCESS: Real AEMET weather data retrieved!", station_id=station_id)
                parsed_data = self.parser.parse_current_weather(weather_data)
                # Ensure the source is set to AEMET for successful API calls
                if parsed_data and isinstance(parsed_data, dict):
                    parsed_data["source"] = WeatherSource.AEMET.value
                    logger.info("📡 AEMET data confirmed - source set to 'aemet'", 
                              temperature=parsed_data.get("temperature"),
                              description=parsed_data.get("description"))
                return parsed_data
            
            logger.warning("⚠️ AEMET API connectivity issues", 
                         station_id=station_id, reason="aemet_api_unreachable")
            return None
            
        except Exception as e:
            logger.error("❌ AEMET API failed", 
                        error=str(e), error_type=type(e).__name__)
            return None
    
    async def get_forecast(self, latitude: float, longitude: float, days: int = 7) -> List[Dict[str, Any]]:
        """Get weather forecast for coordinates"""
        try:
            municipality_code = self.location_service.get_municipality_code(latitude, longitude)
            if not municipality_code:
                logger.error("No municipality code found for coordinates", lat=latitude, lon=longitude)
                return []
            
            forecast_data = await self._fetch_forecast_data(municipality_code)
            if forecast_data:
                parsed_forecast = self.parser.parse_forecast_data(forecast_data, days)
                if parsed_forecast:
                    return parsed_forecast
            
            logger.error("Invalid forecast data received from AEMET API")
            return []
            
        except Exception as e:
            logger.error("Failed to get weather forecast", error=str(e))
            return []
    
    async def get_hourly_forecast(self, latitude: float, longitude: float, hours: int = 48) -> List[Dict[str, Any]]:
        """Get hourly weather forecast using AEMET's hourly prediction API"""
        try:
            logger.info("🕒 Getting hourly forecast from AEMET", 
                       lat=latitude, lon=longitude, hours=hours)
            
            # Check if API key is configured
            if not self.api_key:
                logger.error("❌ AEMET API key not configured")
                return []
            
            municipality_code = self.location_service.get_municipality_code(latitude, longitude)
            if not municipality_code:
                logger.error("❌ No municipality code found for coordinates", 
                             lat=latitude, lon=longitude)
                return []
            
            logger.info("✅ Found municipality code", municipality_code=municipality_code)
            
            # Get real hourly data from AEMET API
            hourly_data = await self._fetch_hourly_forecast_data(municipality_code)
            if hourly_data:
                logger.info("🎉 SUCCESS: Real AEMET hourly data retrieved!", municipality_code=municipality_code)
                parsed_data = self.parser.parse_hourly_forecast_data(hourly_data, hours)
                if parsed_data and len(parsed_data) > 0:
                    return parsed_data
            
            logger.error("⚠️ AEMET hourly API failed or returned no data", 
                         municipality_code=municipality_code)
            return []
            
        except Exception as e:
            logger.error("❌ AEMET hourly forecast failed", 
                        error=str(e), error_type=type(e).__name__)
            return []
    
    async def get_historical_weather(self, 
                                   latitude: float, 
                                   longitude: float, 
                                   start_date: datetime, 
                                   end_date: datetime) -> List[Dict[str, Any]]:
        """Get historical weather data"""
        try:
            logger.debug("Getting historical weather from AEMET API", 
                        lat=latitude, lon=longitude, 
                        start=start_date, end=end_date)
            
            station_id = self.location_service.find_nearest_station(latitude, longitude)
            if not station_id:
                logger.error("No weather station found for historical data", 
                             lat=latitude, lon=longitude)
                return []
            
            historical_data = await self._fetch_historical_data_in_chunks(
                station_id, start_date, end_date
            )
            
            if historical_data:
                logger.debug("Successfully fetched historical weather data", 
                           total_count=len(historical_data))
                return historical_data
            else:
                logger.error("No real historical data available from AEMET")
                return []
                
        except Exception as e:
            logger.error("Failed to get historical weather from AEMET API", error=str(e))
            return []
    
    async def _fetch_current_weather_data(self, station_id: str) -> Optional[Dict[str, Any]]:
        """Fetch current weather data from AEMET API"""
        endpoint = f"/observacion/convencional/datos/estacion/{station_id}"
        initial_response = await self._get(endpoint)
        
        if not self._is_valid_initial_response(initial_response):
            return None
        
        datos_url = initial_response.get("datos")
        actual_weather_data = await self._fetch_from_url(datos_url)
        
        if (actual_weather_data and isinstance(actual_weather_data, list) 
            and len(actual_weather_data) > 0):
            return actual_weather_data[0]
        
        return None
    
    async def _fetch_forecast_data(self, municipality_code: str) -> Optional[List[Dict[str, Any]]]:
        """Fetch forecast data from AEMET API"""
        endpoint = f"/prediccion/especifica/municipio/diaria/{municipality_code}"
        initial_response = await self._get(endpoint)

        # Check for AEMET error responses
        if initial_response and isinstance(initial_response, dict):
            aemet_estado = initial_response.get("estado")
            if aemet_estado == 404 or aemet_estado == "404":
                logger.warning("AEMET API returned 404 error",
                             mensaje=initial_response.get("descripcion"),
                             municipality=municipality_code)
                return None

        if not self._is_valid_initial_response(initial_response):
            return None

        datos_url = initial_response.get("datos")
        return await self._fetch_from_url(datos_url)
    
    async def _fetch_hourly_forecast_data(self, municipality_code: str) -> Optional[List[Dict[str, Any]]]:
        """Fetch hourly forecast data from AEMET API"""
        # Note: AEMET hourly forecast API endpoint
        endpoint = f"/prediccion/especifica/municipio/horaria/{municipality_code}"
        logger.info("Requesting AEMET hourly forecast", endpoint=endpoint, municipality=municipality_code)

        initial_response = await self._get(endpoint)

        # Check for AEMET error responses
        if initial_response and isinstance(initial_response, dict):
            aemet_estado = initial_response.get("estado")
            if aemet_estado == 404 or aemet_estado == "404":
                logger.warning("AEMET API returned 404 error for hourly forecast",
                             mensaje=initial_response.get("descripcion"),
                             municipality=municipality_code)
                return None

        if not self._is_valid_initial_response(initial_response):
            logger.warning("Invalid initial response from AEMET hourly API",
                         response=initial_response, municipality=municipality_code)
            return None

        datos_url = initial_response.get("datos")
        logger.info("Fetching hourly data from AEMET datos URL", url=datos_url)

        return await self._fetch_from_url(datos_url)
    
    async def _fetch_historical_data_in_chunks(self,
                                             station_id: str,
                                             start_date: datetime,
                                             end_date: datetime) -> List[Dict[str, Any]]:
        """Fetch historical data in chunks due to AEMET API limitations"""
        import asyncio
        historical_data = []
        current_date = start_date
        chunk_count = 0

        while current_date <= end_date:
            chunk_end_date = min(
                current_date + timedelta(days=AEMETConstants.MAX_DAYS_PER_REQUEST),
                end_date
            )

            # Add delay to respect rate limits (AEMET allows ~60 requests/minute)
            # Wait 2 seconds between requests to stay well under the limit
            if chunk_count > 0:
                await asyncio.sleep(2)

            chunk_data = await self._fetch_historical_chunk(
                station_id, current_date, chunk_end_date
            )

            if chunk_data:
                historical_data.extend(chunk_data)

            current_date = chunk_end_date + timedelta(days=1)
            chunk_count += 1

            # Log progress every 5 chunks
            if chunk_count % 5 == 0:
                logger.info("Historical data fetch progress",
                          chunks_fetched=chunk_count,
                          records_so_far=len(historical_data))

        return historical_data
    
    async def _fetch_historical_chunk(self, 
                                    station_id: str, 
                                    start_date: datetime, 
                                    end_date: datetime) -> List[Dict[str, Any]]:
        """Fetch a single chunk of historical data"""
        start_str = start_date.strftime("%Y-%m-%dT00:00:00UTC")
        end_str = end_date.strftime("%Y-%m-%dT23:59:59UTC")
        
        endpoint = f"/valores/climatologicos/diarios/datos/fechaini/{start_str}/fechafin/{end_str}/estacion/{station_id}"
        initial_response = await self._get(endpoint)
        
        if not self._is_valid_initial_response(initial_response):
            logger.warning("Invalid initial response from AEMET historical API", 
                         start=start_str, end=end_str)
            return []
        
        datos_url = initial_response.get("datos")
        if not datos_url:
            logger.warning("No datos URL in AEMET historical response", 
                         start=start_str, end=end_str)
            return []
        
        actual_historical_data = await self._fetch_from_url(datos_url)
        
        if actual_historical_data and isinstance(actual_historical_data, list):
            chunk_data = self.parser.parse_historical_data(actual_historical_data)
            logger.debug("Fetched historical data chunk", 
                       count=len(chunk_data), start=start_str, end=end_str)
            return chunk_data
        else:
            logger.warning("No valid historical data received for chunk", 
                         start=start_str, end=end_str)
            return []
    
    async def _fetch_from_url(self, url: str) -> Optional[List[Dict[str, Any]]]:
        """Fetch data from AEMET datos URL"""
        try:
            data = await self._fetch_url_directly(url)

            if data is None:
                logger.warning("No data received from datos URL", url=url)
                return None

            # Check if we got an AEMET error response (dict with estado/descripcion)
            if isinstance(data, dict):
                aemet_estado = data.get("estado")
                aemet_mensaje = data.get("descripcion")

                if aemet_estado or aemet_mensaje:
                    logger.warning("AEMET datos URL returned error response",
                                 estado=aemet_estado,
                                 mensaje=aemet_mensaje,
                                 url=url)
                    return None
                else:
                    # It's a dict but not an error response - unexpected format
                    logger.warning("Expected list from datos URL but got dict",
                                 data_type=type(data),
                                 keys=list(data.keys())[:5],
                                 url=url)
                    return None

            if isinstance(data, list):
                return data

            logger.warning("Unexpected data type from datos URL",
                         data_type=type(data), url=url)
            return None

        except Exception as e:
            logger.error("Failed to fetch from datos URL", url=url, error=str(e))
            return None
    
    def _is_valid_initial_response(self, response: Any) -> bool:
        """Check if initial AEMET API response is valid"""
        return (response and isinstance(response, dict) and 
                response.get("datos") and isinstance(response.get("datos"), str))
    
