import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error, r2_score
import matplotlib.pyplot as plt
import seaborn as sns
import requests
from io import StringIO
from datetime import datetime, timedelta

# Define regions and their corresponding states
REGIONS = {
    'North': ['Jammu and Kashmir', 'Himachal Pradesh', 'Punjab', 'Uttarakhand', 'Haryana', 'Delhi'],
    'West': ['Rajasthan', 'Gujarat', 'Maharashtra', 'Goa'],
    'South': ['Karnataka', 'Kerala', 'Tamil Nadu', 'Andhra Pradesh', 'Telangana', 'Puducherry'],
    'East': ['West Bengal', 'Odisha', 'Bihar', 'Jharkhand', 'Assam', 'Tripura', 'Mizoram', 'Nagaland'],
    'Central': ['Madhya Pradesh', 'Chhattisgarh', 'Uttar Pradesh']
}

def assign_season(month):
    """Assign season based on month."""
    if month in [12, 1, 2]:
        return 'Winter'
    elif month in [3, 4, 5]:
        return 'Spring'
    elif month in [6, 7, 8]:
        return 'Summer'
    else:
        return 'Autumn'

def fetch_state_data(state, lat, lon, parameters, start_date, end_date):
    """
    Fetch climate data for a specific state from the NASA Power API.
    
    Args:
        state (str): Name of the state.
        lat (float): Latitude of the state's centroid.
        lon (float): Longitude of the state's centroid.
        parameters (str): Climate parameters to fetch.
        start_date (str): Start date in YYYYMM format.
        end_date (str): End date in YYYYMM format.
    
    Returns:
        DataFrame: Climate data for the state or empty DataFrame if failed.
    """
    api_url = (
        f"https://power.larc.nasa.gov/api/temporal/monthly/point?"
        f"parameters={parameters}&community=AG&longitude={lon}&latitude={lat}"
        f"&start={start_date}&end={end_date}&format=CSV"
    )
    print(f"Fetching data for {state} (Lat: {lat}, Lon: {lon})")
    try:
        response = requests.get(api_url)
        response.raise_for_status()  # Raises HTTPError for bad responses
        csv_content = response.text
        csv_lines = csv_content.splitlines()
        
        # The first 2 lines are typically headers/metadata
        data_start = 2
        if len(csv_lines) > data_start:
            csv_data = '\n'.join(csv_lines[data_start:])
            state_data = pd.read_csv(StringIO(csv_data))
            if not state_data.empty:
                state_data['State'] = state
                return state_data
            else:
                print(f"No data found for {state}.")
                return pd.DataFrame()
        else:
            print(f"No data found for {state}.")
            return pd.DataFrame()
    except requests.exceptions.HTTPError as http_err:
        print(f"HTTP error occurred for {state}: {http_err} - Status Code: {response.status_code}")
        return pd.DataFrame()
    except Exception as err:
        print(f"Other error occurred for {state}: {err}")
        return pd.DataFrame()

