"""Emitters for ancestry graph in various formats (JSON, Mermaid, GraphML, DOT).

This module provides emitters to export ancestry graphs to different formats
for analysis and visualization.
"""

import json
from datetime import UTC, datetime
from pathlib import Path
from typing import Any

from ...stages.analysis.ancestry_graph import AncestryGraph
from ...stages.normalize.provenance import create_provenance


class AncestryJsonEmitter:
    """Emitter for JSON format ancestry graphs."""

    def emit(
        self,
        graph: AncestryGraph,
        output_path: Path,
        pretty: bool = True,
        include_query_cache: bool = False,
        author: str | None = None,
    ) -> None:
        """Emit ancestry graph to JSON format.

        Args:
            graph: AncestryGraph to emit
            output_path: Output file path
            pretty: Whether to pretty-print JSON
            include_query_cache: Whether to include pre-computed query results
            author: Author name for provenance (will load from config if None)
        """
        timestamp = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
        provenance = create_provenance(author=author, timestamp=timestamp)

        output = {
            "$schema": "https://rpax.io/schemas/xaml-ancestry-graph.json",
            "schema_version": "0.4.0",
            "collected_at": timestamp,
            "provenance": {
                "generated_by": provenance.generated_by,
                "generated_at": provenance.generated_at,
                "generator_url": provenance.generator_url,
                "authors": provenance.authors,
                "license": provenance.license,
                "license_url": provenance.license_url,
            },
            **graph.to_dict(),
        }

        if include_query_cache:
            output["query_cache"] = {}

        # Write to file
        with open(output_path, "w", encoding="utf-8") as f:
            if pretty:
                json.dump(output, f, indent=2, ensure_ascii=False)
            else:
                json.dump(output, f, ensure_ascii=False)


class AncestryMermaidEmitter:
    """Emitter for Mermaid diagram format.

    Generates Mermaid flowchart diagrams showing variable ancestry relationships.
    """

    def emit(
        self,
        graph: AncestryGraph,
        output_path: Path,
        max_nodes: int = 50,
        group_by_workflow: bool = True,
    ) -> None:
        """Emit ancestry graph to Mermaid format.

        Args:
            graph: AncestryGraph to emit
            output_path: Output file path
            max_nodes: Maximum nodes to include (for readability)
            group_by_workflow: Whether to group nodes by workflow in subgraphs
        """
        lines = [
            "graph TB",
            "  %% Ancestry Graph",
            "  %% Generated by xaml-parser",
            "",
        ]

        # Group nodes by workflow if requested
        if group_by_workflow:
            workflow_groups: dict[str, list[str]] = {}
            for node_id, node in graph.nodes.items():
                if node.workflow_id not in workflow_groups:
                    workflow_groups[node.workflow_id] = []
                workflow_groups[node.workflow_id].append(node_id)

            # Emit subgraphs for each workflow
            for wf_id, node_ids in workflow_groups.items():
                if len(lines) > max_nodes + 10:  # Safety limit
                    break

                # Get workflow name
                first_node = graph.nodes[node_ids[0]]
                wf_name = first_node.workflow_name or "Workflow"

                lines.append(f"  subgraph {self._sanitize_id(wf_id)}[{wf_name}]")

                for node_id in node_ids[:max_nodes]:
                    node = graph.nodes[node_id]
                    lines.append(self._format_node(node_id, node))

                lines.append("  end")
                lines.append("")
        else:
            # Emit all nodes without grouping
            for i, (node_id, node) in enumerate(graph.nodes.items()):
                if i >= max_nodes:
                    break
                lines.append(self._format_node(node_id, node))

        lines.append("")

        # Emit edges
        edge_count = 0
        for _edge_id, edge in graph.edges.items():
            if edge_count >= max_nodes * 2:  # Limit edges too
                break

            from_id_safe = self._sanitize_id(edge.from_id)
            to_id_safe = self._sanitize_id(edge.to_id)

            # Format edge label
            label = self._format_edge_label(edge)

            # Choose arrow style based on edge kind
            if edge.kind in ["arg_binding_in", "arg_binding_out"]:
                # Thick arrow for interprocedural
                arrow = "==>"
            elif edge.kind == "assign":
                # Normal arrow for assignment
                arrow = "-->"
            else:
                # Dotted arrow for transformations
                arrow = "-.->"

            lines.append(f"  {from_id_safe} {arrow}|{label}| {to_id_safe}")
            edge_count += 1

        lines.append("")

        # Add styling
        lines.extend(
            [
                "  %% Styling",
                "  classDef variable fill:#e1f5ff,stroke:#01579b,stroke-width:2px",
                "  classDef argument fill:#fff9c4,stroke:#f57f17,stroke-width:2px",
                "  classDef definite stroke:#2e7d32,stroke-width:3px",
                "  classDef possible stroke:#f57c00,stroke-width:2px,stroke-dasharray: 5 5",
                "  classDef unknown stroke:#c62828,stroke-width:2px,stroke-dasharray: 2 2",
                "",
            ]
        )

        # Apply classes to nodes
        for node_id, node in graph.nodes.items():
            node_id_safe = self._sanitize_id(node_id)
            if node.entity_type == "variable":
                lines.append(f"  class {node_id_safe} variable")
            elif node.entity_type == "argument":
                lines.append(f"  class {node_id_safe} argument")

        # Write to file
        with open(output_path, "w", encoding="utf-8") as f:
            f.write("\n".join(lines))

    def _sanitize_id(self, node_id: str) -> str:
        """Sanitize node ID for Mermaid.

        Args:
            node_id: Original node ID

        Returns:
            Sanitized ID safe for Mermaid
        """
        # Replace : and other special chars with underscores
        return node_id.replace(":", "_").replace("-", "_").replace(".", "_")

    def _format_node(self, node_id: str, node: Any) -> str:
        """Format node for Mermaid.

        Args:
            node_id: Node ID
            node: AncestryNode object

        Returns:
            Mermaid node definition line
        """
        node_id_safe = self._sanitize_id(node_id)

        # Format label: name (type)
        type_display = node.type.name if node.type else "?"
        label = f"{node.name}<br/>{type_display}"

        # Choose node shape based on entity type
        if node.entity_type == "variable":
            # Rectangle for variables
            return f'    {node_id_safe}["{label}"]'
        elif node.entity_type == "argument":
            # Rounded rectangle for arguments
            return f'    {node_id_safe}("{label}")'
        else:
            # Default rectangle
            return f'    {node_id_safe}["{label}"]'

    def _format_edge_label(self, edge: Any) -> str:
        """Format edge label for Mermaid.

        Args:
            edge: AncestryEdge object

        Returns:
            Edge label string
        """
        if edge.kind == "arg_binding_in":
            return "In arg"
        elif edge.kind == "arg_binding_out":
            return "Out arg"
        elif edge.kind == "assign":
            return "assign"
        elif edge.transformation:
            # Show transformation operation
            op = edge.transformation.operation
            if op == "dictionary_access":
                key = edge.transformation.details.get("key", "?")
                return f"dict[{key}]"
            elif op == "method_call":
                method = edge.transformation.details.get("method", "?")
                return f".{method}()"
            elif op == "property_access":
                prop = edge.transformation.details.get("property", "?")
                return f".{prop}"
            elif op == "cast":
                return "cast"
            else:
                return op
        else:
            return edge.kind


