# VERSION: v1.7 - fire/ui_components/forecasting/forecast_results_widget.py (pre refactoring)
 
# RESP: Dashboard che visualizza e esporta report di backtest completi di metadati (frontmatter) e branding in alto a destra.
 
# DEPS: AppState, QCPlotlyChartWidget, pandas, plotly.
 
# CHANGELOG v1.7:
 
#   - Titoli e report HTML ora mostrano dinamicamente il nome del modello usato (TimesFM o Prophet).
 
# TODO: N/A
 
  
 
import pandas as pd
 
import plotly.graph_objects as go
 
from typing import Dict, Any
 
from datetime import datetime
 
  
 
from PySide6.QtWidgets import (
 
    QWidget, QVBoxLayout, QSplitter, QTabWidget, QHBoxLayout,
 
    QSpacerItem, QSizePolicy, QToolButton, QFileDialog, QDockWidget
 
)
 
from PySide6.QtCore import Qt, Slot
 
from PySide6.QtGui import QIcon
 
  
 
from fire.app_state import AppState
 
from fire.ui_components.charts.qc_plotly_chart_widget import QCPlotlyChartWidget
 
  
 
class ForecastResultsWidget(QWidget):
 
    """
 
    Dashboard per visualizzare i risultati di un backtest di forecasting,
 
    inclusi grafici interattivi e una barra di azioni per l'esportazione e la gestione della vista.
 
    """
 
    def __init__(self, app_state: AppState, parent=None):
 
        super().__init__(parent)
 
        self.app_state = app_state
 
        self.results: Dict[str, Any] = {}
 
        self.ticker: str = ""
 
        self.model_name: str = "" # Aggiunto
 
        self.initial_window: int = 0
 
        self.horizon: int = 0
 
        self.fig_forecast_json: str = ""
 
        self.fig_mae_json: str = ""
 
        self.fig_rmse_json: str = ""
 
        self.fig_da_json: str = ""
 
  
 
        self._setup_ui()
 
        self._connect_signals()
 
  
 
    def _setup_ui(self):
 
        main_layout = QVBoxLayout(self)
 
        main_layout.setContentsMargins(5, 5, 5, 5)
 
        self.results_splitter = QSplitter(Qt.Orientation.Vertical)
 
        self.forecast_plot_widget = QCPlotlyChartWidget(self.app_state)
 
        analysis_tabs_widget = QWidget()
 
        analysis_tabs_layout = QVBoxLayout(analysis_tabs_widget)
 
        analysis_tabs_layout.setContentsMargins(0,0,0,0)
 
        self.analysis_tabs = QTabWidget()
 
        self.mae_plot_widget = QCPlotlyChartWidget(self.app_state)
 
        self.rmse_plot_widget = QCPlotlyChartWidget(self.app_state)
 
        self.da_plot_widget = QCPlotlyChartWidget(self.app_state)
 
        self.analysis_tabs.addTab(self.mae_plot_widget, "MAE Over Time")
 
        self.analysis_tabs.addTab(self.rmse_plot_widget, "RMSE Over Time")
 
        self.analysis_tabs.addTab(self.da_plot_widget, "Directional Accuracy")
 
        analysis_tabs_layout.addWidget(self.analysis_tabs)
 
        self.results_splitter.addWidget(self.forecast_plot_widget)
 
        self.results_splitter.addWidget(analysis_tabs_widget)
 
        self.results_splitter.setSizes([600, 400])
 
  
 
        action_bar_layout = QHBoxLayout()
 
        action_bar_layout.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
 
  
 
        self.export_button = QToolButton()
 
        self.export_button.setIcon(QIcon.fromTheme("document-save"))
 
        self.export_button.setText("Export to HTML")
 
        self.export_button.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
 
        self.export_button.setToolTip("Esporta questa dashboard in un singolo file HTML.")
 
        self.maximize_button = QToolButton()
 
        self.maximize_button.setIcon(QIcon.fromTheme("view-fullscreen"))
 
        self.maximize_button.setToolTip("Maximize/Restore Window")
 
        action_bar_layout.addWidget(self.export_button)
 
        action_bar_layout.addWidget(self.maximize_button)
 
  
 
        main_layout.addWidget(self.results_splitter)
 
        main_layout.addLayout(action_bar_layout)
 
    def _connect_signals(self):
 
        self.maximize_button.clicked.connect(self._toggle_maximize)
 
        self.export_button.clicked.connect(self._handle_export)
 
  
 
    @Slot()
 
    def _toggle_maximize(self):
 
        parent_dock = self.parent()
 
        while parent_dock and not isinstance(parent_dock, QDockWidget):
 
            parent_dock = parent_dock.parent()
 
        if parent_dock:
 
            if parent_dock.isMaximized():
 
                parent_dock.showNormal()
 
                self.maximize_button.setIcon(QIcon.fromTheme("view-fullscreen"))
 
            else:
 
                parent_dock.showMaximized()
 
                self.maximize_button.setIcon(QIcon.fromTheme("view-restore"))
 
  
 
    @Slot()
 
    def _handle_export(self):
 
        if not self.results:
 
            self.app_state.log("Nessun risultato da esportare.", "WARNING")
 
            return
 
        suggested_filename = f"forecast_backtest_{self.ticker}_{self.model_name}.html"
 
        filepath, _ = QFileDialog.getSaveFileName(self, "Esporta Report HTML", suggested_filename, "HTML Files (*.html)")
 
        if not filepath:
 
            return
 
  
 
        try:
 
            html_content = self._generate_html_report()
 
            with open(filepath, 'w', encoding='utf-8') as f:
 
                f.write(html_content)
 
            self.app_state.log(f"Report HTML esportato con successo in: {filepath}", "SUCCESS")
 
        except Exception as e:
 
            self.app_state.log(f"Errore durante l'esportazione del report HTML: {e}", "ERROR")
 
  
 
    def _generate_html_report(self) -> str:
 
        """Crea un singolo file HTML autonomo con un frontmatter di metadati e tutti i grafici."""
 
        report_date = datetime.now().strftime("%Y-%m-%d %H:%M")
 
        full_series = self.results.get("full_series")
 
        if full_series is not None and not full_series.empty:
 
            start_date = full_series.index[0].strftime("%Y-%m-%d")
 
            end_date = full_series.index[-1].strftime("%Y-%m-%d")
 
            data_range_str = f"{start_date} to {end_date}"
 
        else:
 
            data_range_str = "N/A"
 
  
 
        html = f"""
 
        <!DOCTYPE html>
 
        <html>
 
        <head>
 
            <meta charset="utf-8" />
 
            <title>Forecast Backtest Report for {self.ticker}</title>
 
            <script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
 
            <style>
 
                body {{ font-family: sans-serif; margin: 2em; background-color: #f4f4f9; }}
 
                h1, h2 {{ color: #333; }}
 
                .chart-container {{ border: 1px solid #ccc; margin-bottom: 2em; background-color: #fff; }}
 
                .report-meta {{
 
                    border: 1px solid #ccc; padding: 1em; margin-bottom: 2em;
 
                    background-color: #fff; border-radius: 5px;
 
                }}
 
                .report-meta table {{ width: 100%; border-collapse: collapse; }}
 
                .report-meta th, .report-meta td {{ text-align: left; padding: 10px; border-bottom: 1px solid #ddd; }}
 
                .report-meta th {{ width: 30%; font-weight: bold; color: #555; }}
 
            </style>
 
        </head>
 
        <body>
 
            <h1>Forecast Backtest Report for {self.ticker}</h1>
 
            <div class="report-meta">
 
                <h2>Analysis Parameters</h2>
 
                <table>
 
                    <tr><th>Report Date</th><td>{report_date}</td></tr>
 
                    <tr><th>Ticker</th><td>{self.ticker}</td></tr>
 
                    <tr><th>Model Used</th><td>{self.model_name.upper()}</td></tr>
 
                    <tr><th>Historical Data Range</th><td>{data_range_str}</td></tr>
 
                    <tr><th>Initial Training Window</th><td>{self.initial_window} days</td></tr>
 
                    <tr><th>Forecast Horizon</th><td>{self.horizon} days</td></tr>
 
                </table>
 
            </div>
 
  
 
            <div id="chart-forecast" class="chart-container" style="height: 60vh;"></div>
 
            <div id="chart-mae" class="chart-container" style="height: 40vh;"></div>
 
            <div id="chart-rmse" class="chart-container" style="height: 40vh;"></div>
 
            <div id="chart-da" class="chart-container" style="height: 40vh;"></div>
 
  
 
            <script>
 
                const fig_forecast = {self.fig_forecast_json};
 
                Plotly.newPlot('chart-forecast', fig_forecast.data, fig_forecast.layout, {{responsive: true}});
 
                const fig_mae = {self.fig_mae_json};
 
                Plotly.newPlot('chart-mae', fig_mae.data, fig_mae.layout, {{responsive: true}});
 
                const fig_rmse = {self.fig_rmse_json};
 
                Plotly.newPlot('chart-rmse', fig_rmse.data, fig_rmse.layout, {{responsive: true}});
 
                const fig_da = {self.fig_da_json};
 
                Plotly.newPlot('chart-da', fig_da.data, fig_da.layout, {{responsive: true}});
 
            </script>
 
        </body>
 
        </html>
 
        """
 
        return html
 
  
 
    def display_results(self, results: Dict[str, Any], ticker: str):
 
        self.results = results
 
        self.ticker = ticker
 
        self.model_name = results.get("model_name", "Unknown Model") # Aggiunto
 
        self.initial_window = results.get("initial_window", "N/A")
 
        self.horizon = results.get("horizon", "N/A")
 
        self._plot_forecast_vs_actuals()
 
        self._plot_mae_over_time()
 
        self._plot_rmse_over_time()
 
        self._plot_directional_accuracy()
 
  
 
    def _get_fire_annotation(self):
 
        return dict(
 
            text="Generated by FIRE", align='left', showarrow=False,
 
            xref='paper', yref='paper', x=0.99, y=1.05,
 
            font=dict(size=10, color="grey"), opacity=0.7
 
        )
 
  
 
    def _plot_forecast_vs_actuals(self):
 
        fig = go.Figure()
 
        full_series = self.results["full_series"]
 
        fig.add_trace(go.Scatter(
 
            x=full_series.index, y=full_series,
 
            mode='lines', name='Dati Reali', line=dict(color='blue', width=2)
 
        ))
 
  
 
        for i, forecast_df in enumerate(self.results["all_forecasts"]):
 
            fig.add_trace(go.Scatter(
 
                x=forecast_df.index, y=forecast_df['forecast'],
 
                mode='lines', name=f'Forecast Step {i+1}',
 
                line=dict(color='red', width=2), opacity=0.8
 
            ))
 
        fig.update_layout(
 
            title=f"Backtest {self.model_name.upper()} (Rolling-Origin) per {self.ticker}",
 
            legend_title="Serie",
 
            annotations=[self._get_fire_annotation()]
 
        )
 
        self.fig_forecast_json = fig.to_json()
 
        self.forecast_plot_widget.plot(self.fig_forecast_json)
 
  
 
    def _plot_mae_over_time(self):
 
        error_df = pd.DataFrame(self.results["error_history"])
 
        mean_mae = self.results.get("mean_mae", 0.0)
 
        fig = go.Figure()
 
        fig.add_trace(go.Scatter(
 
            x=error_df['date'], y=error_df['mae'],
 
            mode='lines+markers', name="MAE", line=dict(color='green', width=2)
 
        ))
 
        fig.update_layout(
 
            title=f"MAE Over Time (Mean: {mean_mae:.2f})",
 
            xaxis_title="Data di Inizio Previsione",
 
            yaxis_title="Mean Absolute Error",
 
            annotations=[self._get_fire_annotation()]
 
        )
 
        self.fig_mae_json = fig.to_json()
 
        self.mae_plot_widget.plot(self.fig_mae_json)
 
  
 
    def _plot_rmse_over_time(self):
 
        error_df = pd.DataFrame(self.results["error_history"])
 
        mean_rmse = self.results.get("mean_rmse", 0.0)
 
        fig = go.Figure()
 
        fig.add_trace(go.Scatter(
 
            x=error_df['date'], y=error_df['rmse'],
 
            mode='lines+markers', name="RMSE", line=dict(color='orange', width=2)
 
        ))
 
        fig.update_layout(
 
            title=f"RMSE Over Time (Mean: {mean_rmse:.2f})",
 
            xaxis_title="Data di Inizio Previsione",
 
            yaxis_title="Root Mean Square Error",
 
            annotations=[self._get_fire_annotation()]
 
        )
 
        self.fig_rmse_json = fig.to_json()
 
        self.rmse_plot_widget.plot(self.fig_rmse_json)
 
  
 
    def _plot_directional_accuracy(self):
 
        error_df = pd.DataFrame(self.results["error_history"])
 
        mean_da = self.results.get("mean_directional_accuracy", 0.0)
 
        rolling_da = error_df['directional_accuracy'].rolling(window=20, min_periods=1).mean()
 
        fig = go.Figure()
 
        fig.add_trace(go.Scatter(
 
            x=error_df['date'],
 
            y=rolling_da,
 
            mode='lines',
 
            name='Rolling DA (20 periods)',
 
            line=dict(color='purple', width=2)
 
        ))
 
        fig.update_layout(
 
            title=f"Directional Accuracy Over Time (Mean: {mean_da:.2%})",
 
            xaxis_title="Data di Inizio Previsione",
 
            yaxis_title="Accuracy (Rolling Average)",
 
            yaxis=dict(range=[0, 1], tickformat=".0%"),
 
            annotations=[self._get_fire_annotation()]
 
        )
 
        self.fig_da_json = fig.to_json()
 
        self.da_plot_widget.plot(self.fig_da_json)