def get_climate_data():
    """
    Downloads historical climate data for India from the NASA Power API.
    Combines data from all regions and states into a single DataFrame.

    Returns:
        DataFrame: Combined climate data for all regions.
    """
    try:
        print("Fetching data from NASA Power API...")
        # Define parameters for the API
        parameters = "T2M,PRECTOT"  # Temperature (2 meters) and Precipitation

        # Dynamically set the start and end dates
        start_year = 1990
        current_date = datetime.now()
        end_year = current_date.year
        end_month = current_date.month

        # Format start and end dates as YYYYMM
        start_date = f"{start_year}01"
        end_date = f"{end_year}{end_month:02d}"

        all_regions_data = []

        # Approximate centroid coordinates for each state
        state_coords = {
            'Jammu and Kashmir': (34.0, 74.8),
            'Himachal Pradesh': (31.1, 77.2),
            'Punjab': (31.0, 75.0),
            'Uttarakhand': (30.4, 78.0),
            'Haryana': (29.0, 76.0),
            'Delhi': (28.6, 77.2),
            'Rajasthan': (27.0, 75.0),
            'Gujarat': (22.3, 72.6),
            'Maharashtra': (19.0, 75.0),
            'Goa': (15.3, 73.9),
            'Karnataka': (15.4, 75.0),
            'Kerala': (10.5, 76.2),
            'Tamil Nadu': (11.0, 78.0),
            'Andhra Pradesh': (15.9, 79.0),
            'Telangana': (17.5, 78.5),
            'Puducherry': (11.9, 79.8),
            'West Bengal': (22.7, 88.4),
            'Odisha': (20.6, 85.1),
            'Bihar': (25.3, 85.0),
            'Jharkhand': (23.8, 86.4),
            'Assam': (26.2, 91.7),
            'Tripura': (23.8, 91.3),
            'Mizoram': (23.7, 92.7),
            'Nagaland': (25.7, 94.1),
            'Madhya Pradesh': (23.3, 77.4),
            'Chhattisgarh': (21.0, 81.0),
            'Uttar Pradesh': (26.8, 80.9)
        }

        # Iterate over each region and fetch data for its states
        for region, states in REGIONS.items():
            print(f"\nProcessing region: {region}")
            for state in states:
                lat, lon = state_coords.get(state, (20.0, 80.0))  # Default coords if not found
                state_data = fetch_state_data(state, lat, lon, parameters, start_date, end_date)
                if not state_data.empty:
                    state_data['Region'] = region
                    all_regions_data.append(state_data)

        if not all_regions_data:
            print("No data fetched. Exiting.")
            return pd.DataFrame()

        # Combine all regions into a single DataFrame
        df = pd.concat(all_regions_data, ignore_index=True)

        # Rename columns for clarity (adjust based on actual CSV structure)
        # Typically, the first column is 'YYYYMM', followed by variables like 'T2M', 'PRECTOT'
        if 'YYYYMM' in df.columns:
            df.rename(columns={'YYYYMM': 'YearMonth', 'T2M': 'Temperature', 'PRECTOT': 'Precipitation'}, inplace=True)
        else:
            print("Expected 'YYYYMM' column not found in the data.")
            return pd.DataFrame()

        # Convert 'YearMonth' to datetime
        df['YearMonth'] = pd.to_datetime(df['YearMonth'], format='%Y%m')

        # Extract Year and Month
        df['Year'] = df['YearMonth'].dt.year
        df['Month'] = df['YearMonth'].dt.month

        # Assign Seasons
        df['Season'] = df['Month'].apply(assign_season)

        # Reset index to ensure proper ordering
        df = df.sort_values(['Region', 'State', 'Year', 'Month']).reset_index(drop=True)

        # Feature Engineering
        # Create lagged features for Temperature
        df['Temp_1m_lag'] = df.groupby(['Region', 'State'])['Temperature'].shift(1)
        df['Temp_3m_lag'] = df.groupby(['Region', 'State'])['Temperature'].shift(3)
        df['Temp_6m_lag'] = df.groupby(['Region', 'State'])['Temperature'].shift(6)
        df['Temp_12m_lag'] = df.groupby(['Region', 'State'])['Temperature'].shift(12)

        # Create lagged features for Precipitation
        df['Prec_1m_lag'] = df.groupby(['Region', 'State'])['Precipitation'].shift(1)
        df['Prec_3m_lag'] = df.groupby(['Region', 'State'])['Precipitation'].shift(3)
        df['Prec_6m_lag'] = df.groupby(['Region', 'State'])['Precipitation'].shift(6)
        df['Prec_12m_lag'] = df.groupby(['Region', 'State'])['Precipitation'].shift(12)

        # Create rolling mean features for Temperature
        df['Temp_3m_mean'] = df.groupby(['Region', 'State'])['Temperature'].rolling(window=3).mean().reset_index(0, drop=True)
        df['Temp_6m_mean'] = df.groupby(['Region', 'State'])['Temperature'].rolling(window=6).mean().reset_index(0, drop=True)
        df['Temp_12m_mean'] = df.groupby(['Region', 'State'])['Temperature'].rolling(window=12).mean().reset_index(0, drop=True)

        # Create rolling mean features for Precipitation
        df['Prec_3m_mean'] = df.groupby(['Region', 'State'])['Precipitation'].rolling(window=3).mean().reset_index(0, drop=True)
        df['Prec_6m_mean'] = df.groupby(['Region', 'State'])['Precipitation'].rolling(window=6).mean().reset_index(0, drop=True)
        df['Prec_12m_mean'] = df.groupby(['Region', 'State'])['Precipitation'].rolling(window=12).mean().reset_index(0, drop=True)

        # Add trend feature
        df['Years_from_start'] = df.groupby(['Region', 'State'])['Year'].transform(lambda x: x - x.min())

        # Season-Year interaction (optional)
        df['Season_Year'] = df['Season'].astype(str) + '_' + df['Year'].astype(str)

        # Drop rows with NaN values resulting from lagging and rolling
        df = df.dropna().reset_index(drop=True)

        print("Climate data fetched and processed successfully.")
        return df