class AncestryGraphMLEmitter:
    """Emitter for GraphML format (for Gephi, Cytoscape, etc.)."""

    def emit(self, graph: AncestryGraph, output_path: Path) -> None:
        """Emit ancestry graph to GraphML format.

        Args:
            graph: AncestryGraph to emit
            output_path: Output file path
        """
        try:
            import networkx as nx

            if graph.use_networkx:
                # Write directly from NetworkX graph
                nx.write_graphml(graph.graph, output_path)
            else:
                # Convert to NetworkX first
                g = nx.DiGraph()

                for node_id, node in graph.nodes.items():
                    g.add_node(
                        node_id,
                        name=node.name,
                        entity_type=node.entity_type,
                        type=node.type.full_name,
                        workflow=node.workflow_name,
                    )

                for _edge_id, edge in graph.edges.items():
                    g.add_edge(
                        edge.from_id,
                        edge.to_id,
                        kind=edge.kind,
                        confidence=edge.confidence,
                    )

                nx.write_graphml(g, output_path)

        except ImportError as err:
            raise RuntimeError(
                "NetworkX is required for GraphML export. Install with: pip install networkx"
            ) from err


class AncestryDOTEmitter:
    """Emitter for DOT format (for Graphviz)."""

    def emit(
        self,
        graph: AncestryGraph,
        output_path: Path,
        layout: str = "dot",
    ) -> None:
        """Emit ancestry graph to DOT format.

        Args:
            graph: AncestryGraph to emit
            output_path: Output file path
            layout: Layout engine (dot, neato, fdp, circo, twopi)
        """
        lines = [
            "digraph ancestry_graph {",
            f"  layout={layout};",
            "  rankdir=TB;",
            "  node [shape=rectangle, style=filled];",
            "",
        ]

        # Emit nodes with attributes
        for node_id, node in graph.nodes.items():
            node_id_safe = self._sanitize_id(node_id)
            label = f"{node.name}\\n{node.type.name}"

            # Color by entity type
            if node.entity_type == "variable":
                fillcolor = "lightblue"
                shape = "rectangle"
            elif node.entity_type == "argument":
                fillcolor = "lightyellow"
                shape = "oval"
            else:
                fillcolor = "white"
                shape = "rectangle"

            lines.append(
                f'  {node_id_safe} [label="{label}", fillcolor={fillcolor}, shape={shape}];'
            )

        lines.append("")

        # Emit edges
        for _edge_id, edge in graph.edges.items():
            from_id_safe = self._sanitize_id(edge.from_id)
            to_id_safe = self._sanitize_id(edge.to_id)

            # Edge label
            label = edge.kind
            if edge.transformation:
                label += f"\\n{edge.transformation.operation}"

            # Edge style by confidence
            if edge.confidence == "definite":
                style = "solid"
                color = "black"
            elif edge.confidence == "possible":
                style = "dashed"
                color = "orange"
            else:
                style = "dotted"
                color = "red"

            lines.append(
                f'  {from_id_safe} -> {to_id_safe} [label="{label}", style={style}, color={color}];'
            )

        lines.append("}")

        # Write to file
        with open(output_path, "w", encoding="utf-8") as f:
            f.write("\n".join(lines))

    def _sanitize_id(self, node_id: str) -> str:
        """Sanitize node ID for DOT.

        Args:
            node_id: Original node ID

        Returns:
            Sanitized ID
        """
        # Wrap in quotes and escape special chars
        return f'"{node_id}"'
