# -*- coding: utf-8 -*-
"""
/***************************************************************************
 ClimaPlots Dialog
                                 A QGIS plugin
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2024-10-24
        git sha              : $Format:%H$
        copyright            : (C) 2024 by Caio Arantes
        email                : caiosimplicioarante@gmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
"""

# =============================================================================
# STANDARD LIBRARY IMPORTS
# =============================================================================
import os
import sys
import datetime
import importlib.util
import urllib.request
import urllib.parse
import urllib.error
import json
import ssl
import io

# =============================================================================
# THIRD-PARTY SCIENTIFIC COMPUTING IMPORTS
# =============================================================================
import numpy as np
import pandas as pd
import scipy
import requests
from scipy.stats import gamma, norm

# Set numpy boolean compatibility
np.bool = np.bool_

# Climate analysis libraries
import pymannkendall as mk
import pyhomogeneity as hg
import climdex.precipitation as pdex
import climdex.temperature as tdex
import xarray as xr

# Plotting libraries
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots

# QGIS imports
import qgis

# =============================================================================
# QGIS CORE IMPORTS
# =============================================================================
from qgis.core import (
    # Basic QGIS classes
    QgsMessageLog,
    Qgis,
    QgsWkbTypes,
    QgsProject,
    QgsApplication,
    
    # Vector layer handling
    QgsVectorLayer,
    QgsVectorFileWriter,
    QgsFeatureRequest,
    QgsFeature,
    QgsGeometry,
    QgsField,
    
    # Raster layer handling
    QgsRasterLayer,
    QgsRasterShader,
    QgsColorRampShader,
    QgsSingleBandPseudoColorRenderer,
    QgsMultiBandColorRenderer,
    QgsContrastEnhancement,
    QgsRasterBandStats,
    
    # Style and visualization
    QgsStyle,
    QgsColorRamp,
    QgsLayerTreeLayer,
    
    # Coordinate systems and geometry processing
    QgsCoordinateReferenceSystem,
    QgsCoordinateTransform,
    QgsRectangle,
    
    # Processing and feedback
    QgsProcessingFeedback,
    
    # Map layer management
    QgsMapLayer,
)

# =============================================================================
# QGIS GUI IMPORTS
# =============================================================================
from qgis.PyQt.QtGui import (
    QFont, 
    QColor
)

from qgis.PyQt.QtCore import (
    QDate, 
    Qt, 
    QVariant, 
    QSettings, 
    QTimer, 
    QEvent
)

from qgis.PyQt.QtWidgets import (
    # Application and main window components
    QApplication,
    QMainWindow,
    QWidget,
    QDialog,
    
    # Layout management
    QVBoxLayout,
    QHBoxLayout,
    QGridLayout,
    
    # Input widgets
    QCheckBox,
    QDateEdit,
    QLineEdit,
    QPushButton,
    QToolButton,
    
    # Display widgets
    QLabel,
    QTextBrowser,
    QScrollArea,
    
    # Dialog components
    QMessageBox,
    QFileDialog,
    QDialogButtonBox,
    
    # Size and layout policies
    QSizePolicy,
)

from qgis.PyQt import uic
import os
import webbrowser


# =============================================================================
# LOCAL MODULE IMPORTS
# =============================================================================
from .modules import (
    map_tools,
    save_utils,
)
from .mouse_events import Delete_Marker

# =============================================================================
# UI CONFIGURATION
# =============================================================================
# Load the UI file for the main dialog
language = QSettings().value("locale/userLocale", "en")[0:2]

if language == "pt":
    ui_file = os.path.join("ui", "climaplots_dialog_base_pt.ui")
else:
    ui_file = os.path.join("ui", "climaplots_dialog_base.ui")
FORM_CLASS, _ = uic.loadUiType(os.path.join(os.path.dirname(__file__), ui_file))

# =============================================================================
# MAIN DIALOG CLASS
# =============================================================================

