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 get_climate_data():
    """
    Downloads historical climate data for India from NASA Power API.

    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 and Precipitation
        start_year = 1990
        end_year = 2023

        # Function to fetch data for a specific region
        def fetch_region_data(region, states):
            all_states_data = []
            for state in states:
                # 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)
                }

                lat, lon = state_coords.get(state, (20.0, 80.0))  # Default coords if not found
                api_url = (
                    f"https://power.larc.nasa.gov/api/temporal/monthly/point?"
                    f"parameters={parameters}&community=AG&longitude={lon}&latitude={lat}"
                    f"&start={start_year}01&end={end_year}12&format=CSV"
                )

                print(f"Fetching data for {state} (Lat: {lat}, Lon: {lon})")
        df['Temp_3m_lag'] = df.groupby('Region')['Temperature'].shift(3)
        df['Temp_6m_lag'] = df.groupby('Region')['Temperature'].shift(6)
        df['Temp_12m_lag'] = df.groupby('Region')['Temperature'].shift(12)

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

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

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

        # Add trend feature
        df['Years_from_start'] = df.groupby('Region')['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
        df = df.dropna()

        return df

    def prepare_future_features(last_data, months_to_predict):
        """Prepare feature set for future predictions per region."""
        future_dates = pd.date_range(
            start=last_data.index.max() + pd.DateOffset(months=1),
            periods=months_to_predict,
            freq='M'
        )

        future_df = pd.DataFrame(index=future_dates)
        future_df['Year'] = future_df.index.year
        future_df['Month'] = future_df.index.month
        future_df['Region'] = last_data['Region'].unique()[0]  # Assuming predicting per region
        future_df['Season'] = pd.cut(future_df['Month'],
                                     bins=[0, 2, 5, 8, 11, 12],
                                     labels=['Winter', 'Spring', 'Summer', 'Autumn', 'Winter'])
        future_df['Years_from_start'] = future_df['Year'] - last_data[last_data['Region'] == future_df['Region']].Year.min()

        # Initialize lists to store lagged and rolling mean features
        temp_lags = ['Temp_1m_lag', 'Temp_3m_lag', 'Temp_6m_lag', 'Temp_12m_lag']
        prec_lags = ['Prec_1m_lag', 'Prec_3m_lag', 'Prec_6m_lag', 'Prec_12m_lag']
        temp_means = ['Temp_3m_mean', 'Temp_6m_mean', 'Temp_12m_mean']
        prec_means = ['Prec_3m_mean', 'Prec_6m_mean', 'Prec_12m_mean']

        # Fetch the last known data per feature
        region = future_df['Region'].iloc[0]
        region_data = last_data[last_data['Region'] == region].copy()

        for lag in temp_lags:
            period = int(lag.split('_')[1][:-1])
            future_df[lag] = region_data['Temperature'].shift(period).tail(period).mean()

        for lag in prec_lags:
            period = int(lag.split('_')[1][:-1])
            future_df[lag] = region_data['Precipitation'].shift(period).tail(period).mean()

        for mean in temp_means:
            window = int(mean.split('_')[1][:-1])
            future_df[mean] = region_data['Temperature'].rolling(window=window).mean().iloc[-1]

        for mean in prec_means:
            window = int(mean.split('_')[1][:-1])
            future_df[mean] = region_data['Precipitation'].rolling(window=window).mean().iloc[-1]

        # Drop rows with any remaining NaN
        future_df = future_df.dropna()

        return future_df

    def train_model(df_region):
        """Train the prediction model for a specific region."""
        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 and Precipitation."""
        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 on 5-year and 30-year scales."""
        fig, axes = plt.subplots(2, 1, figsize=(15, 14))

        # 5-year scale
        recent_year = historical_df.index.year.max()
        five_years_ago = recent_year - 5
        mask_5yr = historical_df.index.year >= five_years_ago
        axes[0].plot(historical_df.index[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.index.year.min()
        end_year = predictions_df.index.year.max()
        axes[1].plot(historical_df.index, 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 main():
        # Get historical data
        print("Downloading climate data...")
        df = get_climate_data()

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

        # Prepare features
        print("Preparing features...")
        df_featured = create_features(df)

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

        # Initialize dictionaries to store performance metrics
        performance_metrics = {}

        # Train model for each region
        regions = df_featured['Region'].unique()
        for region in regions:
            print(f"\nTraining model for {region} region...")
            df_region = df_featured[df_featured['Region'] == region]
            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_featured[df_featured['Region'] == region]
            last_data = df_region.copy()

            # Make future predictions for Temperature
            future_features_t = prepare_future_features(last_data, months_to_predict)
            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']
            features_t = model_info_t['features']
            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)
            future_features_p = future_features_p.rename(columns={
                'Temp_1m_lag': 'Prec_1m_lag',
                'Temp_3m_lag': 'Prec_3m_lag',
                'Temp_6m_lag': 'Prec_6m_lag',
                'Temp_12m_lag': 'Prec_12m_lag',
                'Temp_3m_mean': 'Prec_3m_mean',
                'Temp_6m_mean': 'Prec_6m_mean',
                'Temp_12m_mean': 'Prec_12m_mean'
            })
            model_info_p = models_dict[region]['Precipitation']
            scaler_p = model_info_p['scaler']
            features_p = model_info_p['features']
            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.index)

            # 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}.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()
    

Why does this code have this error:
  File "<ipython-input-14-ca1f04afccf6>", line 110
    def prepare_future_features(last_data, months_to_predict):
    ^
SyntaxError: expected 'except' or 'finally' block