"""
Weather API Fetcher.

Handles fetching real-time weather data from OpenWeatherMap API.
"""

import os
import asyncio
import json
from dataclasses import dataclass
from datetime import datetime
from typing import Any, Dict, Optional
from pathlib import Path

try:
    import aiohttp
    AIOHTTP_AVAILABLE = True
except ImportError:
    AIOHTTP_AVAILABLE = False

try:
    import requests
    REQUESTS_AVAILABLE = True
except ImportError:
    REQUESTS_AVAILABLE = False


@dataclass
class WeatherData:
    """Container for weather data."""
    condition: str
    description: str
    temperature: float
    humidity: int
    wind_speed: float
    wind_direction: int
    visibility: int
    clouds: int
    is_night: bool
    location: str
    timestamp: datetime
    icon: str
    raw_data: Dict[str, Any]
    
    @classmethod
    def from_api_response(cls, data: Dict[str, Any], location: str) -> 'WeatherData':
        """Create WeatherData from OpenWeatherMap API response."""
        weather = data.get('weather', [{}])[0]
        main = data.get('main', {})
        wind = data.get('wind', {})
        sys_data = data.get('sys', {})
        
        # Determine if it's night based on sunrise/sunset
        current_time = datetime.utcnow().timestamp()
        sunrise = sys_data.get('sunrise', 0)
        sunset = sys_data.get('sunset', 0)
        is_night = current_time < sunrise or current_time > sunset
        
        return cls(
            condition=weather.get('main', 'Clear'),
            description=weather.get('description', 'clear sky'),
            temperature=main.get('temp', 0),
            humidity=main.get('humidity', 0),
            wind_speed=wind.get('speed', 0),
            wind_direction=wind.get('deg', 0),
            visibility=data.get('visibility', 10000),
            clouds=data.get('clouds', {}).get('all', 0),
            is_night=is_night,
            location=location,
            timestamp=datetime.utcnow(),
            icon=weather.get('icon', '01d'),
            raw_data=data
        )
    
    def get_intensity(self) -> float:
        """Calculate weather intensity for animations."""
        condition_lower = self.condition.lower()
        
        if 'thunder' in condition_lower:
            return 1.5 + (self.wind_speed / 20)
        elif 'rain' in condition_lower or 'drizzle' in condition_lower:
            # Use cloud cover and humidity for rain intensity
            return 0.5 + (self.humidity / 100) + (self.clouds / 100)
        elif 'snow' in condition_lower:
            return 0.5 + (self.humidity / 100)
        elif 'fog' in condition_lower or 'mist' in condition_lower:
            # Use visibility for fog intensity (lower visibility = denser fog)
            return 2.0 - (self.visibility / 10000)
        else:
            return 1.0


class WeatherFetchError(Exception):
    """Exception raised when weather fetching fails."""
    pass


