"""
Interactive dashboard for real-time cave monitoring and analysis.
Provides web-based interface for stability monitoring and alerts.

Author: Samir Baladi
"""

import numpy as np
from typing import Dict, List, Tuple, Optional, Union
import logging
import json
from datetime import datetime

logger = logging.getLogger(__name__)

try:
    import plotly.graph_objects as go
    from plotly.subplots import make_subplots
    PLOTLY_AVAILABLE = True
except ImportError:
    PLOTLY_AVAILABLE = False
    logger.warning("Plotly not available - dashboard functionality limited")

try:
    import dash
    from dash import dcc, html
    DASH_AVAILABLE = True
except ImportError:
    DASH_AVAILABLE = False
    logger.warning("Dash not available - web dashboard disabled")


class Dashboard:
    """
    Interactive dashboard for cave monitoring and stability assessment.
    """
    
    def __init__(self, title: str = "CAVORA Cave Monitoring Dashboard"):
        """
        Initialize dashboard.
        
        Args:
            title: Dashboard title
        """
        self.title = title
        self.data = {}
        self.alerts = []
        self.app = None
        logger.info(f"Initialized Dashboard: {title}")

    def add_data_source(self, name: str, data: Dict) -> None:
        """
        Add a data source to dashboard.
        
        Args:
            name: Data source name
            data: Data dictionary
        """
        self.data[name] = data
        logger.debug(f"Added data source: {name}")

    def add_alert(self, 
                  level: str,
                  message: str,
                  location: str,
                  timestamp: Optional[float] = None) -> None:
        """
        Add an alert to dashboard.
        
        Args:
            level: Alert level (INFO, WARNING, CRITICAL)
            message: Alert message
            location: Alert location
            timestamp: Alert time (uses current if None)
        """
        if timestamp is None:
            timestamp = datetime.now().timestamp()
        
        alert = {
            'level': level,
            'message': message,
            'location': location,
            'timestamp': timestamp,
            'color': self._get_alert_color(level)
        }
        
        self.alerts.append(alert)
        logger.warning(f"Dashboard alert: {level} - {message}")

    def _get_alert_color(self, level: str) -> str:
        """Get color for alert level."""
        colors = {
            'INFO': 'blue',
            'WARNING': 'orange',
            'CRITICAL': 'red',
            'EMERGENCY': 'purple'
        }
        return colors.get(level, 'gray')

    def create_stability_gauge(self, 
                              score: float,
                              title: str = "Current Stability") -> Optional[go.Figure]:
        """
        Create a gauge chart for stability score.
        
        Args:
            score: Stability score (0-100)
            title: Chart title
            
        Returns:
            Plotly figure or None if plotly unavailable
        """
        if not PLOTLY_AVAILABLE:
            return None
        
        # Determine color based on score
        if score >= 80:
            color = "green"
        elif score >= 60:
            color = "yellow"
        elif score >= 40:
            color = "orange"
        elif score >= 20:
            color = "red"
        else:
            color = "darkred"
        
        fig = go.Figure(go.Indicator(
            mode="gauge+number",
            value=score,
            title={'text': title},
            domain={'x': [0, 1], 'y': [0, 1]},
            gauge={
                'axis': {'range': [0, 100]},
                'bar': {'color': color},
                'steps': [
                    {'range': [0, 20], 'color': 'darkred'},
                    {'range': [20, 40], 'color': 'red'},
                    {'range': [40, 60], 'color': 'orange'},
                    {'range': [60, 80], 'color': 'yellow'},
                    {'range': [80, 100], 'color': 'lightgreen'}
                ],
                'threshold': {
                    'line': {'color': "black", 'width': 4},
                    'thickness': 0.75,
                    'value': score
                }
            }
        ))
        
        fig.update_layout(height=300)
        return fig

    def create_time_series_dashboard(self,
                                    times: np.ndarray,
                                    values_dict: Dict[str, np.ndarray],
                                    title: str = "Time Series") -> Optional[go.Figure]:
        """
        Create multi-panel time series dashboard.
        
        Args:
            times: Time array
            values_dict: Dictionary of variable name to values
            title: Plot title
            
        Returns:
            Plotly figure or None if plotly unavailable
        """
        if not PLOTLY_AVAILABLE:
            return None
        
        n_plots = len(values_dict)
        fig = make_subplots(
            rows=n_plots, 
            cols=1,
            subplot_titles=list(values_dict.keys()),
            shared_xaxes=True
        )
        
        for i, (name, values) in enumerate(values_dict.items(), 1):
            fig.add_trace(
                go.Scatter(x=times, y=values, mode='lines', name=name),
                row=i, col=1
            )
        
        fig.update_layout(
            height=300 * n_plots,
            title_text=title,
            showlegend=False
        )
        
        fig.update_xaxes(title_text="Time", row=n_plots, col=1)
        
        return fig

    def create_3d_passage_view(self,
                              x: np.ndarray,
                              y: np.ndarray,
                              z: np.ndarray,
                              color_values: Optional[np.ndarray] = None,
                              title: str = "3D Passage View") -> Optional[go.Figure]:
        """
        Create 3D view of cave passage.
        
        Args:
            x, y, z: Coordinate arrays
            color_values: Values for coloring (e.g., stability)
            title: Plot title
            
        Returns:
            Plotly figure or None if plotly unavailable
        """
        if not PLOTLY_AVAILABLE:
            return None
        
        if color_values is None:
            color_values = z  # Color by depth
        
        fig = go.Figure(data=[
            go.Scatter3d(
                x=x, y=y, z=z,
                mode='markers',
                marker=dict(
                    size=3,
                    color=color_values,
                    colorscale='Viridis',
                    showscale=True,
                    colorbar=dict(title="Value")
                ),
                text=[f"({xi:.1f}, {yi:.1f}, {zi:.1f})" for xi, yi, zi in zip(x, y, z)]
            )
        ])
        
        fig.update_layout(
            title=title,
            scene=dict(
                xaxis_title='X (m)',
                yaxis_title='Y (m)',
                zaxis_title='Z (m)'
            ),
            height=600
        )
        
        return fig

    def create_risk_matrix(self,
                          risks: Dict[str, float],
                          title: str = "Risk Assessment Matrix") -> Optional[go.Figure]:
        """
        Create risk matrix visualization.
        
        Args:
            risks: Dictionary of risk categories to scores
            title: Plot title
            
        Returns:
            Plotly figure or None if plotly unavailable
        """
        if not PLOTLY_AVAILABLE:
            return None
        
        categories = list(risks.keys())
        scores = list(risks.values())
        
        colors = []
        for score in scores:
            if score >= 0.8:
                colors.append('red')
            elif score >= 0.6:
                colors.append('orange')
            elif score >= 0.4:
                colors.append('yellow')
            else:
                colors.append('green')
        
        fig = go.Figure(data=[
            go.Bar(
                x=categories,
                y=scores,
                marker_color=colors,
                text=[f"{s:.0%}" for s in scores],
                textposition='outside'
            )
        ])
        
        fig.update_layout(
            title=title,
            yaxis=dict(
                title="Risk Level",
                range=[0, 1],
                tickformat='.0%'
            ),
            height=400
        )
        
        return fig

    def create_alerts_panel(self, max_alerts: int = 10) -> Optional[go.Figure]:
        """
        Create alerts panel visualization.
        
        Args:
            max_alerts: Maximum number of alerts to show
            
        Returns:
            Plotly figure or None if plotly unavailable
        """
        if not PLOTLY_AVAILABLE or not self.alerts:
            return None
        
        recent = sorted(self.alerts, key=lambda x: x['timestamp'], reverse=True)[:max_alerts]
        
        # Format timestamps
        times = []
        messages = []
        colors = []
        
        for alert in recent:
            dt = datetime.fromtimestamp(alert['timestamp'])
            times.append(dt.strftime('%Y-%m-%d %H:%M'))
            messages.append(f"{alert['location']}: {alert['message']}")
            colors.append(alert['color'])
        
        fig = go.Figure(data=[
            go.Table(
                header=dict(
                    values=['Time', 'Alert', 'Level'],
                    fill_color='paleturquoise',
                    align='left'
                ),
                cells=dict(
                    values=[times, messages, [a['level'] for a in recent]],
                    fill_color=[['white'] * len(recent), ['white'] * len(recent), colors],
                    align='left'
                )
            )
        ])
        
        fig.update_layout(
            title="Recent Alerts",
            height=400
        )
        
        return fig

    def create_dashboard_html(self, filename: str = "dashboard.html") -> None:
        """
        Create HTML dashboard file.
        
        Args:
            filename: Output HTML filename
        """
        if not DASH_AVAILABLE:
            logger.error("Dash not available - cannot create HTML dashboard")
            return
        
        # Create Dash app
        app = dash.Dash(__name__)
        
        # Build layout
        app.layout = html.Div([
            html.H1(self.title, style={'textAlign': 'center'}),
            
            html.Div([
                html.H2("Current Status"),
                html.Div(id='status-indicators')
            ]),
            
            html.Div([
                html.H2("Stability Overview"),
                dcc.Graph(id='stability-gauge')
            ]),
            
            html.Div([
                html.H2("Recent Alerts"),
                html.Div(id='alerts-list')
            ]),
            
            html.Div([
                html.H2("Time Series Data"),
                dcc.Graph(id='time-series')
            ]),
            
            dcc.Interval(
                id='interval-component',
                interval=60*1000,  # Update every minute
                n_intervals=0
            )
        ])
        
        # Save to file
        app.layout.to_plotly_json()
        
        with open(filename, 'w') as f:
            f.write(str(app.layout))
        
        logger.info(f"Dashboard saved to {filename}")

    def export_to_json(self, filename: str) -> None:
        """
        Export dashboard data to JSON.
        
        Args:
            filename: Output filename
        """
        export_data = {
            'title': self.title,
            'timestamp': datetime.now().timestamp(),
            'data_sources': list(self.data.keys()),
            'alerts': self.alerts[-50:],  # Last 50 alerts
            'summary': self.get_summary()
        }
        
        with open(filename, 'w') as f:
            json.dump(export_data, f, indent=2)
        
        logger.info(f"Dashboard data exported to {filename}")

    def get_summary(self) -> Dict:
        """Get dashboard summary."""
        alert_counts = {'INFO': 0, 'WARNING': 0, 'CRITICAL': 0, 'EMERGENCY': 0}
        for alert in self.alerts:
            alert_counts[alert['level']] = alert_counts.get(alert['level'], 0) + 1
        
        return {
            'total_alerts': len(self.alerts),
            'alert_counts': alert_counts,
            'data_sources': len(self.data),
            'last_update': datetime.now().isoformat()
        }


# Example usage
if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    
    print("="*60)
    print("CAVORA - Dashboard")
    print("="*60)
    
    dashboard = Dashboard()
    
    # Add some alerts
    dashboard.add_alert("INFO", "Survey started", "Main Passage")
    dashboard.add_alert("WARNING", "CO₂ levels elevated", "Chamber B")
    dashboard.add_alert("CRITICAL", "Rapid water level rise", "Sump Area")
    
    # Create stability gauge
    if PLOTLY_AVAILABLE:
        gauge = dashboard.create_stability_gauge(75)
        print("\nCreated stability gauge")
    
    # Export data
    dashboard.export_to_json("dashboard_data.json")
    
    print("\n" + "="*60)
