"""
Farnsworth Visualizations - Advanced Data Visualization Components

Provides interactive visualizations for:
- Memory activation heatmaps
- Knowledge graph exploration
- Evolution fitness timelines
- Agent swarm activity

Novel Features:
- Real-time memory access patterns
- Interactive entity relationship graphs
- Animated evolution progress
- Swarm topology visualization
"""

import json
from datetime import datetime, timedelta
from typing import Optional, Any
from dataclasses import dataclass

try:
    import plotly.graph_objects as go
    import plotly.express as px
    from plotly.subplots import make_subplots
    PLOTLY_AVAILABLE = True
except ImportError:
    PLOTLY_AVAILABLE = False

try:
    import networkx as nx
    NETWORKX_AVAILABLE = True
except ImportError:
    NETWORKX_AVAILABLE = False

from loguru import logger


@dataclass
class VisualizationConfig:
    """Configuration for visualizations."""
    theme: str = "plotly_dark"
    width: int = 800
    height: int = 600
    show_legend: bool = True
    animation_duration: int = 500


class MemoryVisualizer:
    """
    Visualization components for the memory system.

    Provides:
    - Memory access heatmaps
    - Importance distribution charts
    - Temporal access patterns
    - Source distribution
    """

    def __init__(self, config: Optional[VisualizationConfig] = None):
        self.config = config or VisualizationConfig()

    def create_access_heatmap(
        self,
        access_data: list[dict],
        time_buckets: int = 24,
    ) -> Optional[go.Figure]:
        """
        Create a heatmap of memory access patterns.

        Args:
            access_data: List of access records with timestamps
            time_buckets: Number of time buckets for aggregation

        Returns:
            Plotly figure or None if not available
        """
        if not PLOTLY_AVAILABLE:
            return None

        # Aggregate access data by time and source
        # Placeholder implementation
        hours = list(range(24))
        sources = ["Archival", "Conversation", "Knowledge Graph", "Working"]

        # Generate sample data
        import random
        z_data = [[random.randint(0, 100) for _ in hours] for _ in sources]

        fig = go.Figure(data=go.Heatmap(
            z=z_data,
            x=[f"{h}:00" for h in hours],
            y=sources,
            colorscale="Viridis",
            hoverongaps=False,
        ))

        fig.update_layout(
            title="Memory Access Patterns (24h)",
            xaxis_title="Hour of Day",
            yaxis_title="Memory Source",
            template=self.config.theme,
            width=self.config.width,
            height=self.config.height // 2,
        )

        return fig

    def create_importance_distribution(
        self,
        memories: list[dict],
    ) -> Optional[go.Figure]:
        """
        Create a histogram of memory importance scores.

        Args:
            memories: List of memory records with importance scores

        Returns:
            Plotly figure or None
        """
        if not PLOTLY_AVAILABLE:
            return None

        # Extract importance scores or use sample data
        if memories:
            importance_scores = [m.get("importance", 0.5) for m in memories]
        else:
            import random
            importance_scores = [random.random() for _ in range(1000)]

        fig = go.Figure(data=go.Histogram(
            x=importance_scores,
            nbinsx=20,
            marker_color="#667eea",
        ))

        fig.update_layout(
            title="Memory Importance Distribution",
            xaxis_title="Importance Score",
            yaxis_title="Count",
            template=self.config.theme,
            width=self.config.width,
            height=self.config.height // 2,
        )

        return fig

    def create_temporal_view(
        self,
        memories: list[dict],
        days: int = 30,
    ) -> Optional[go.Figure]:
        """
        Create a timeline of memory creation over time.

        Args:
            memories: List of memory records with timestamps
            days: Number of days to show

        Returns:
            Plotly figure or None
        """
        if not PLOTLY_AVAILABLE:
            return None

        # Generate sample timeline data
        import random
        dates = [(datetime.now() - timedelta(days=i)).strftime("%Y-%m-%d")
                 for i in range(days, 0, -1)]
        counts = [random.randint(10, 100) for _ in dates]

        fig = go.Figure(data=go.Scatter(
            x=dates,
            y=counts,
            mode="lines+markers",
            fill="tozeroy",
            marker=dict(color="#667eea"),
            line=dict(color="#667eea", width=2),
        ))

        fig.update_layout(
            title=f"Memory Creation ({days} days)",
            xaxis_title="Date",
            yaxis_title="Memories Created",
            template=self.config.theme,
            width=self.config.width,
            height=self.config.height // 2,
        )

        return fig

    def create_source_pie(
        self,
        source_counts: dict[str, int],
    ) -> Optional[go.Figure]:
        """
        Create a pie chart of memory sources.

        Args:
            source_counts: Dictionary of source -> count

        Returns:
            Plotly figure or None
        """
        if not PLOTLY_AVAILABLE:
            return None

        if not source_counts:
            source_counts = {
                "Archival": 500,
                "Conversation": 350,
                "Knowledge Graph": 250,
                "Working": 147,
            }

        fig = go.Figure(data=go.Pie(
            labels=list(source_counts.keys()),
            values=list(source_counts.values()),
            hole=0.4,
            marker=dict(colors=["#667eea", "#764ba2", "#f093fb", "#f5576c"]),
        ))

        fig.update_layout(
            title="Memory by Source",
            template=self.config.theme,
            width=self.config.width // 2,
            height=self.config.height // 2,
        )

        return fig