def prepare_future_features(last_data, months_to_predict, region, parameter='Temperature'):
    """
    Prepare feature set for future predictions per region.

    Args:
        last_data (DataFrame): The last known data for the region.
        months_to_predict (int): Number of future months to predict.
        region (str): The region for which predictions are made.
        parameter (str): 'Temperature' or 'Precipitation'.

    Returns:
        DataFrame: Future features ready for prediction.
    """
    # Generate future dates
    last_date = last_data['YearMonth'].max()
    future_dates = pd.date_range(start=last_date + pd.DateOffset(months=1), periods=months_to_predict, freq='M')

    future_df = pd.DataFrame({'YearMonth': future_dates})
    future_df['Year'] = future_df['YearMonth'].dt.year
    future_df['Month'] = future_df['YearMonth'].dt.month
    future_df['Region'] = region
    future_df['Season'] = future_df['Month'].apply(assign_season)
    future_df['Years_from_start'] = future_df['Year'] - last_data['Year'].min()

    # Initialize lagged and rolling mean features based on parameter
    if parameter == 'Temperature':
        lags = ['Temp_1m_lag', 'Temp_3m_lag', 'Temp_6m_lag', 'Temp_12m_lag']
        means = ['Temp_3m_mean', 'Temp_6m_mean', 'Temp_12m_mean']
    else:
        lags = ['Prec_1m_lag', 'Prec_3m_lag', 'Prec_6m_lag', 'Prec_12m_lag']
        means = ['Prec_3m_mean', 'Prec_6m_mean', 'Prec_12m_mean']

    # Fetch the last known data for lagged features
    for lag in lags:
        lag_value = last_data[lag].iloc[-1]
        future_df[lag] = lag_value  # Simple approach; for more accuracy, iterative prediction is needed

    for mean in means:
        mean_value = last_data[mean].iloc[-1]
        future_df[mean] = mean_value

    return future_df

