import os
import requests
import json
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Tuple
import logging
from dotenv import load_dotenv
import pandas as pd
import re

load_dotenv()

logger = logging.getLogger(__name__)

class APIDataFetcher:
    def __init__(self, base_url: str, timeout: int = 30, retry_attempts: int = 3):
        self.base_url = base_url.rstrip('/')
        self.api_key = os.getenv('API_KEY')
        self.timeout = timeout
        self.retry_attempts = retry_attempts
        
        if not self.api_key:
            raise ValueError("API_KEY not found in .env file")
        
        self.session = requests.Session()
        self.session.headers.update({
            'Content-Type': 'application/json',
            'x-api-key': self.api_key
        })
    
    def _make_request(self, endpoint: str, params: Dict) -> Dict:
        """Make API request with retry logic."""
        url = f"{self.base_url}{endpoint}"
        
        # API key is already in headers (x-api-key)
        for attempt in range(self.retry_attempts):
            try:
                response = self.session.get(url, params=params, timeout=self.timeout)
                response.raise_for_status()
                return response.json()
            except requests.exceptions.Timeout:
                logger.warning(f"API request timeout (attempt {attempt + 1}/{self.retry_attempts})")
                if attempt == self.retry_attempts - 1:
                    raise
            except requests.exceptions.RequestException as e:
                logger.error(f"API request failed: {e}")
                if attempt == self.retry_attempts - 1:
                    raise
            except json.JSONDecodeError as e:
                logger.error(f"Failed to parse API response: {e}")
                raise
        
        raise Exception("API request failed after all retries")
    
    def fetch_lightning_data(
        self, 
        center_lat: float, 
        center_lng: float, 
        radius_km: float,
        start_date: str,
        end_date: str
    ) -> List[Dict]:
        """
        Fetch lightning data from API.
        
        Args:
            center_lat: Center latitude
            center_lng: Center longitude
            radius_km: Radius in kilometers (converted to meters for API)
            start_date: Start date in YYYY-MM-DD format
            end_date: End date in YYYY-MM-DD format
        
        Returns:
            List of lightning records
        """
        endpoint = "/lightning-data/historical/"
        
        radius_m = int(radius_km * 1000)
        
        params = {
            'queryType': 'circle',
            'centerLongitude': center_lng,
            'centerLatitude': center_lat,
            'radius': radius_m,
            'startDate': start_date,
            'endDate': end_date
        }
        
        logger.info(f"Fetching lightning data: center=({center_lat}, {center_lng}), radius={radius_km}km, dates={start_date} to {end_date}")
        
        try:
            data = self._make_request(endpoint, params)
            
            if isinstance(data, dict) and 'data' in data:
                records = data['data']
            elif isinstance(data, list):
                records = data
            else:
                records = []
            
            logger.info(f"Fetched {len(records)} lightning records")
            return records
            
        except Exception as e:
            logger.error(f"Failed to fetch lightning data: {e}")
            raise
    
    def fetch_storm_data(
        self,
        center_lat: float,
        center_lng: float,
        radius_km: float,
        start_date: str,
        end_date: str
    ) -> List[Dict]:
        """
        Fetch storm data from API.
        
        Args:
            center_lat: Center latitude
            center_lng: Center longitude
            radius_km: Radius in kilometers (converted to meters for API)
            start_date: Start date in YYYY-MM-DD format
            end_date: End date in YYYY-MM-DD format
        
        Returns:
            List of storm records
        """
        endpoint = "/storm-data/historical/"
        
        radius_m = int(radius_km * 1000)
        
        params = {
            'queryType': 'circle',
            'centerLongitude': center_lng,
            'centerLatitude': center_lat,
            'radius': radius_m,
            'startDate': start_date,
            'endDate': end_date
        }
        
        logger.info(f"Fetching storm data: center=({center_lat}, {center_lng}), radius={radius_km}km, dates={start_date} to {end_date}")
        
        try:
            data = self._make_request(endpoint, params)
            
            if isinstance(data, dict) and 'data' in data:
                records = data['data']
            elif isinstance(data, list):
                records = data
            else:
                records = []
            
            logger.info(f"Fetched {len(records)} storm records")
            return records
            
        except Exception as e:
            logger.error(f"Failed to fetch storm data: {e}")
            raise
    
    def calculate_location_bounds(
        self,
        turbine_df: pd.DataFrame,
        max_distance_ring_m: int,
        padding_km: float = 5
    ) -> Dict[str, float]:
        """
        Auto-calculate center + radius from turbine coordinates.
        
        Args:
            turbine_df: DataFrame with 'lat' and 'lng' columns
            max_distance_ring_m: Maximum distance ring in meters
            padding_km: Additional padding in kilometers
        
        Returns:
            Dict with center_lat, center_lng, radius_km
        """
        from src.analysis.geospatial import haversine_distance
        
        centroid_lat = turbine_df['lat'].mean()
        centroid_lng = turbine_df['lng'].mean()
        
        max_distance_from_centroid = 0
        for _, turbine in turbine_df.iterrows():
            distance = haversine_distance(
                centroid_lat, centroid_lng,
                turbine['lat'], turbine['lng']
            )
            max_distance_from_centroid = max(max_distance_from_centroid, distance)
        
        radius_km = (max_distance_from_centroid / 1000) + \
                    (max_distance_ring_m / 1000) + \
                    padding_km
        
        return {
            "center_lat": centroid_lat,
            "center_lng": centroid_lng,
            "radius_km": radius_km
        }
    
    @staticmethod
    def determine_query_date_range(
        farm_config: Dict,
        api_config: Dict
    ) -> Tuple[datetime, datetime]:
        """
        Determine date range for API query.
        
        Args:
            farm_config: Farm configuration dict
            api_config: API configuration dict
        
        Returns:
            Tuple of (start_date, end_date) as datetime objects
        """
        date_config = farm_config['api_params']['date_range']
        
        def _parse_config_date(value: str) -> datetime:
            value_str = str(value).strip()
            if not value_str:
                raise ValueError("Empty date value")
            
            if re.fullmatch(r"\d{2}-\d{2}-\d{4}", value_str):
                return datetime.strptime(value_str, '%d-%m-%Y')
            
            ts = pd.to_datetime(value_str, errors='raise')
            if isinstance(ts, pd.Timestamp):
                if ts.tzinfo is not None:
                    ts = ts.tz_convert('UTC').tz_localize(None)
                return ts.to_pydatetime()
            
            raise ValueError(f"Unsupported date value: {value_str}")
        
        if date_config['method'] == 'manual':
            start_date = _parse_config_date(date_config['start_date'])
            end_date = _parse_config_date(date_config['end_date'])
            return start_date, end_date
        
        today = datetime.now()
        query_range = date_config.get('query_range', {})
        method = query_range.get('method', api_config.get('default_query_range', {}).get('method', 'current_month'))
        
        if method == 'current_month':
            start_date = today.replace(day=1)
            end_date = today
        
        elif method == 'last_month':
            if today.month == 1:
                start_date = today.replace(year=today.year-1, month=12, day=1)
            else:
                start_date = today.replace(month=today.month-1, day=1)
            
            if today.month == 1:
                last_day = (today.replace(year=today.year-1, month=12, day=28) + 
                           timedelta(days=4)).replace(day=1) - timedelta(days=1)
            else:
                last_day = (today.replace(month=today.month, day=28) + 
                           timedelta(days=4)).replace(day=1) - timedelta(days=1)
            end_date = last_day
        
        elif method == 'days_back':
            days = query_range.get('days', 30)
            start_date = today - timedelta(days=days)
            end_date = today
        
        elif method == 'custom':
            start_date = _parse_config_date(query_range['start_date'])
            end_date = _parse_config_date(query_range['end_date'])
        
        else:
            start_date = today.replace(day=1)
            end_date = today
        
        return start_date, end_date