class GraphVisualizer:
    """
    Visualization components for the knowledge graph.

    Provides:
    - Interactive node-link diagrams
    - Entity type clustering
    - Relationship exploration
    - Path visualization
    """

    def __init__(self, config: Optional[VisualizationConfig] = None):
        self.config = config or VisualizationConfig()

    def create_network_graph(
        self,
        entities: list[dict],
        relationships: list[dict],
        layout: str = "spring",
    ) -> Optional[go.Figure]:
        """
        Create an interactive network graph visualization.

        Args:
            entities: List of entity records
            relationships: List of relationship records
            layout: Layout algorithm ("spring", "circular", "random")

        Returns:
            Plotly figure or None
        """
        if not PLOTLY_AVAILABLE or not NETWORKX_AVAILABLE:
            return None

        # Create NetworkX graph
        G = nx.Graph()

        # Add nodes
        if entities:
            for entity in entities:
                G.add_node(
                    entity.get("id", entity.get("name")),
                    name=entity.get("name", "Unknown"),
                    entity_type=entity.get("type", "Unknown"),
                )
        else:
            # Sample data
            sample_entities = [
                ("farnsworth", "Farnsworth", "Project"),
                ("memory", "Memory System", "Component"),
                ("evolution", "Evolution", "Feature"),
                ("agents", "Agent Swarm", "Component"),
                ("user", "User", "Person"),
                ("claude", "Claude Code", "Tool"),
            ]
            for eid, name, etype in sample_entities:
                G.add_node(eid, name=name, entity_type=etype)

        # Add edges
        if relationships:
            for rel in relationships:
                G.add_edge(
                    rel.get("source"),
                    rel.get("target"),
                    relation_type=rel.get("type", "related"),
                )
        else:
            # Sample relationships
            sample_rels = [
                ("farnsworth", "memory"),
                ("farnsworth", "evolution"),
                ("farnsworth", "agents"),
                ("user", "farnsworth"),
                ("claude", "farnsworth"),
                ("memory", "agents"),
            ]
            for source, target in sample_rels:
                G.add_edge(source, target)

        # Compute layout
        if layout == "spring":
            pos = nx.spring_layout(G, seed=42)
        elif layout == "circular":
            pos = nx.circular_layout(G)
        else:
            pos = nx.random_layout(G, seed=42)

        # Extract coordinates
        edge_x = []
        edge_y = []
        for edge in G.edges():
            x0, y0 = pos[edge[0]]
            x1, y1 = pos[edge[1]]
            edge_x.extend([x0, x1, None])
            edge_y.extend([y0, y1, None])

        node_x = [pos[node][0] for node in G.nodes()]
        node_y = [pos[node][1] for node in G.nodes()]
        node_text = [G.nodes[node].get("name", node) for node in G.nodes()]

        # Create figure
        fig = go.Figure()

        # Add edges
        fig.add_trace(go.Scatter(
            x=edge_x,
            y=edge_y,
            line=dict(width=1, color="#888"),
            hoverinfo="none",
            mode="lines",
        ))

        # Add nodes
        fig.add_trace(go.Scatter(
            x=node_x,
            y=node_y,
            mode="markers+text",
            hoverinfo="text",
            text=node_text,
            textposition="top center",
            marker=dict(
                size=20,
                color="#667eea",
                line=dict(width=2, color="#fff"),
            ),
        ))

        fig.update_layout(
            title="Knowledge Graph",
            showlegend=False,
            template=self.config.theme,
            width=self.config.width,
            height=self.config.height,
            xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
            yaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
        )

        return fig

    def create_entity_type_chart(
        self,
        type_counts: dict[str, int],
    ) -> Optional[go.Figure]:
        """
        Create a bar chart of entity types.

        Args:
            type_counts: Dictionary of type -> count

        Returns:
            Plotly figure or None
        """
        if not PLOTLY_AVAILABLE:
            return None

        if not type_counts:
            type_counts = {
                "Project": 5,
                "Component": 12,
                "Feature": 18,
                "Person": 8,
                "Tool": 15,
                "Concept": 25,
            }

        fig = go.Figure(data=go.Bar(
            x=list(type_counts.keys()),
            y=list(type_counts.values()),
            marker_color="#667eea",
        ))

        fig.update_layout(
            title="Entities by Type",
            xaxis_title="Entity Type",
            yaxis_title="Count",
            template=self.config.theme,
            width=self.config.width // 2,
            height=self.config.height // 2,
        )

        return fig