def train_model(df_region):
    """
    Train the prediction models for Temperature and Precipitation for a specific region.

    Args:
        df_region (DataFrame): The climate data for the region.

    Returns:
        dict: Contains models, scalers, features, and test datasets for Temperature and Precipitation.
    """
    features = ['Year', 'Month', 'Years_from_start',
                'Temp_1m_lag', 'Temp_3m_lag', 'Temp_6m_lag', 'Temp_12m_lag',
                'Prec_1m_lag', 'Prec_3m_lag', 'Prec_6m_lag', 'Prec_12m_lag',
                'Temp_3m_mean', 'Temp_6m_mean', 'Temp_12m_mean',
                'Prec_3m_mean', 'Prec_6m_mean', 'Prec_12m_mean']

    X = df_region[features]
    y_temp = df_region['Temperature']
    y_prec = df_region['Precipitation']

    # Split the data for Temperature
    X_train_t, X_test_t, y_train_t, y_test_t = train_test_split(X, y_temp, test_size=0.2, random_state=42)

    # Split the data for Precipitation
    X_train_p, X_test_p, y_train_p, y_test_p = train_test_split(X, y_prec, test_size=0.2, random_state=42)

    # Scale the features
    scaler_t = StandardScaler()
    scaler_p = StandardScaler()
    X_train_t_scaled = scaler_t.fit_transform(X_train_t)
    X_test_t_scaled = scaler_t.transform(X_test_t)

    X_train_p_scaled = scaler_p.fit_transform(X_train_p)
    X_test_p_scaled = scaler_p.transform(X_test_p)

    # Train the model for Temperature
    model_t = RandomForestRegressor(n_estimators=100, random_state=42)
    model_t.fit(X_train_t_scaled, y_train_t)

    # Train the model for Precipitation
    model_p = RandomForestRegressor(n_estimators=100, random_state=42)
    model_p.fit(X_train_p_scaled, y_train_p)

    # Make predictions on test set
    y_pred_t = model_t.predict(X_test_t_scaled)
    y_pred_p = model_p.predict(X_test_p_scaled)

    return {
        'Temperature': {
            'model': model_t,
            'scaler': scaler_t,
            'features': features,
            'X_test': X_test_t,
            'y_test': y_test_t,
            'y_pred': y_pred_t
        },
        'Precipitation': {
            'model': model_p,
            'scaler': scaler_p,
            'features': features,
            'X_test': X_test_p,
            'y_test': y_test_p,
            'y_pred': y_pred_p
        }
    }

def make_future_predictions(model_info, scaler, future_features):
    """
    Make predictions for future months for Temperature or Precipitation.

    Args:
        model_info (dict): Contains model, scaler, and feature information.
        scaler (StandardScaler): Scaler used to transform the features.
        future_features (DataFrame): Features for future predictions.

    Returns:
        ndarray: Predicted values.
    """
    X_future = future_features[model_info['features']]
    X_future_scaled = scaler.transform(X_future)
    predictions = model_info['model'].predict(X_future_scaled)
    return predictions

def plot_time_scales(historical_df, predictions_df, region, parameter):
    """
    Plot historical and predicted data on 5-year and 30-year scales.

    Args:
        historical_df (DataFrame): Historical climate data.
        predictions_df (DataFrame): Predicted climate data.
        region (str): The region name.
        parameter (str): 'Temperature' or 'Precipitation'.
    """
    fig, axes = plt.subplots(2, 1, figsize=(15, 14))

    # 5-year scale
    recent_year = historical_df['Year'].max()
    five_years_ago = recent_year - 5
    mask_5yr = historical_df['Year'] >= five_years_ago
    axes[0].plot(historical_df['YearMonth'][mask_5yr], historical_df.loc[mask_5yr, parameter],
                label='Historical', color='blue', alpha=0.6)
    axes[0].plot(predictions_df.index, predictions_df[f'Predicted_{parameter}'],
                label='Predicted', color='red', linestyle='--')
    # Add confidence interval for predictions (assuming +/- 10% for demonstration)
    axes[0].fill_between(predictions_df.index,
                         predictions_df[f'Predicted_{parameter}'] * 0.9,
                         predictions_df[f'Predicted_{parameter}'] * 1.1,
                         color='red', alpha=0.2)
    axes[0].set_title(f'{parameter} for {region} Region - Last 5 Years and Predictions')
    axes[0].set_xlabel('Year')
    axes[0].set_ylabel(parameter)
    axes[0].legend()
    axes[0].grid(True)

    # 30-year scale with indices every 10 years
    start_year = historical_df['Year'].min()
    end_year = predictions_df.index.year.max()
    axes[1].plot(historical_df['YearMonth'], historical_df[parameter],
                label='Historical', color='blue', alpha=0.6)
    axes[1].plot(predictions_df.index, predictions_df[f'Predicted_{parameter}'],
                label='Predicted', color='red', linestyle='--')
    # Add confidence interval for predictions (assuming +/- 10% for demonstration)
    axes[1].fill_between(predictions_df.index,
                         predictions_df[f'Predicted_{parameter}'] * 0.9,
                         predictions_df[f'Predicted_{parameter}'] * 1.1,
                         color='red', alpha=0.2)
    axes[1].set_title(f'{parameter} for {region} Region - 30 Years with Predictions')
    axes[1].set_xlabel('Year')
    axes[1].set_ylabel(parameter)
    axes[1].legend()
    axes[1].grid(True)

    # Set x-axis major ticks every 10 years for the 30-year plot
    years = pd.date_range(start=f'{start_year}-01-01', end=f'{end_year}-12-31', freq='10YS')
    axes[1].set_xticks(years)
    axes[1].set_xticklabels([year.year for year in years])

    plt.tight_layout()
    plt.show()

