"""Structural pattern analyzer orchestrator.

Coordinates pattern detection across all detectors.
Uses Rust + cuGraph backends - no NetworkX dependency.
"""

import time
from typing import Dict, List, Any, Optional, Tuple

from loguru import logger

from chainswarm_analyzers_baseline.config import SettingsLoader
from chainswarm_analyzers_baseline.patterns.base_detector import BasePatternDetector
from chainswarm_analyzers_baseline.patterns.detectors import (
    CycleDetector,
    LayeringDetector,
    NetworkDetector,
    ProximityDetector,
    MotifDetector,
    BurstDetector,
    ThresholdDetector,
)
from chainswarm_analyzers_baseline.graph.builder import GraphData, build_money_flow_graph


class StructuralPatternAnalyzer:
    """Orchestrates pattern detection across all detectors."""

    def __init__(
        self,
        settings_loader: SettingsLoader,
        network: str,
        detectors: Optional[List[BasePatternDetector]] = None,
    ):
        self.network = network
        self.config = settings_loader.load(network)

        if detectors is None:
            self.detectors = self._create_default_detectors()
        else:
            self.detectors = detectors

        logger.info(
            f"Initialized StructuralPatternAnalyzer with {len(self.detectors)} detectors for {network}"
        )

    def _create_default_detectors(self) -> List[BasePatternDetector]:
        return [
            CycleDetector(
                config=self.config,
                address_labels_cache={},
                network=self.network
            ),
            LayeringDetector(
                config=self.config,
                address_labels_cache={},
                network=self.network
            ),
            NetworkDetector(
                config=self.config,
                address_labels_cache={},
                network=self.network
            ),
            ProximityDetector(
                config=self.config,
                address_labels_cache={},
                network=self.network
            ),
            MotifDetector(
                config=self.config,
                address_labels_cache={},
                network=self.network
            ),
            BurstDetector(
                config=self.config,
                address_labels_cache={},
                network=self.network
            ),
            ThresholdDetector(
                config=self.config,
                address_labels_cache={},
                network=self.network
            ),
        ]

    def analyze(
        self,
        money_flows: List[Dict[str, Any]],
        address_labels: Dict[str, Dict[str, Any]],
        timestamp_data: Dict[str, List[Dict[str, Any]]],
        window_days: int,
        processing_date: str
    ) -> List[Dict[str, Any]]:
        if not money_flows:
            raise ValueError("No money flows provided for pattern analysis")

        logger.info(f"Building graph from {len(money_flows)} money flows")
        graph_data = build_money_flow_graph(money_flows)

        if graph_data.graph.node_count() == 0:
            raise ValueError("Empty graph built from money flows")

        logger.info(
            f"Graph built: {graph_data.graph.node_count()} nodes, "
            f"{graph_data.graph.edge_count()} edges"
        )

        return self.analyze_graph(
            graph_data, address_labels, timestamp_data, window_days, processing_date
        )

    def analyze_graph(
        self,
        graph_data: GraphData,
        address_labels: Dict[str, Dict[str, Any]],
        timestamp_data: Dict[str, List[Dict[str, Any]]],
        window_days: int,
        processing_date: str
    ) -> List[Dict[str, Any]]:
        all_patterns = []

        if graph_data.graph.node_count() == 0:
            raise ValueError("Empty graph, no patterns to detect")

        logger.info(
            f"Starting pattern analysis on graph with "
            f"{graph_data.graph.node_count()} nodes, {graph_data.graph.edge_count()} edges"
        )

        for detector in self.detectors:
            detector_name = detector.__class__.__name__

            try:
                logger.info(f"Running {detector_name}")

                if isinstance(detector, BurstDetector):
                    patterns = detector.detect(
                        graph_data, address_labels, window_days, processing_date, timestamp_data
                    )
                else:
                    patterns = detector.detect(
                        graph_data, address_labels, window_days, processing_date
                    )

                if patterns:
                    all_patterns.extend(patterns)
                    logger.info(f"{detector_name}: {len(patterns)} patterns")

            except Exception as e:
                logger.error(f"{detector_name} failed: {e}")
                raise

        logger.info(f"Total patterns detected: {len(all_patterns)}")
        return all_patterns

    def analyze_with_config(
        self,
        graph_data: GraphData,
        address_labels: Dict[str, Dict[str, Any]],
        timestamp_data: Dict[str, List[Dict[str, Any]]],
        config: Dict[str, Any]
    ) -> List[Dict[str, Any]]:
        window_days = config.get('window_days', 30)
        processing_date = config.get('processing_date', '')

        return self.analyze_graph(
            graph_data=graph_data,
            address_labels=address_labels,
            timestamp_data=timestamp_data,
            window_days=window_days,
            processing_date=processing_date
        )

    def get_detector_names(self) -> List[str]:
        return [d.__class__.__name__ for d in self.detectors]

    def add_detector(self, detector: BasePatternDetector) -> None:
        self.detectors.append(detector)
        logger.info(f"Added detector: {detector.__class__.__name__}")

    def remove_detector(self, detector_name: str) -> bool:
        for i, detector in enumerate(self.detectors):
            if detector.__class__.__name__ == detector_name:
                self.detectors.pop(i)
                logger.info(f"Removed detector: {detector_name}")
                return True
        return False

    def analyze_with_timing(
        self,
        money_flows: List[Dict[str, Any]],
        address_labels: Dict[str, Dict[str, Any]],
        timestamp_data: Dict[str, List[Dict[str, Any]]],
        window_days: int,
        processing_date: str
    ) -> Tuple[List[Dict[str, Any]], Dict[str, float]]:
        """
        Analyze patterns with per-detector timing instrumentation.

        Args:
            money_flows: List of money flow records
            address_labels: Dict of address -> label info
            timestamp_data: Dict of address -> timestamp records for burst detection
            window_days: Analysis window in days
            processing_date: Processing date string (YYYY-MM-DD)

        Returns:
            Tuple of (patterns_list, detector_timing_dict)
            detector_timing_dict maps detector name -> seconds
        """
        if not money_flows:
            raise ValueError("No money flows provided for pattern analysis")

        logger.info(f"Building graph from {len(money_flows)} money flows")
        graph_data = build_money_flow_graph(money_flows)

        if graph_data.graph.node_count() == 0:
            raise ValueError("Empty graph built from money flows")

        logger.info(
            f"Graph built: {graph_data.graph.node_count()} nodes, "
            f"{graph_data.graph.edge_count()} edges"
        )

        return self.analyze_graph_with_timing(
            graph_data, address_labels, timestamp_data, window_days, processing_date
        )

    def analyze_graph_with_timing(
        self,
        graph_data: GraphData,
        address_labels: Dict[str, Dict[str, Any]],
        timestamp_data: Dict[str, List[Dict[str, Any]]],
        window_days: int,
        processing_date: str
    ) -> Tuple[List[Dict[str, Any]], Dict[str, float]]:
        """
        Analyze graph patterns with per-detector timing.

        Returns:
            Tuple of (all_patterns, detector_timing)
        """
        all_patterns = []
        detector_timing: Dict[str, float] = {}

        if graph_data.graph.node_count() == 0:
            raise ValueError("Empty graph, no patterns to detect")

        logger.info(
            f"Starting timed pattern analysis on graph with "
            f"{graph_data.graph.node_count()} nodes, {graph_data.graph.edge_count()} edges"
        )

        for detector in self.detectors:
            detector_name = detector.__class__.__name__

            try:
                start_time = time.time()

                logger.info(f"Running {detector_name}")

                if isinstance(detector, BurstDetector):
                    patterns = detector.detect(
                        graph_data, address_labels, window_days, processing_date, timestamp_data
                    )
                else:
                    patterns = detector.detect(
                        graph_data, address_labels, window_days, processing_date
                    )

                elapsed = time.time() - start_time
                detector_timing[detector_name] = elapsed

                if patterns:
                    all_patterns.extend(patterns)
                    logger.info(f"{detector_name}: {len(patterns)} patterns in {elapsed:.2f}s")
                else:
                    logger.info(f"{detector_name}: 0 patterns in {elapsed:.2f}s")

            except Exception as e:
                elapsed = time.time() - start_time
                detector_timing[detector_name] = elapsed
                logger.error(f"{detector_name} failed after {elapsed:.2f}s: {e}")
                raise

        logger.info(f"Total patterns detected: {len(all_patterns)}")
        return all_patterns, detector_timing