class EvolutionVisualizer:
    """
    Visualization components for the evolution system.

    Provides:
    - Fitness timeline
    - Generation progress
    - Pareto front visualization
    - Metric comparison
    """

    def __init__(self, config: Optional[VisualizationConfig] = None):
        self.config = config or VisualizationConfig()

    def create_fitness_timeline(
        self,
        fitness_history: list[dict],
    ) -> Optional[go.Figure]:
        """
        Create a timeline of fitness evolution.

        Args:
            fitness_history: List of fitness records over time

        Returns:
            Plotly figure or None
        """
        if not PLOTLY_AVAILABLE:
            return None

        # Generate sample data if not provided
        if fitness_history:
            generations = [f["generation"] for f in fitness_history]
            fitness_values = [f["fitness"] for f in fitness_history]
        else:
            import random
            generations = list(range(1, 51))
            # Simulate improving fitness with noise
            base_fitness = 0.5
            fitness_values = []
            for i in generations:
                base_fitness = min(0.95, base_fitness + random.uniform(0.001, 0.01))
                fitness_values.append(base_fitness + random.uniform(-0.02, 0.02))

        fig = go.Figure()

        # Best fitness line
        fig.add_trace(go.Scatter(
            x=generations,
            y=fitness_values,
            mode="lines+markers",
            name="Best Fitness",
            line=dict(color="#4caf50", width=2),
            marker=dict(size=6),
        ))

        # Add trend line
        import numpy as np
        z = np.polyfit(generations, fitness_values, 1)
        p = np.poly1d(z)
        fig.add_trace(go.Scatter(
            x=generations,
            y=[p(x) for x in generations],
            mode="lines",
            name="Trend",
            line=dict(color="#ff9800", width=2, dash="dash"),
        ))

        fig.update_layout(
            title="Fitness Evolution Over Generations",
            xaxis_title="Generation",
            yaxis_title="Fitness Score",
            template=self.config.theme,
            width=self.config.width,
            height=self.config.height // 2,
            legend=dict(orientation="h", yanchor="bottom", y=1.02),
        )

        return fig

    def create_metric_radar(
        self,
        metrics: dict[str, float],
    ) -> Optional[go.Figure]:
        """
        Create a radar chart of fitness metrics.

        Args:
            metrics: Dictionary of metric_name -> value (0-1)

        Returns:
            Plotly figure or None
        """
        if not PLOTLY_AVAILABLE:
            return None

        if not metrics:
            metrics = {
                "Task Success": 0.94,
                "User Satisfaction": 0.91,
                "Efficiency": 0.78,
                "Response Quality": 0.85,
                "Memory Utilization": 0.72,
                "Agent Coordination": 0.88,
            }

        categories = list(metrics.keys())
        values = list(metrics.values())

        # Close the radar chart
        categories.append(categories[0])
        values.append(values[0])

        fig = go.Figure()

        fig.add_trace(go.Scatterpolar(
            r=values,
            theta=categories,
            fill="toself",
            fillcolor="rgba(102, 126, 234, 0.3)",
            line=dict(color="#667eea", width=2),
            marker=dict(size=8),
        ))

        fig.update_layout(
            title="Fitness Metrics Overview",
            polar=dict(
                radialaxis=dict(
                    visible=True,
                    range=[0, 1],
                ),
            ),
            template=self.config.theme,
            width=self.config.width // 2,
            height=self.config.height // 2,
        )

        return fig

    def create_pareto_front(
        self,
        population: list[dict],
    ) -> Optional[go.Figure]:
        """
        Create a Pareto front visualization for multi-objective optimization.

        Args:
            population: List of solutions with multiple objective values

        Returns:
            Plotly figure or None
        """
        if not PLOTLY_AVAILABLE:
            return None

        # Generate sample population if not provided
        if not population:
            import random
            population = []
            for i in range(50):
                x = random.random()
                # Create Pareto-like distribution
                y = 1 - x + random.uniform(-0.1, 0.1)
                is_pareto = random.random() > 0.7
                population.append({
                    "task_success": x,
                    "efficiency": max(0, min(1, y)),
                    "is_pareto": is_pareto,
                })

        task_success = [p["task_success"] for p in population]
        efficiency = [p["efficiency"] for p in population]
        colors = ["#4caf50" if p.get("is_pareto", False) else "#667eea" for p in population]

        fig = go.Figure()

        # All solutions
        fig.add_trace(go.Scatter(
            x=task_success,
            y=efficiency,
            mode="markers",
            marker=dict(
                size=10,
                color=colors,
                line=dict(width=1, color="#fff"),
            ),
            hovertemplate="Task Success: %{x:.2f}<br>Efficiency: %{y:.2f}",
        ))

        fig.update_layout(
            title="Pareto Front (Multi-Objective Optimization)",
            xaxis_title="Task Success",
            yaxis_title="Efficiency",
            template=self.config.theme,
            width=self.config.width,
            height=self.config.height // 2,
        )

        return fig

    def create_generation_boxplot(
        self,
        generation_data: list[dict],
    ) -> Optional[go.Figure]:
        """
        Create boxplots showing fitness distribution per generation.

        Args:
            generation_data: List of generation fitness distributions

        Returns:
            Plotly figure or None
        """
        if not PLOTLY_AVAILABLE:
            return None

        # Generate sample data
        import random

        fig = go.Figure()

        for gen in range(1, 11):
            base = 0.5 + gen * 0.03
            values = [base + random.uniform(-0.1, 0.1) for _ in range(20)]

            fig.add_trace(go.Box(
                y=values,
                name=f"Gen {gen}",
                marker_color="#667eea",
            ))

        fig.update_layout(
            title="Fitness Distribution by Generation",
            xaxis_title="Generation",
            yaxis_title="Fitness Score",
            template=self.config.theme,
            width=self.config.width,
            height=self.config.height // 2,
            showlegend=False,
        )

        return fig