class WeatherFetcher:
    """
    Fetches weather data from OpenWeatherMap API.
    
    Supports both synchronous and asynchronous fetching,
    with caching and offline fallback capabilities.
    """
    
    BASE_URL = "https://api.openweathermap.org/data/2.5/weather"
    CACHE_FILE = Path.home() / ".soracli" / "weather_cache.json"
    
    def __init__(self, api_key: Optional[str] = None, units: str = "metric"):
        """
        Initialize weather fetcher.
        
        Args:
            api_key: OpenWeatherMap API key. If not provided, tries to get from
                     OPENWEATHERMAP_API_KEY environment variable.
            units: Temperature units ('metric', 'imperial', or 'standard')
        """
        self.api_key = api_key or os.environ.get('OPENWEATHERMAP_API_KEY')
        self.units = units
        self._cache: Dict[str, Dict] = {}
        self._load_cache()
    
    def _load_cache(self) -> None:
        """Load cached weather data from disk."""
        try:
            if self.CACHE_FILE.exists():
                with open(self.CACHE_FILE, 'r') as f:
                    self._cache = json.load(f)
        except Exception:
            self._cache = {}
    
    def _save_cache(self) -> None:
        """Save weather data to disk cache."""
        try:
            self.CACHE_FILE.parent.mkdir(parents=True, exist_ok=True)
            with open(self.CACHE_FILE, 'w') as f:
                json.dump(self._cache, f)
        except Exception:
            pass  # Cache saving is optional
    
    def _update_cache(self, location: str, data: Dict[str, Any]) -> None:
        """Update cache with new weather data."""
        self._cache[location.lower()] = {
            'data': data,
            'timestamp': datetime.utcnow().isoformat()
        }
        self._save_cache()
    
    def _get_cached(self, location: str, max_age_minutes: int = 30) -> Optional[Dict]:
        """Get cached data if available and not too old."""
        cached = self._cache.get(location.lower())
        if not cached:
            return None
        
        try:
            cached_time = datetime.fromisoformat(cached['timestamp'])
            age = (datetime.utcnow() - cached_time).total_seconds() / 60
            
            if age < max_age_minutes:
                return cached['data']
        except Exception:
            pass
        
        return None
    
    def _build_url(self, location: str) -> str:
        """Build API URL with parameters."""
        return f"{self.BASE_URL}?q={location}&appid={self.api_key}&units={self.units}"
    
    def fetch(self, location: str, use_cache: bool = True) -> WeatherData:
        """
        Fetch weather data synchronously.
        
        Args:
            location: City name or location string
            use_cache: Whether to use cached data if available
        
        Returns:
            WeatherData object
        
        Raises:
            WeatherFetchError: If fetching fails and no fallback available
        """
        if not self.api_key:
            raise WeatherFetchError(
                "No API key provided. Set OPENWEATHERMAP_API_KEY environment variable "
                "or pass api_key to WeatherFetcher."
            )
        
        # Check cache first
        if use_cache:
            cached = self._get_cached(location)
            if cached:
                return WeatherData.from_api_response(cached, location)
        
        if not REQUESTS_AVAILABLE:
            raise WeatherFetchError(
                "requests library not available. Install with: pip install requests"
            )
        
        try:
            response = requests.get(self._build_url(location), timeout=10)
            response.raise_for_status()
            data = response.json()
            
            # Update cache
            self._update_cache(location, data)
            
            return WeatherData.from_api_response(data, location)
        
        except requests.exceptions.RequestException as e:
            # Try to return cached data as fallback
            cached = self._cache.get(location.lower(), {}).get('data')
            if cached:
                return WeatherData.from_api_response(cached, location)
            
            raise WeatherFetchError(f"Failed to fetch weather data: {e}")
    
    async def fetch_async(self, location: str, use_cache: bool = True) -> WeatherData:
        """
        Fetch weather data asynchronously.
        
        Args:
            location: City name or location string
            use_cache: Whether to use cached data if available
        
        Returns:
            WeatherData object
        
        Raises:
            WeatherFetchError: If fetching fails and no fallback available
        """
        if not self.api_key:
            raise WeatherFetchError(
                "No API key provided. Set OPENWEATHERMAP_API_KEY environment variable "
                "or pass api_key to WeatherFetcher."
            )
        
        # Check cache first
        if use_cache:
            cached = self._get_cached(location)
            if cached:
                return WeatherData.from_api_response(cached, location)
        
        if not AIOHTTP_AVAILABLE:
            # Fall back to synchronous fetch in async context
            return await asyncio.get_event_loop().run_in_executor(
                None, lambda: self.fetch(location, use_cache=False)
            )
        
        try:
            async with aiohttp.ClientSession() as session:
                async with session.get(
                    self._build_url(location), 
                    timeout=aiohttp.ClientTimeout(total=10)
                ) as response:
                    response.raise_for_status()
                    data = await response.json()
                    
                    # Update cache
                    self._update_cache(location, data)
                    
                    return WeatherData.from_api_response(data, location)
        
        except Exception as e:
            # Try to return cached data as fallback
            cached = self._cache.get(location.lower(), {}).get('data')
            if cached:
                return WeatherData.from_api_response(cached, location)
            
            raise WeatherFetchError(f"Failed to fetch weather data: {e}")
    
    def get_offline_fallback(self, location: str) -> Optional[WeatherData]:
        """Get cached data regardless of age for offline mode."""
        cached = self._cache.get(location.lower(), {}).get('data')
        if cached:
            return WeatherData.from_api_response(cached, location)
        return None
    
    def clear_cache(self) -> None:
        """Clear all cached weather data."""
        self._cache = {}
        try:
            if self.CACHE_FILE.exists():
                self.CACHE_FILE.unlink()
        except Exception:
            pass


# Demo weather data for offline/demo mode
DEMO_WEATHER = {
    'rain': WeatherData(
        condition='Rain',
        description='moderate rain',
        temperature=18.0,
        humidity=85,
        wind_speed=5.5,
        wind_direction=180,
        visibility=5000,
        clouds=90,
        is_night=False,
        location='Demo',
        timestamp=datetime.utcnow(),
        icon='10d',
        raw_data={}
    ),
    'snow': WeatherData(
        condition='Snow',
        description='light snow',
        temperature=-2.0,
        humidity=90,
        wind_speed=3.0,
        wind_direction=45,
        visibility=3000,
        clouds=100,
        is_night=False,
        location='Demo',
        timestamp=datetime.utcnow(),
        icon='13d',
        raw_data={}
    ),
    'storm': WeatherData(
        condition='Thunderstorm',
        description='thunderstorm with heavy rain',
        temperature=22.0,
        humidity=95,
        wind_speed=15.0,
        wind_direction=270,
        visibility=2000,
        clouds=100,
        is_night=False,
        location='Demo',
        timestamp=datetime.utcnow(),
        icon='11d',
        raw_data={}
    ),
    'fog': WeatherData(
        condition='Fog',
        description='dense fog',
        temperature=10.0,
        humidity=100,
        wind_speed=1.0,
        wind_direction=0,
        visibility=200,
        clouds=100,
        is_night=False,
        location='Demo',
        timestamp=datetime.utcnow(),
        icon='50d',
        raw_data={}
    ),
    'clear': WeatherData(
        condition='Clear',
        description='clear sky',
        temperature=25.0,
        humidity=40,
        wind_speed=2.0,
        wind_direction=90,
        visibility=10000,
        clouds=5,
        is_night=False,
        location='Demo',
        timestamp=datetime.utcnow(),
        icon='01d',
        raw_data={}
    ),
}


def get_demo_weather(condition: str) -> WeatherData:
    """Get demo weather data for a specific condition."""
    return DEMO_WEATHER.get(condition.lower(), DEMO_WEATHER['clear'])