class ClimaPlotsDialog(QDialog, FORM_CLASS):
    """
    Main dialog class for ClimaPlots plugin.
    
    This class handles the user interface for climate data analysis and visualization.
    It provides functionality for:
    - Fetching climate data from NASA POWER API
    - Performing statistical analysis (Mann-Kendall, Pettitt tests)
    - Generating various climate plots and indices
    - Exporting results and visualizations
    
    Attributes:
        iface: QGIS interface object for interaction with the main application
        focus_timer: Timer for managing window focus and activation
        df: Main DataFrame containing raw climate data
        dataframes_dict: Dictionary containing processed climate indices
        df_save, df_save2, df_save3: DataFrames for export functionality
        fig, fig2, fig3: Plotly figure objects for visualizations
        config: Configuration dictionary for Plotly charts
    """
    
    def __init__(self, parent=None, iface=None):
        """
        Initialize the ClimaPlots dialog.
        
        Sets up the user interface, window properties, event connections,
        and initializes data attributes.
        
        Args:
            parent: Parent widget (default: None)
            iface: QGIS interface object for plugin integration (default: None)
        """
        super(ClimaPlotsDialog, self).__init__(parent)
        self.setupUi(self)
        self.iface = iface
        
        # Configure window properties for modeless dialog
        self.setWindowFlags(
            Qt.WindowType.Window |
            Qt.WindowType.WindowCloseButtonHint |
            Qt.WindowType.WindowMinimizeButtonHint |
            Qt.WindowType.WindowMaximizeButtonHint
        )
        self.setModal(False)
        
        # Initialize focus management timer
        self.focus_timer = QTimer()
        self.focus_timer.timeout.connect(self.check_focus)

        # Initialize window size after UI setup
        QTimer.singleShot(0, lambda: self.resizeEvent("small"))

        self.language_method()

        # Configure Plotly chart settings
        self.config = {
            "displaylogo": False,
            "modeBarButtonsToRemove": [
                "toImage", "sendDataToCloud", "zoom2d", "pan2d", "select2d",
                "lasso2d", "zoomIn2d", "zoomOut2d", "autoScale2d", "resetScale2d",
                "hoverClosestCartesian", "hoverCompareCartesian", "zoom3d", "pan3d",
                "orbitRotation", "tableRotation", "resetCameraLastSave",
                "resetCameraDefault3d", "hoverClosest3d", "zoomInGeo", "zoomOutGeo",
                "resetGeo", "hoverClosestGeo", "hoverClosestGl2d", "hoverClosestPie",
                "toggleHover", "toggleSpikelines", "resetViews",
            ],
        }

        # Connect UI event signals to handler methods
        self._connect_ui_signals()
        
        # Initialize data attributes
        self._initialize_data_attributes()
        
        # Setup climate indices dropdown
        self._setup_climate_indices()
        self.language = QgsApplication.instance().locale()[:2]
        
        # Set initial UI state
        self.tabWidget.setCurrentIndex(0)

    def _connect_ui_signals(self):
        """Connect UI widget signals to their respective handler methods."""
        self.tabWidget.currentChanged.connect(self.on_tab_changed)
        self.navegador.clicked.connect(self.on_navegador_clicked)
        self.navegador_2.clicked.connect(self.on_navegador_clicked_2)
        self.navegador_3.clicked.connect(self.on_navegador_clicked_3)
        self.save_plot.clicked.connect(self.save_clicked)
        self.save_plot2.clicked.connect(self.save_clicked2)
        self.save_plot3.clicked.connect(self.save_clicked3)
        self.save_raw.clicked.connect(self.save_raw_clicked)
        self.rejected.connect(self.fun_fechou)
        self.gerar_req.clicked.connect(self.request_api)
        self.atributo.currentTextChanged.connect(self.plots1)
        self.atributo_2.currentTextChanged.connect(self.plots3)
        self.googlemaps.clicked.connect(map_tools.hybrid_function)
        self.proxy.clicked.connect(self.open_proxy_dialog)
        self.learn.clicked.connect(self.open_learn_dialog)

    def language_method(self):

        atributos = ["Max Temperature", "Min Temperature", "Precipitation", "Relative Humidity", "Irradiation"]

        for atributo in atributos:
            self.atributo.addItem(atributo)

    def _initialize_data_attributes(self):
        """Initialize data storage attributes."""
        self.df = None
        self.dataframes_dict = None

    def open_learn_dialog(self):
        """Open the learn dialog."""
        webbrowser.open("https://caioarantes.github.io/climaplots/")

    def _setup_climate_indices(self):
        """Setup the climate indices dropdown with available options."""
        sheet_names = [
            'Annual Summer Days',
            'Annual Frost Days',
            'Annual Tropical Nights',
            'Annual Icing Days',
            'Monthly Maximum Temperature',
            'Monthly Minimum Temperature of Maximum Temperatures',
            'Monthly Maximum Temperature of Minimum Temperatures',
            'Monthly Minimum Temperature',
            'Daily Temperature Range',
            'Monthly Maximum 1-day Precipitation',
            'Monthly Maximum 5-day Precipitation',
            'Annual Count of Days when Precipitation Exceeds 10mm',
            'Annual Count of Days when Precipitation Exceeds 20mm',
            'Simple Precipitation Intensity Index',
            'Number of Consecutive Dry Days in a Month',
            'Number of Consecutive Wet Days in a Month',
            'The Standardized Precipitation Index (SPI)'
        ]
        
        for name in sheet_names:
            self.atributo_2.addItem(name)
        self.atributo_2.setCurrentIndex(0)

    # =========================================================================
    # WINDOW EVENT HANDLING METHODS
    # =========================================================================
    
    def showEvent(self, event):
        """
        Handle dialog show event.
        
        Starts the focus timer and ensures proper window sizing when the dialog
        is first shown.
        
        Args:
            event: The show event object
        """
        super().showEvent(event)
        
        # Ensure window size is locked when first shown
        if not hasattr(self, '_size_locked'):
            self.resizeEvent("small")
            self._size_locked = True
            
        # Start focus management timer
        if hasattr(self, 'focus_timer'):
            self.focus_timer.start(100)

    def hideEvent(self, event):
        """
        Handle dialog hide event.
        
        Stops the focus timer to prevent unnecessary processing when the dialog
        is hidden.
        
        Args:
            event: The hide event object
        """
        super().hideEvent(event)
        if hasattr(self, 'focus_timer'):
            try:
                self.focus_timer.stop()
            except RuntimeError:
                # Timer might already be stopped or deleted
                pass
        # Remove any markers left on the canvas when the dialog is hidden
        try:
            canvas = getattr(self, 'canvas', None) or (self.iface.mapCanvas() if hasattr(self, 'iface') else None)
            markers = getattr(self, 'Markers', None)
            if canvas and markers:
                Delete_Marker(canvas, markers)
        except Exception:
            # Do not fail hide for marker cleanup issues
            pass

    def check_focus(self):
        """
        Check if the plugin window should be brought to front.
        
        This method implements a less aggressive focus management strategy
        that only raises the window when appropriate, avoiding interference
        with internal QGIS events.
        """
        if not self.isVisible() or self.isMinimized():
            return
            
        # Only act when clicking on map canvas specifically
        active_window = QApplication.activeWindow()
        if (active_window == self.iface.mainWindow() and 
            not QApplication.activeModalWidget() and
            not self.isActiveWindow()):
            
            # Only raise window, don't activate to avoid event interference
            self.raise_()

    def closeEvent(self, event):
        """
        Handle dialog close event.
        
        Instead of actually closing the dialog, this method hides it to
        preserve the current state and settings. This allows the user
        to reopen the dialog with all their previous work intact.
        
        Args:
            event: The close event object
        """
        # Stop the focus timer to prevent memory leaks
        if hasattr(self, 'focus_timer'):
            try:
                self.focus_timer.stop()
            except RuntimeError:
                # Timer might already be stopped
                pass
        
        # Hide the dialog instead of closing to preserve state
        # Also remove markers from canvas when closing
        try:
            canvas = getattr(self, 'canvas', None) or (self.iface.mapCanvas() if hasattr(self, 'iface') else None)
            markers = getattr(self, 'Markers', None)
            if canvas and markers:
                Delete_Marker(canvas, markers)
        except Exception:
            pass
        self.hide()
        event.ignore()

    def on_tab_changed(self, index):
        """
        Handle tab change events.
        
        Adjusts window size based on the selected tab to optimize the layout
        for different content types.
        
        Args:
            index: The index of the newly selected tab
        """
        print(f"Tab changed to index: {index}")
        if index != 0:
            self.resizeEvent("big")
        else:
            self.resizeEvent("small")

    def resizeEvent(self, event):
        """
        Handle window resize events.
        
        Manages window sizing for different interface states (small for input,
        big for visualization tabs).
        
        Args:
            event: Resize event type ("small" or "big")
        """
        # Remove size constraints temporarily
        self.setMinimumSize(0, 0)
        self.setMaximumSize(16777215, 16777215)

        if event == "small":
            self.resize(402, 210)
            self.setFixedSize(self.width(), self.height())  # Lock to small size
        elif event == "big":
            self.resize(945, 535)
            self.setFixedSize(self.width(), self.height())  # Lock to big size

    # =========================================================================
    # UTILITY AND EVENT HANDLER METHODS
    # =========================================================================

    def open_proxy_dialog(self):
        dialog = QDialog(self)
        dialog.setWindowTitle("Proxy Settings")
        layout = QVBoxLayout(dialog)

        label = QLabel("Enter proxy (e.g. http://user:pass@host:port):")
        layout.addWidget(label)

        proxy_edit = QLineEdit()
        # Load existing proxy from QSettings if available
        proxy = QSettings().value("climaplots/proxy", "")
        proxy_edit.setText(proxy if proxy else "")
        layout.addWidget(proxy_edit)

        button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        layout.addWidget(button_box)

        def accept():
            proxy = proxy_edit.text().strip()
            QSettings().setValue("climaplots/proxy", proxy)
            dialog.accept()

        button_box.accepted.connect(accept)
        button_box.rejected.connect(dialog.reject)

        dialog.exec_()

    def fun_fechou(self):
        """
        Handle dialog closure cleanup.
        
        Clears input fields, resets tab selection, and activates the pan tool
        in QGIS when the dialog is closed or rejected.
        """
        self.LongEdit.clear()
        self.LatEdit.clear()
        self.tabWidget.setCurrentIndex(0)
        qgis.utils.iface.actionPan().trigger()
        # Ensure markers are removed when the dialog's own cleanup is triggered
        try:
            canvas = getattr(self, 'canvas', None) or (self.iface.mapCanvas() if hasattr(self, 'iface') else None)
            markers = getattr(self, 'Markers', None)
            if canvas and markers:
                Delete_Marker(canvas, markers)
        except Exception:
            pass

    # =========================================================================
    # SAVE BUTTON HANDLERS
    # =========================================================================

    def save_clicked(self):
        """Handle save button click for annual trends data."""
        name = f"Anual_trends_{self.atributo.currentText()}.csv"
        save_utils.save(self.df_save, name, self)

    def save_clicked2(self):
        """Handle save button click for thermopluviometric data."""
        name = "Thermopluviometric.csv"
        save_utils.save(self.df_save2, name, self)

    def save_clicked3(self):
        """Handle save button click for climate indices data."""
        name = f"{self.atributo_2.currentText()}.csv"
        save_utils.save(self.df_save3, name, self)

    def save_raw_clicked(self):
        """Handle save button click for raw climate data."""
        name = "Raw_data.csv"
        save_utils.save(self.df, name, self)

    # =========================================================================
    # NAVIGATION BUTTON HANDLERS
    # =========================================================================

    def on_navegador_clicked(self):
        """Open plot 1 (annual trends) in external browser."""
        self.fig.show()

    def on_navegador_clicked_2(self):
        """Open plot 2 (thermopluviometric diagram) in external browser."""
        self.fig2.show()

    def on_navegador_clicked_3(self):
        """Open plot 3 (climate indices) in external browser."""
        self.fig3.show()

    # =========================================================================
    # PLOTTING AND VISUALIZATION METHODS
    # =========================================================================

    def plots1(self):
        """
        Generate annual trends plot with statistical analysis.
        
        Creates a line plot showing annual trends for the selected climate variable
        (precipitation, min temperature, or max temperature). Includes Mann-Kendall
        trend test and Pettitt homogeneity test results in the plot title.
        """
        if self.df is None:
            return
            
        atributo = self.atributo.currentText()
        df = self.df.copy()
        
        # Group data by year for annual aggregation
        df['Year'] = df['Date'].dt.year
        df_aux = df.groupby('Year')[['Precipitation']].sum()

        # Prepare list of variables to aggregate by annual mean (include RH and Irradiation if present)
        mean_candidates = ['Min Temperature', 'Max Temperature', 'Relative Humidity', 'Irradiation']
        mean_cols = [c for c in mean_candidates if c in df.columns]

        if mean_cols:
            df_mean = df.groupby('Year').mean()[mean_cols]
        else:
            # Fallback: create an empty dataframe indexed by year
            df_mean = pd.DataFrame(index=df_aux.index)

        # Always include annual precipitation (sum)
        df_mean['Precipitation'] = df_aux['Precipitation']
        df_mean.reset_index(inplace=True)

        # Create date column for the beginning of each year
        df_mean['Date'] = pd.to_datetime(df_mean['Year'].astype(str) + '-01-01')
        
        # Prepare data for statistical analysis
        if atributo not in df_mean.columns:
            QMessageBox.warning(self, 'Data not available', f"Attribute '{atributo}' is not available for the selected location.")
            return
        df_plot = df_mean[['Date', atributo]].copy()
        df_plot.index = df_plot['Date']
        df_plot = df_plot[[atributo]].astype(float)
        
        # Perform statistical tests
        result_mk = mk.original_test(df_plot)
        result_pettitt = hg.pettitt_test(df_plot)

        # Create plot titles with test results
        title1 = (f'Mann Kendall Test: <b>{result_mk.trend}</b>, alpha=0.05, '
                 f'p-value={round(result_mk.p, 4)}')
        
        if result_pettitt.h:
            title2 = (f'Pettitt Test: data is <b>nonhomogeneous</b>, '
                     f'probable change point location={str(result_pettitt.cp)[:4]}, '
                     f'alpha=0.05, p-value={round(result_pettitt.p, 4)}')
        else:
            title2 = (f'Pettitt Test: data is <b>homogeneous</b>, '
                     f'alpha=0.05, p-value={round(result_pettitt.p, 4)}')

        title = title1 + '<br>' + title2

        # Create the plot
        self.fig = px.line(
            df_mean, 
            x="Date", 
            y=[atributo], 
            title=(f'<b>{atributo}</b> (Long: {self.LongEdit.text()} '
                  f'Lat: {self.LatEdit.text()}) <br>{title}')
        )

        self.fig.update_layout(showlegend=False)

        # Set appropriate y-axis labels
        if atributo == "Precipitation":
            self.fig.update_yaxes(title_text="Precipitation (mm) - Annual Total")
        elif atributo == "Min Temperature":
            self.fig.update_yaxes(title_text="Min Temperature (ºC) - Annual Mean")
        elif atributo == "Max Temperature":
            self.fig.update_yaxes(title_text="Max Temperature (ºC) - Annual Mean")
        elif atributo == "Irradiation":
            # After conversion, units are kWh/m²/day
            self.fig.update_yaxes(title_text="Irradiation (kWh/m²/day) - Annual Mean")
        elif atributo == "Relative Humidity":
            self.fig.update_yaxes(title_text="Relative Humidity (%) - Annual Mean")

        # Render plot in web view
        self.webView_1.setHtml(
            self.fig.to_html(include_plotlyjs="cdn", config=self.config)
        )
        print('Plot 1 (Annual Trends) generated successfully')

        # Store data for export
        # Ensure Year column exists for export
        try:
            if 'Year' not in df_mean.columns and 'Date' in df_mean.columns:
                df_mean['Year'] = pd.to_datetime(df_mean['Date']).dt.year
        except Exception:
            pass
        self.df_save = df_mean

    def plots2(self):
        """
        Generate thermopluviometric diagram.
        
        Creates a dual-axis plot showing monthly precipitation (bars) and
        maximum temperature (line) to visualize the climate pattern throughout
        the year.
        """
        if self.df is None:
            return
            
        print('Generating thermopluviometric diagram...')

        df = self.df.copy()
        
        # Group data by year and month for monthly averages
        df_aux = df.groupby([
            df.Date.dt.year, 
            df.Date.dt.month
        ]).sum(numeric_only=True)[['Precipitation']]
        
        df = df.groupby([
            df.Date.dt.year, 
            df.Date.dt.month
        ]).mean(numeric_only=True)[['Min Temperature', 'Max Temperature']]
        
        df['Precipitation'] = df_aux['Precipitation']
        df.reset_index(level=1, inplace=True)
        
        # Calculate monthly averages across all years
        df = df.groupby(df.Date).mean()
        df.reset_index(inplace=True)
        df.rename(columns={'Date': 'Month'}, inplace=True)

        # Create subplot with secondary y-axis
        self.fig2 = make_subplots(specs=[[{"secondary_y": True}]])
        
        # Add precipitation bars (primary y-axis)
        self.fig2.add_trace(
            go.Bar(
                x=df['Month'], 
                y=df['Precipitation'], 
                name='Precipitation',
                marker_color='#3498db'
            ),
            secondary_y=False,
        )

        # Add maximum temperature line (secondary y-axis)
        self.fig2.add_trace(
            go.Scatter(
                x=df['Month'], 
                y=df['Max Temperature'], 
                mode='lines+markers',
                name='Max Temperature',
                line=dict(color='#e67e22'),
                marker=dict(color='#e67e22')
            ),
            secondary_y=True,
        )

        # Add minimum temperature line (secondary y-axis)
        if 'Min Temperature' in df.columns:
            self.fig2.add_trace(
                go.Scatter(
                    x=df['Month'],
                    y=df['Min Temperature'],
                    mode='lines+markers',
                    name='Min Temperature',
                    line=dict(color='#2ecc71', dash='dot'),
                    marker=dict(color='#2ecc71')
                ),
                secondary_y=True,
            )
        
        # Update layout and titles
        self.fig2.update_layout(
            title_text=(f"<b>Thermo-pluviometric diagram</b> "
                       f"(Long: {self.LongEdit.text()} Lat: {self.LatEdit.text()})")
        )

        # Set x-axis title and tick configuration
        self.fig2.update_xaxes(title_text="Month")
        self.fig2.update_layout(
            xaxis=dict(tickmode='linear')
        )

        # Set y-axes titles
        self.fig2.update_yaxes(
            title_text="Temperature (ºC)", 
            secondary_y=True
        )
        self.fig2.update_yaxes(
            title_text="Precipitation (mm)", 
            secondary_y=False
        )

        # Render plot in web view
        self.webView_2.setHtml(
            self.fig2.to_html(include_plotlyjs="cdn", config=self.config)
        )
        print('Thermopluviometric diagram generated successfully')

        # Store data for export
        # Ensure Year column exists for export when possible
        try:
            if 'Month' in df.columns:
                # if Month contains datetimes, extract year
                dt = pd.to_datetime(df['Month'], errors='coerce')
                if not dt.isna().all():
                    df['Year'] = dt.dt.year
        except Exception:
            pass
        self.df_save2 = df

    def plots3_compute(self):
        """
        Compute climate indices using climdex library.
        
        Calculates various climate indices including temperature and precipitation
        indices according to the ETCCDI (Expert Team on Climate Change Detection
        and Indices) recommendations. Stores results in dataframes_dict for
        later visualization and export.
        """
        if self.df is None:
            return
            
        print('Computing climate indices...')
        df = self.df.copy()
        df_aux = df.copy()
        
        # Prepare data for xarray conversion
        df.set_index('Date', inplace=True)
        ds = df[['Precipitation', 'Max Temperature', 'Min Temperature']].copy().to_xarray()

        # Initialize climate indices calculators
        precip_indices = pdex.indices(time_dim='Date')
        temp_indices = tdex.indices(time_dim='Date')

        # Compute indices with per-index error handling so one failure doesn't stop others
        results = {}

        # =====================================================================
        # TEMPERATURE INDICES
        # =====================================================================
        try:
            frost_days_df = temp_indices.annual_frost_days(ds, varname='Min Temperature').to_dataframe()
            frost_days_df.columns = ['Annual Frost Days']
            results['Annual Frost Days'] = frost_days_df
        except Exception as e:
            QgsMessageLog.logMessage(f'Failed to compute Annual Frost Days: {e}', 'ClimaPlots', Qgis.Warning)
            print(f'Failed to compute Annual Frost Days: {e}')

        try:
            tropical_nights_df = temp_indices.annual_tropical_nights(ds, varname='Min Temperature').to_dataframe()
            tropical_nights_df.columns = ['Annual Tropical Nights']
            results['Annual Tropical Nights'] = tropical_nights_df
        except Exception as e:
            QgsMessageLog.logMessage(f'Failed to compute Annual Tropical Nights: {e}', 'ClimaPlots', Qgis.Warning)
            print(f'Failed to compute Annual Tropical Nights: {e}')

        try:
            icing_days_df = temp_indices.annual_icing_days(ds, varname='Max Temperature').to_dataframe()
            icing_days_df.columns = ['Annual Icing Days']
            results['Annual Icing Days'] = icing_days_df
        except Exception as e:
            QgsMessageLog.logMessage(f'Failed to compute Annual Icing Days: {e}', 'ClimaPlots', Qgis.Warning)
            print(f'Failed to compute Annual Icing Days: {e}')

        try:
            summer_days_df = temp_indices.annual_summer_days(ds, varname='Max Temperature').to_dataframe()
            summer_days_df.columns = ['Annual Summer Days']
            results['Annual Summer Days'] = summer_days_df
        except Exception as e:
            QgsMessageLog.logMessage(f'Failed to compute Annual Summer Days: {e}', 'ClimaPlots', Qgis.Warning)
            print(f'Failed to compute Annual Summer Days: {e}')

        def _safe_monthly(func, ds, varname, out_name, colname):
            try:
                df_tmp = func(ds, varname=varname).to_dataframe()[[varname]]
                df_tmp.columns = [colname]
                results[out_name] = df_tmp
            except Exception as e:
                QgsMessageLog.logMessage(f'Failed to compute {out_name}: {e}', 'ClimaPlots', Qgis.Warning)
                print(f'Failed to compute {out_name}: {e}')

        _safe_monthly(temp_indices.monthly_txx, ds, 'Max Temperature', 'Monthly Maximum Temperature', 'Max Temperature')
        _safe_monthly(temp_indices.monthly_txn, ds, 'Max Temperature', 'Monthly Minimum Temperature of Maximum Temperatures', 'Max Temperature')
        _safe_monthly(temp_indices.monthly_tnx, ds, 'Min Temperature', 'Monthly Maximum Temperature of Minimum Temperatures', 'Min Temperature')
        _safe_monthly(temp_indices.monthly_tnn, ds, 'Min Temperature', 'Monthly Minimum Temperature', 'Min Temperature')

        try:
            dtr_df = temp_indices.daily_temperature_range(ds, ds, min_varname='Min Temperature', max_varname='Max Temperature').to_dataframe(name='DTR')
            dtr_df.columns = ['Daily Temperature Range']
            results['Daily Temperature Range'] = dtr_df
        except Exception as e:
            QgsMessageLog.logMessage(f'Failed to compute Daily Temperature Range: {e}', 'ClimaPlots', Qgis.Warning)
            print(f'Failed to compute Daily Temperature Range: {e}')

        # =====================================================================
        # PRECIPITATION INDICES
        # =====================================================================
        try:
            rx1day_df = precip_indices.monthly_rx1day(ds, varname='Precipitation').to_dataframe()
            rx1day_df.columns = ['Monthly Maximum 1-day Precipitation']
            results['Monthly Maximum 1-day Precipitation'] = rx1day_df
        except Exception as e:
            QgsMessageLog.logMessage(f'Failed to compute Monthly Maximum 1-day Precipitation: {e}', 'ClimaPlots', Qgis.Warning)
            print(f'Failed to compute Monthly Maximum 1-day Precipitation: {e}')

        try:
            rx5day_df = precip_indices.monthly_rx5day(ds, varname='Precipitation').to_dataframe()
            rx5day_df.columns = ['Monthly Maximum 5-day Precipitation']
            results['Monthly Maximum 5-day Precipitation'] = rx5day_df
        except Exception as e:
            QgsMessageLog.logMessage(f'Failed to compute Monthly Maximum 5-day Precipitation: {e}', 'ClimaPlots', Qgis.Warning)
            print(f'Failed to compute Monthly Maximum 5-day Precipitation: {e}')

        try:
            r10mm_df = precip_indices.annual_r10mm(ds, varname='Precipitation').to_dataframe()
            r10mm_df.columns = ['Annual Count of Days when Precipitation Exceeds 10mm']
            results['Annual Count of Days when Precipitation Exceeds 10mm'] = r10mm_df
        except Exception as e:
            QgsMessageLog.logMessage(f'Failed to compute Annual R10mm: {e}', 'ClimaPlots', Qgis.Warning)
            print(f'Failed to compute Annual R10mm: {e}')

        try:
            r20mm_df = precip_indices.annual_r20mm(ds, varname='Precipitation').to_dataframe()
            r20mm_df.columns = ['Annual Count of Days when Precipitation Exceeds 20mm']
            results['Annual Count of Days when Precipitation Exceeds 20mm'] = r20mm_df
        except Exception as e:
            QgsMessageLog.logMessage(f'Failed to compute Annual R20mm: {e}', 'ClimaPlots', Qgis.Warning)
            print(f'Failed to compute Annual R20mm: {e}')


        try:
            sdii_value_df = precip_indices.sdii(ds, period='1M', varname='Precipitation').to_dataframe()
            sdii_value_df.columns = ['Simple Precipitation Intensity Index']
            results['Simple Precipitation Intensity Index'] = sdii_value_df
        except Exception as e:
            QgsMessageLog.logMessage(f'Failed to compute SDII: {e}', 'ClimaPlots', Qgis.Warning)
            print(f'Failed to compute SDII: {e}')

        try:
            cdd_value_df = precip_indices.cdd(ds, period='1M', varname='Precipitation').to_dataframe()
            cdd_value_df.columns = ['Number of Consecutive Dry Days in a Month']
            results['Number of Consecutive Dry Days in a Month'] = cdd_value_df
        except Exception as e:
            QgsMessageLog.logMessage(f'Failed to compute CDD: {e}', 'ClimaPlots', Qgis.Warning)
            print(f'Failed to compute CDD: {e}')

        try:
            cwd_value_df = precip_indices.cwd(ds, period='1M', varname='Precipitation').to_dataframe()
            cwd_value_df.columns = ['Number of Consecutive Wet Days in a Month']
            results['Number of Consecutive Wet Days in a Month'] = cwd_value_df
        except Exception as e:
            QgsMessageLog.logMessage(f'Failed to compute CWD: {e}', 'ClimaPlots', Qgis.Warning)
            print(f'Failed to compute CWD: {e}')

        # =====================================================================
        # STANDARDIZED PRECIPITATION INDEX (SPI) CALCULATION
        # =====================================================================
        
        # Calculate 90-day rolling precipitation sum
        df_aux['Accumulated_Precipitation'] = df_aux['Precipitation'].rolling(
            window=90
        ).sum()
        df_aux.dropna(inplace=True)
        
        # Fit gamma distribution to accumulated precipitation and compute SPI
        try:
            params = gamma.fit(df_aux['Accumulated_Precipitation'], floc=0)
            df_aux['Cumulative_Probability'] = gamma.cdf(df_aux['Accumulated_Precipitation'], *params)
            df_aux['SPI'] = norm.ppf(df_aux['Cumulative_Probability'])
            spi_value_df = df_aux[['SPI']].copy()
            spi_value_df.columns = ['The Standardized Precipitation Index (SPI)']
            results['The Standardized Precipitation Index (SPI)'] = spi_value_df
        except Exception as e:
            QgsMessageLog.logMessage(f'Failed to compute SPI: {e}', 'ClimaPlots', Qgis.Warning)
            print(f'Failed to compute SPI: {e}')

        print('Climate indices computation completed')

        # Store all successfully computed indices in dictionary for later access
        self.dataframes_dict = results

    def plots3(self):
        """
        Generate climate indices visualization.
        
        Creates a line plot for the selected climate index from the computed
        dataframes dictionary. Adds a year column to the data for better
        temporal analysis and export functionality.
        """
        if self.df is None:
            return
            
        print('Generating climate indices plot...')
        
        # Get the selected climate index data
        selected = self.atributo_2.currentText()
        if selected not in self.dataframes_dict:
            QMessageBox.warning(self, 'No data', f"No computed data available for '{selected}'")
            return

        df_plot = self.dataframes_dict[selected].copy()
        print(f'Selected index: {selected}')

        # Determine which column to plot:
        # 1) exact match with selected name
        # 2) if only one column exists, use it
        # 3) otherwise, pick the first numeric column
        if selected in df_plot.columns:
            ycol = selected
        elif df_plot.shape[1] == 1:
            ycol = df_plot.columns[0]
        else:
            numeric_cols = df_plot.select_dtypes(include=[np.number]).columns.tolist()
            if numeric_cols:
                ycol = numeric_cols[0]
            else:
                QMessageBox.warning(self, 'Data not plottable', f"No numeric column found for '{selected}'")
                return

        print(f'Plotting column: {ycol}')

        # Run statistical tests (Mann-Kendall and Pettitt) on the chosen series
        test_title = ''
        try:
            print(df_plot)
            # Prepare series similar to plots1: a one-column DataFrame or Series
            if 'Date' in df_plot.columns:
                df_test = df_plot[[ycol]].copy()
                df_test.index = pd.to_datetime(df_plot['Date'])
            else:
                df_test = df_plot[[ycol]].copy()


            # Mann-Kendall
            try:
                result_mk = mk.original_test(df_test)
                title1 = (f'Mann Kendall Test: <b>{result_mk.trend}</b>, alpha=0.05, '
                          f'p-value={round(result_mk.p, 4)}')
            except Exception as e:
                title1 = f'Mann Kendall Test failed: {e}'

            # Pettitt
            try:
                result_pettitt = hg.pettitt_test(df_test)
                print(f'Pettitt test result: h={result_pettitt.h}, cp={result_pettitt.cp}, p={result_pettitt.p}')
                def format_cp_label(cp, idx):
                    print(type(cp), type(idx))
                    # If idx is a pandas Index, try to get the label at position cp
                    if type(idx).__name__ == 'Index' or str(type(idx)).endswith('pandas.core.indexes.base.Index'):
                        try:
                            return idx[cp]
                        except Exception:
                            return cp
                    print(f'test - Formatting change point label for cp={cp} and index={idx}')
                    # Exibe o rótulo do índice na posição cp, sem conversão
        
                    return cp


                if result_pettitt.h:
                    cp_label = format_cp_label(result_pettitt.cp, df_test.index)
                    title2 = (f'Pettitt Test: data is <b>nonhomogeneous</b>, '
                              f'probable change point location={cp_label}, '
                              f'alpha=0.05, p-value={round(result_pettitt.p, 4)}')
                else:
                    title2 = (f'Pettitt Test: data is <b>homogeneous</b>, '
                              f'alpha=0.05, p-value={round(result_pettitt.p, 4)}')
            except Exception as e:
                title2 = f'Pettitt Test failed: {e}'

            test_title = title1 + '<br>' + title2
        except Exception as e:
            test_title = f'Stat tests failed: {e}'

        # Create the line plot (use index as x if no explicit Date column)
        full_title = (f'<b>{selected}</b> (Long: {self.LongEdit.text()} Lat: {self.LatEdit.text()})')
        if test_title:
            full_title = full_title + '<br>' + test_title

        if 'Date' in df_plot.columns:
            self.fig3 = px.line(
                df_plot,
                x='Date',
                y=ycol,
                title=full_title
            )
        else:
            self.fig3 = px.line(
                df_plot,
                y=ycol,
                title=full_title
            )

        self.fig3.update_layout(showlegend=False)
        
        # Render plot in web view
        self.webView_3.setHtml(
            self.fig3.to_html(include_plotlyjs="cdn", config=self.config)
        )
        
        # Add year column to DataFrame for better analysis and export
        if 'Date' in df_plot.columns:
            df_plot['Year'] = pd.to_datetime(df_plot['Date']).dt.year
        else:
            # try several fallbacks: datetime index, index named Year, or integer year-like index
            try:
                idx = df_plot.index
                # datetime index
                if pd.api.types.is_datetime64_any_dtype(idx) or pd.api.types.is_datetime64_any_dtype(pd.to_datetime(idx, errors='coerce')):
                    df_plot['Year'] = pd.to_datetime(idx).year
                else:
                    # integer-like index representing years
                    if pd.api.types.is_integer_dtype(idx) or pd.api.types.is_integer_dtype(idx.astype('int', copy=False)):
                        vals = np.array(idx, dtype='int')
                        if vals.size and vals.min() >= 1800 and vals.max() <= 2100:
                            df_plot['Year'] = vals
            except Exception:
                pass
        
        # Store data for export
        self.df_save3 = df_plot
        print('Climate indices plot generated successfully')

    # =========================================================================
    # DATA ACQUISITION METHODS
    # =========================================================================

    def actual_request_api(self):
        """
        Fetch climate data from NASA POWER API.
        
        Retrieves daily climate data including maximum temperature, minimum 
        temperature, and precipitation from NASA's POWER (Prediction of 
        Worldwide Energy Resources) database for the specified coordinates.
        
        Returns:
            pandas.DataFrame: Climate data with Date, Precipitation, 
                            Min Temperature, and Max Temperature columns
        """
        # Calculate end date (previous year to ensure complete data)
        endtime = str(int(datetime.date.today().strftime("%Y")) - 1) + '1231'
        
        # Construct API request URL
        base_url = (
            r"https://power.larc.nasa.gov/api/temporal/daily/point?"
            # Request max/min temperature, precipitation (corrected),
            # relative humidity at 2m (RH2M) and surface shortwave downwelling
            # (ALLSKY_SFC_SW_DWN) for irradiation
            r"parameters=T2M_MAX,PRECTOTCORR,T2M_MIN,RH2M,ALLSKY_SFC_SW_DWN&community=RE&"
            r"longitude={longitude}&latitude={latitude}&"
            r"start=19810101&end=" + endtime + "&format=JSON"
        )
        
        api_request_url = base_url.format(
            longitude=float(self.LongEdit.text()), 
            latitude=float(self.LatEdit.text())
        )
        
        # Prepare proxy settings for requests
        proxies = {}
        proxy = QSettings().value("climaplots/proxy", "")
        if proxy:
            proxies = {
                'http': proxy,
                'https': proxy
            }
            print(f"Using proxy: {proxy}")
        
        # Make API request with timeout and proxy support
        # Try with proxy first, then without proxy if it fails
        response = None
        try:
            if proxies:
                print("Attempting request with proxy...")
                response = requests.get(
                    url=api_request_url, 
                    verify=True, 
                    timeout=1000,
                    proxies=proxies
                )
            else:
                # No proxy configured, make direct request
                response = requests.get(
                    url=api_request_url, 
                    verify=True, 
                    timeout=1000
                )
        except Exception as e:
            if proxies:
                print(f"Request with proxy failed: {e}")
                print("Retrying without proxy...")
                try:
                    response = requests.get(
                        url=api_request_url, 
                        verify=True, 
                        timeout=1000
                    )
                    print("Request successful without proxy")
                except Exception as e2:
                    print(f"Request failed even without proxy: {e2}")
                    raise e2
            else:
                print(f"Direct request failed: {e}")
                raise e
        content = json.loads(response.content.decode('utf-8'))
        
        # Convert to DataFrame and rename columns
        df = pd.DataFrame.from_dict(content['properties']['parameter'])
        df = df.reset_index().rename(columns={
            'index': 'Date',
            'PRECTOTCORR': 'Precipitation',
            'T2M_MIN': 'Min Temperature',
            'T2M_MAX': 'Max Temperature',
            'RH2M': 'Relative Humidity',
            'ALLSKY_SFC_SW_DWN': 'Irradiation'
        })
        df['Irradiation'].replace(-999.0, np.nan, inplace=True)
        # Convert date column to datetime
        df.Date = pd.to_datetime(df.Date)
        # Parse 'Irradiation' values as numeric and keep the units as provided by the API.
        # NOTE: automatic conversion removed; plugin will preserve whatever unit
        # the API returns. If you want kWh/m^2/day, re-enable conversion after
        # confirming the API unit.
        try:
            if 'Irradiation' in df.columns:
                df['Irradiation'] = pd.to_numeric(df['Irradiation'], errors='coerce')
        except Exception as e:
            # Non-fatal: keep original values if coercion fails
            print(f'Warning: failed to parse Irradiation values: {e}')

        print('Climate data request completed successfully')
        print(df.head())
        return df

    def request_api(self):
        """
        Main method to request climate data and generate all visualizations.
        
        Handles the complete workflow from data acquisition to plot generation:
        1. Fetches data from NASA POWER API
        2. Generates annual trends plot with statistics
        3. Creates thermopluviometric diagram
        4. Computes climate indices
        5. Generates climate indices plot
        6. Switches to visualization tab
        """
        # Show loading cursor
        QApplication.setOverrideCursor(Qt.WaitCursor)
        
        try:
            # Clean previous results before running a new analysis
            try:
                # Dataframes
                self.df = None
                self.dataframes_dict = None
                self.df_save = None
                self.df_save2 = None
                self.df_save3 = None
                # Figures
                self.fig = None
                self.fig2 = None
                self.fig3 = None
                # Clear web views
                try:
                    self.webView_1.setHtml('')
                except Exception:
                    pass
                try:
                    self.webView_2.setHtml('')
                except Exception:
                    pass
                try:
                    self.webView_3.setHtml('')
                except Exception:
                    pass
                # Clear stats widgets if present
                try:
                    if hasattr(self, 'results_text_1'):
                        self.results_text_1.clear(); self.results_text_1.setVisible(False)
                    if hasattr(self, 'results_text_3'):
                        self.results_text_3.clear(); self.results_text_3.setVisible(False)
                    if hasattr(self, 'save_stats_btn_1'):
                        self.save_stats_btn_1.setVisible(False)
                    if hasattr(self, 'save_stats_btn_3'):
                        self.save_stats_btn_3.setVisible(False)
                except Exception:
                    pass
                # Clear last stats
                if hasattr(self, '_last_stats_1'):
                    delattr(self, '_last_stats_1') if hasattr(self, '_last_stats_1') else None
                if hasattr(self, '_last_stats_3'):
                    delattr(self, '_last_stats_3') if hasattr(self, '_last_stats_3') else None
                # Remove markers from canvas
                try:
                    canvas = getattr(self, 'canvas', None) or (self.iface.mapCanvas() if hasattr(self, 'iface') else None)
                    markers = getattr(self, 'Markers', None)
                    if canvas and markers:
                        Delete_Marker(canvas, markers)
                except Exception:
                    pass
            except Exception:
                pass

            # Fetch climate data
            self.df = self.actual_request_api()
            
            # Generate all plots and analyses
            self.plots1()                  # Annual trends with statistics
            self.plots2()                  # Thermopluviometric diagram
            self.plots3_compute()          # Compute climate indices
            self.plots3()                  # Climate indices visualization
            
            # Switch to visualization tab
            self.tabWidget.setCurrentIndex(1)
            
        except Exception as e:
            print(f"An error occurred during data processing: {e}")
            # Could add user notification here
            
        finally:
            # Restore normal cursor
            QApplication.restoreOverrideCursor()