class AgentVisualizer:
    """
    Visualization components for agent monitoring.

    Provides:
    - Agent activity timeline
    - Task distribution charts
    - Performance comparison
    - Swarm topology
    """

    def __init__(self, config: Optional[VisualizationConfig] = None):
        self.config = config or VisualizationConfig()

    def create_activity_timeline(
        self,
        activity_logs: list[dict],
    ) -> Optional[go.Figure]:
        """
        Create a Gantt-style timeline of agent activities.

        Args:
            activity_logs: List of activity records

        Returns:
            Plotly figure or None
        """
        if not PLOTLY_AVAILABLE:
            return None

        # Sample activity data
        if not activity_logs:
            now = datetime.now()
            activity_logs = [
                {
                    "agent": "code-001",
                    "task": "Code generation",
                    "start": now - timedelta(minutes=30),
                    "end": now - timedelta(minutes=25),
                },
                {
                    "agent": "reasoning-001",
                    "task": "Analysis",
                    "start": now - timedelta(minutes=28),
                    "end": now - timedelta(minutes=20),
                },
                {
                    "agent": "research-001",
                    "task": "Information gathering",
                    "start": now - timedelta(minutes=22),
                    "end": now - timedelta(minutes=15),
                },
                {
                    "agent": "code-001",
                    "task": "Code review",
                    "start": now - timedelta(minutes=15),
                    "end": now - timedelta(minutes=10),
                },
            ]

        fig = go.Figure()

        colors = {
            "code": "#e3f2fd",
            "reasoning": "#fff3e0",
            "research": "#e8f5e9",
            "creative": "#fce4ec",
        }

        for activity in activity_logs:
            agent_type = activity["agent"].split("-")[0]
            color = colors.get(agent_type, "#f5f5f5")

            fig.add_trace(go.Bar(
                x=[(activity["end"] - activity["start"]).total_seconds() / 60],
                y=[activity["agent"]],
                orientation="h",
                base=[(activity["start"] - datetime.now()).total_seconds() / 60],
                marker_color=color,
                text=activity["task"],
                textposition="inside",
                hovertemplate=f"{activity['task']}<br>Duration: %{{x:.1f}} min",
            ))

        fig.update_layout(
            title="Agent Activity Timeline",
            xaxis_title="Time (minutes ago)",
            yaxis_title="Agent",
            template=self.config.theme,
            width=self.config.width,
            height=self.config.height // 2,
            showlegend=False,
            barmode="stack",
        )

        return fig

    def create_task_distribution(
        self,
        task_counts: dict[str, int],
    ) -> Optional[go.Figure]:
        """
        Create a pie chart of task distribution by agent type.

        Args:
            task_counts: Dictionary of agent_type -> task_count

        Returns:
            Plotly figure or None
        """
        if not PLOTLY_AVAILABLE:
            return None

        if not task_counts:
            task_counts = {
                "Code": 42,
                "Reasoning": 38,
                "Research": 27,
                "Creative": 20,
            }

        fig = go.Figure(data=go.Pie(
            labels=list(task_counts.keys()),
            values=list(task_counts.values()),
            hole=0.4,
            marker=dict(colors=["#1565c0", "#ef6c00", "#2e7d32", "#c2185b"]),
        ))

        fig.update_layout(
            title="Tasks by Agent Type",
            template=self.config.theme,
            width=self.config.width // 2,
            height=self.config.height // 2,
        )

        return fig

    def create_performance_comparison(
        self,
        agent_metrics: list[dict],
    ) -> Optional[go.Figure]:
        """
        Create a grouped bar chart comparing agent performance.

        Args:
            agent_metrics: List of agent performance records

        Returns:
            Plotly figure or None
        """
        if not PLOTLY_AVAILABLE:
            return None

        if not agent_metrics:
            agent_metrics = [
                {"agent": "Code", "success_rate": 96, "avg_time": 1.8},
                {"agent": "Reasoning", "success_rate": 94, "avg_time": 3.2},
                {"agent": "Research", "success_rate": 91, "avg_time": 2.5},
                {"agent": "Creative", "success_rate": 89, "avg_time": 2.1},
            ]

        agents = [m["agent"] for m in agent_metrics]
        success_rates = [m["success_rate"] for m in agent_metrics]
        avg_times = [m["avg_time"] for m in agent_metrics]

        fig = make_subplots(specs=[[{"secondary_y": True}]])

        fig.add_trace(go.Bar(
            name="Success Rate (%)",
            x=agents,
            y=success_rates,
            marker_color="#4caf50",
        ), secondary_y=False)

        fig.add_trace(go.Scatter(
            name="Avg Time (s)",
            x=agents,
            y=avg_times,
            mode="lines+markers",
            line=dict(color="#ff9800", width=3),
            marker=dict(size=10),
        ), secondary_y=True)

        fig.update_layout(
            title="Agent Performance Comparison",
            template=self.config.theme,
            width=self.config.width,
            height=self.config.height // 2,
            legend=dict(orientation="h", yanchor="bottom", y=1.02),
        )

        fig.update_yaxes(title_text="Success Rate (%)", secondary_y=False)
        fig.update_yaxes(title_text="Avg Time (s)", secondary_y=True)

        return fig


