"""
Knowledge Graph Relationships Module

Manages relationships between knowledge base entries across domains.
Provides graph traversal, lookup, and visualization capabilities.
"""

import csv
from pathlib import Path
from typing import Dict, List, Set, Tuple, Optional
from collections import defaultdict
from dataclasses import dataclass


@dataclass
class Relationship:
    """Represents a relationship between two knowledge entries"""
    source_domain: str
    source_id: str
    relationship_type: str
    target_domain: str
    target_id: str
    strength: int  # 1-10 scale
    context: str
    bidirectional: bool
    notes: str

    def to_dict(self) -> Dict:
        """Convert to dictionary representation"""
        return {
            'source_domain': self.source_domain,
            'source_id': self.source_id,
            'relationship_type': self.relationship_type,
            'target_domain': self.target_domain,
            'target_id': self.target_id,
            'strength': self.strength,
            'context': self.context,
            'bidirectional': self.bidirectional,
            'notes': self.notes
        }

    def __repr__(self):
        return f"{self.source_domain}:{self.source_id} --[{self.relationship_type}]--> {self.target_domain}:{self.target_id}"


class RelationshipGraph:
    """
    Knowledge graph for managing relationships between entries

    Features:
    - O(1) forward/reverse/type lookups via indexes
    - BFS traversal for related entries
    - Strength-based filtering
    - Bidirectional edge support
    - Graph visualization export (Mermaid, Graphviz)
    """

    def __init__(self, csv_path: Optional[Path] = None):
        """
        Initialize the relationship graph

        Args:
            csv_path: Path to relationships.csv file. If None, uses default location.
        """
        self.relationships: List[Relationship] = []

        # Indexes for O(1) lookup
        self.forward_index: Dict[Tuple[str, str], List[Relationship]] = defaultdict(list)
        self.reverse_index: Dict[Tuple[str, str], List[Relationship]] = defaultdict(list)
        self.type_index: Dict[str, List[Relationship]] = defaultdict(list)

        if csv_path is None:
            # Default to .shared/stacks-agent/data/relationships.csv
            script_dir = Path(__file__).parent
            csv_path = script_dir.parent / 'data' / 'relationships.csv'

        self.csv_path = csv_path

        if csv_path.exists():
            self.load_from_csv(csv_path)

    def load_from_csv(self, csv_path: Path):
        """
        Load relationships from CSV file

        CSV Schema:
        source_domain,source_id,relationship_type,target_domain,target_id,strength,context,bidirectional,notes
        """
        with open(csv_path, 'r', encoding='utf-8') as f:
            reader = csv.DictReader(f)
            for row in reader:
                # Parse bidirectional as boolean
                bidirectional = row['bidirectional'].lower() in ('true', '1', 'yes')

                rel = Relationship(
                    source_domain=row['source_domain'],
                    source_id=row['source_id'],
                    relationship_type=row['relationship_type'],
                    target_domain=row['target_domain'],
                    target_id=row['target_id'],
                    strength=int(row['strength']),
                    context=row['context'],
                    bidirectional=bidirectional,
                    notes=row.get('notes', '')
                )

                self.add_relationship(rel)

    def add_relationship(self, rel: Relationship):
        """
        Add a relationship to the graph and update indexes

        Args:
            rel: Relationship object to add
        """
        self.relationships.append(rel)

        # Update forward index (source -> targets)
        source_key = (rel.source_domain, rel.source_id)
        self.forward_index[source_key].append(rel)

        # Update reverse index (target -> sources)
        target_key = (rel.target_domain, rel.target_id)
        self.reverse_index[target_key].append(rel)

        # Update type index
        self.type_index[rel.relationship_type].append(rel)

        # If bidirectional, add reverse relationship
        if rel.bidirectional:
            reverse_rel = Relationship(
                source_domain=rel.target_domain,
                source_id=rel.target_id,
                relationship_type=rel.relationship_type,
                target_domain=rel.source_domain,
                target_id=rel.source_id,
                strength=rel.strength,
                context=rel.context,
                bidirectional=False,  # Don't double-add
                notes=f"Reverse of: {rel.notes}"
            )
            self.relationships.append(reverse_rel)
            self.forward_index[(reverse_rel.source_domain, reverse_rel.source_id)].append(reverse_rel)
            self.reverse_index[(reverse_rel.target_domain, reverse_rel.target_id)].append(reverse_rel)
            self.type_index[reverse_rel.relationship_type].append(reverse_rel)

    def get_related(
        self,
        domain: str,
        entry_id: str,
        direction: str = 'forward',
        min_strength: int = 7,
        relationship_types: Optional[List[str]] = None
    ) -> List[Relationship]:
        """
        Get related entries for a given entry

        Args:
            domain: Domain of the source entry
            entry_id: ID of the source entry
            direction: 'forward' (outgoing), 'reverse' (incoming), or 'both'
            min_strength: Minimum relationship strength (1-10)
            relationship_types: Optional list of relationship types to filter by

        Returns:
            List of related Relationship objects
        """
        key = (domain, entry_id)
        related = []

        if direction in ('forward', 'both'):
            related.extend(self.forward_index.get(key, []))

        if direction in ('reverse', 'both'):
            related.extend(self.reverse_index.get(key, []))

        # Filter by strength
        related = [r for r in related if r.strength >= min_strength]

        # Filter by relationship type if specified
        if relationship_types:
            related = [r for r in related if r.relationship_type in relationship_types]

        return related

    def get_by_type(self, relationship_type: str) -> List[Relationship]:
        """
        Get all relationships of a specific type

        Args:
            relationship_type: Type of relationship to retrieve

        Returns:
            List of Relationship objects
        """
        return self.type_index.get(relationship_type, [])

    def traverse_bfs(
        self,
        start_domain: str,
        start_id: str,
        max_depth: int = 2,
        min_strength: int = 7
    ) -> Dict[int, List[Tuple[str, str]]]:
        """
        Breadth-first traversal of the graph

        Args:
            start_domain: Starting domain
            start_id: Starting entry ID
            max_depth: Maximum traversal depth
            min_strength: Minimum relationship strength

        Returns:
            Dict mapping depth level to list of (domain, id) tuples
        """
        visited = set()
        result = defaultdict(list)
        queue = [((start_domain, start_id), 0)]  # (node, depth)

        while queue:
            (domain, entry_id), depth = queue.pop(0)

            if (domain, entry_id) in visited or depth > max_depth:
                continue

            visited.add((domain, entry_id))
            result[depth].append((domain, entry_id))

            # Get related entries
            related = self.get_related(domain, entry_id, direction='forward', min_strength=min_strength)

            for rel in related:
                next_node = (rel.target_domain, rel.target_id)
                if next_node not in visited:
                    queue.append((next_node, depth + 1))

        return dict(result)

    def get_statistics(self) -> Dict:
        """
        Get graph statistics

        Returns:
            Dictionary with graph statistics
        """
        unique_nodes = set()
        for rel in self.relationships:
            unique_nodes.add((rel.source_domain, rel.source_id))
            unique_nodes.add((rel.target_domain, rel.target_id))

        type_counts = {
            rel_type: len(rels)
            for rel_type, rels in self.type_index.items()
        }

        strength_distribution = defaultdict(int)
        for rel in self.relationships:
            strength_distribution[rel.strength] += 1

        return {
            'total_relationships': len(self.relationships),
            'unique_nodes': len(unique_nodes),
            'relationship_types': len(self.type_index),
            'type_counts': type_counts,
            'strength_distribution': dict(strength_distribution)
        }

    def export_mermaid(self, max_relationships: int = 50) -> str:
        """
        Export graph to Mermaid diagram format

        Args:
            max_relationships: Maximum relationships to include

        Returns:
            Mermaid diagram syntax string
        """
        lines = ["graph LR"]

        # Sort by strength (descending) and take top N
        sorted_rels = sorted(self.relationships, key=lambda r: r.strength, reverse=True)
        sorted_rels = sorted_rels[:max_relationships]

        for rel in sorted_rels:
            source = f"{rel.source_domain}_{rel.source_id}"
            target = f"{rel.target_domain}_{rel.target_id}"
            label = f"{rel.relationship_type}({rel.strength})"

            lines.append(f"    {source}[{rel.source_domain}:{rel.source_id}]")
            lines.append(f"    {target}[{rel.target_domain}:{rel.target_id}]")
            lines.append(f"    {source} -->|{label}| {target}")

        return "\n".join(lines)

    def export_graphviz(self, max_relationships: int = 50) -> str:
        """
        Export graph to Graphviz DOT format

        Args:
            max_relationships: Maximum relationships to include

        Returns:
            Graphviz DOT syntax string
        """
        lines = ["digraph KnowledgeGraph {"]
        lines.append('    rankdir=LR;')
        lines.append('    node [shape=box];')

        # Sort by strength and take top N
        sorted_rels = sorted(self.relationships, key=lambda r: r.strength, reverse=True)
        sorted_rels = sorted_rels[:max_relationships]

        nodes = set()
        for rel in sorted_rels:
            source = f"{rel.source_domain}_{rel.source_id}"
            target = f"{rel.target_domain}_{rel.target_id}"

            nodes.add((source, f"{rel.source_domain}:{rel.source_id}"))
            nodes.add((target, f"{rel.target_domain}:{rel.target_id}"))

        # Add nodes
        for node_id, node_label in nodes:
            lines.append(f'    {node_id} [label="{node_label}"];')

        # Add edges
        for rel in sorted_rels:
            source = f"{rel.source_domain}_{rel.source_id}"
            target = f"{rel.target_domain}_{rel.target_id}"
            label = f"{rel.relationship_type}\\n(strength: {rel.strength})"

            lines.append(f'    {source} -> {target} [label="{label}"];')

        lines.append("}")
        return "\n".join(lines)


# Global instance (lazy loaded)
_graph_instance = None


def get_graph() -> RelationshipGraph:
    """
    Get the global RelationshipGraph instance (singleton pattern)

    Returns:
        RelationshipGraph instance
    """
    global _graph_instance
    if _graph_instance is None:
        _graph_instance = RelationshipGraph()
    return _graph_instance


def reset_graph():
    """Reset the global graph instance (useful for testing)"""
    global _graph_instance
    _graph_instance = None


if __name__ == '__main__':
    # Test the module
    graph = get_graph()
    stats = graph.get_statistics()

    print("Knowledge Graph Statistics:")
    print(f"  Total relationships: {stats['total_relationships']}")
    print(f"  Unique nodes: {stats['unique_nodes']}")
    print(f"  Relationship types: {stats['relationship_types']}")
    print("\nRelationship type counts:")
    for rel_type, count in stats['type_counts'].items():
        print(f"  {rel_type}: {count}")

    # Example: Get related entries
    if stats['total_relationships'] > 0:
        print("\nExample: Related entries (if any exist)")
        # This would need actual data to demonstrate