def test_single_api_call():
    """
    Test fetching data for a single state to verify API parameters and availability.
    """
    test_state = 'Rajasthan'
    lat, lon = (27.0, 75.0)  # Rajasthan's approximate centroid

    print(f"\nTesting API call for {test_state} (Lat: {lat}, Lon: {lon})")
    try:
        parameters = "T2M,PRECTOT"
        start_year = 1990
        current_date = datetime.now()
        end_year = current_date.year
        end_month = current_date.month
        start_date = f"{start_year}01"
        end_date = f"{end_year}{end_month:02d}"

        api_url = (
            f"https://power.larc.nasa.gov/api/temporal/monthly/point?"
            f"parameters={parameters}&community=AG&longitude={lon}&latitude={lat}"
            f"&start={start_date}&end={end_date}&format=CSV"
        )
        response = requests.get(api_url)
        response.raise_for_status()
        csv_content = response.text
        csv_lines = csv_content.splitlines()
        data_start = 2  # Assuming first 2 lines are headers
        if len(csv_lines) > data_start:
            csv_data = '\n'.join(csv_lines[data_start:])
            state_data = pd.read_csv(StringIO(csv_data))
            if not state_data.empty:
                state_data['State'] = test_state
                print(f"Data fetched successfully for {test_state}:")
                print(state_data.head())
            else:
                print(f"No data available for {test_state}.")
        else:
            print(f"No data found for {test_state}.")
    except requests.exceptions.HTTPError as http_err:
        print(f"HTTP error occurred: {http_err} - Status Code: {response.status_code}")
    except Exception as err:
        print(f"Other error occurred: {err}")