def create_dashboard_figures(
    memory_data: Optional[dict] = None,
    graph_data: Optional[dict] = None,
    evolution_data: Optional[dict] = None,
    agent_data: Optional[dict] = None,
) -> dict[str, go.Figure]:
    """
    Create all dashboard figures.

    Args:
        memory_data: Memory system data
        graph_data: Knowledge graph data
        evolution_data: Evolution metrics data
        agent_data: Agent activity data

    Returns:
        Dictionary of figure name -> Plotly figure
    """
    figures = {}

    # Memory visualizations
    mem_viz = MemoryVisualizer()
    figures["memory_heatmap"] = mem_viz.create_access_heatmap([])
    figures["memory_importance"] = mem_viz.create_importance_distribution([])
    figures["memory_timeline"] = mem_viz.create_temporal_view([])
    figures["memory_sources"] = mem_viz.create_source_pie({})

    # Graph visualizations
    graph_viz = GraphVisualizer()
    figures["knowledge_graph"] = graph_viz.create_network_graph([], [])
    figures["entity_types"] = graph_viz.create_entity_type_chart({})

    # Evolution visualizations
    evo_viz = EvolutionVisualizer()
    figures["fitness_timeline"] = evo_viz.create_fitness_timeline([])
    figures["metric_radar"] = evo_viz.create_metric_radar({})
    figures["pareto_front"] = evo_viz.create_pareto_front([])
    figures["generation_boxplot"] = evo_viz.create_generation_boxplot([])

    # Agent visualizations
    agent_viz = AgentVisualizer()
    figures["agent_activity"] = agent_viz.create_activity_timeline([])
    figures["task_distribution"] = agent_viz.create_task_distribution({})
    figures["agent_performance"] = agent_viz.create_performance_comparison([])

    return figures