def main():
    # First, test a single API call to verify parameters
    test_single_api_call()

    # Proceed only if the test is successful
    proceed = input("\nDo you want to proceed with fetching data for all regions? (yes/no): ").strip().lower()
    if proceed != 'yes':
        print("Exiting the program.")
        return

    # Get historical data
    print("\nDownloading climate data...")
    df = get_climate_data()

    if df.empty:
        print("Failed to download or retrieve data. Please check your data source and internet connection.")
        return

    # Initialize dictionaries to store models and scalers per region
    models_dict = {}
    performance_metrics = {}

    # Train model for each region
    regions = df['Region'].unique()
    for region in regions:
        print(f"\nTraining model for {region} region...")
        df_region = df[df['Region'] == region].reset_index(drop=True)
        models = train_model(df_region)
        models_dict[region] = models
        performance_metrics[region] = {}

        # Calculate and print model performance metrics for Temperature
        mse_t = mean_squared_error(models['Temperature']['y_test'], models['Temperature']['y_pred'])
        r2_t = r2_score(models['Temperature']['y_test'], models['Temperature']['y_pred'])
        print(f"Temperature Model - Mean Squared Error: {mse_t:.4f}, R² Score: {r2_t:.4f}")

        # Calculate and print model performance metrics for Precipitation
        mse_p = mean_squared_error(models['Precipitation']['y_test'], models['Precipitation']['y_pred'])
        r2_p = r2_score(models['Precipitation']['y_test'], models['Precipitation']['y_pred'])
        print(f"Precipitation Model - Mean Squared Error: {mse_p:.4f}, R² Score: {r2_p:.4f}")

        performance_metrics[region]['Temperature'] = {'MSE': mse_t, 'R2': r2_t}
        performance_metrics[region]['Precipitation'] = {'MSE': mse_p, 'R2': r2_p}

    # Make future predictions (e.g., for next 10 years = 120 months)
    months_to_predict = 120
    for region in regions:
        print(f"\nMaking predictions for {region} region...")
        df_region = df[df['Region'] == region].reset_index(drop=True)
        last_data = df_region.copy()

        # Make future predictions for Temperature
        future_features_t = prepare_future_features(last_data, months_to_predict, region, parameter='Temperature')
        if future_features_t.empty:
            print(f"No future features prepared for {region} region.")
            continue
        model_info_t = models_dict[region]['Temperature']
        scaler_t = model_info_t['scaler']
        predictions_t = make_future_predictions(model_info_t, scaler_t, future_features_t)

        # Make future predictions for Precipitation
        future_features_p = prepare_future_features(last_data, months_to_predict, region, parameter='Precipitation')
        if future_features_p.empty:
            print(f"No future features prepared for {region} region.")
            continue
        model_info_p = models_dict[region]['Precipitation']
        scaler_p = model_info_p['scaler']
        predictions_p = make_future_predictions(model_info_p, scaler_p, future_features_p)

        # Create prediction DataFrame
        predictions_df = pd.DataFrame({
            'Predicted_Temperature': predictions_t,
            'Predicted_Precipitation': predictions_p
        }, index=future_features_t['YearMonth'])

        # Plot historical and predictions for Temperature
        plot_time_scales(df_region, predictions_df, region, 'Temperature')

        # Plot historical and predictions for Precipitation
        plot_time_scales(df_region, predictions_df, region, 'Precipitation')

        # Print some prediction statistics for Temperature
        print("\nTemperature Prediction Summary:")
        print(f"Average predicted temperature: {predictions_df['Predicted_Temperature'].mean():.2f}°C")
        print(f"Maximum predicted temperature: {predictions_df['Predicted_Temperature'].max():.2f}°C")
        print(f"Minimum predicted temperature: {predictions_df['Predicted_Temperature'].min():.2f}°C")

        # Print some prediction statistics for Precipitation
        print("\nPrecipitation Prediction Summary:")
        print(f"Average predicted precipitation: {predictions_df['Predicted_Precipitation'].mean():.2f} mm")
        print(f"Maximum predicted precipitation: {predictions_df['Predicted_Precipitation'].max():.2f} mm")
        print(f"Minimum predicted precipitation: {predictions_df['Predicted_Precipitation'].min():.2f} mm")

        # Save predictions to CSV
        filename = f'climate_predictions_{region.replace(" ", "_")}.csv'
        predictions_df.to_csv(filename)
        print(f"\nPredictions saved to '{filename}'")

    # Optionally, save performance metrics
    metrics_records = []
    for region, metrics in performance_metrics.items():
        temp_metrics = metrics['Temperature']
        prec_metrics = metrics['Precipitation']
        metrics_records.append({
            'Region': region,
            'Parameter': 'Temperature',
            'Mean Squared Error': temp_metrics['MSE'],
            'R2 Score': temp_metrics['R2']
        })
        metrics_records.append({
            'Region': region,
            'Parameter': 'Precipitation',
            'Mean Squared Error': prec_metrics['MSE'],
            'R2 Score': prec_metrics['R2']
        })

    metrics_df = pd.DataFrame(metrics_records)
    metrics_df.to_csv('model_performance_metrics.csv', index=False)
    print("\nModel performance metrics saved to 'model_performance_metrics.csv'")

if __name__ == "__main__":
    main()

Fix all errors you can find