"""Temporal burst pattern detector.

Uses Rust (petgraph + rayon) for parallel burst detection.
No NetworkX dependency.
"""

from typing import Dict, List, Any, Optional

import numpy as np
from loguru import logger

from chainswarm_analyzers_baseline.graph.builder import GraphData
from chainswarm_analyzers_baseline.patterns.base_detector import (
    BasePatternDetector,
    PatternType,
    DetectionMethod,
    Severity,
    generate_pattern_hash,
    generate_pattern_id,
)

# Import Rust backend for burst detection
try:
    from chainswarm_analyzers_baseline._rust import RustGraph
    # Check if both Rust methods exist
    if not (hasattr(RustGraph, 'find_chain_bursts') and hasattr(RustGraph, 'detect_temporal_bursts')):
        raise ImportError("Rust backend missing required burst detection methods")
except ImportError as e:
    raise ImportError(f"Rust backend required for BurstDetector: {e}")


class BurstDetector(BasePatternDetector):
    """Detect temporal burst patterns using Rust backend."""

    @property
    def pattern_type(self) -> str:
        return PatternType.TEMPORAL_BURST

    def detect(
        self,
        graph_data: GraphData,
        address_labels: Dict[str, Dict[str, Any]],
        window_days: int,
        processing_date: str,
        timestamp_data: Dict[str, List[Dict[str, Any]]]
    ) -> List[Dict[str, Any]]:
        self._address_labels_cache = address_labels
        self._graph_data = graph_data

        graph = graph_data.graph

        if graph.node_count() == 0:
            return []

        patterns_by_hash = {}

        min_burst_intensity = self._get_config_value(
            'burst_detection', 'min_burst_intensity', 3.0
        )
        min_burst_transactions = self._get_config_value(
            'burst_detection', 'min_burst_transactions', 10
        )
        time_window_seconds = self._get_config_value(
            'burst_detection', 'time_window_seconds', 3600
        )
        z_score_threshold = self._get_config_value(
            'burst_detection', 'z_score_threshold', 2.0
        )

        # Use Rust for parallel node-level burst detection
        logger.debug(f"Starting node-level burst detection for {graph.node_count()} nodes")

        # Prepare data for Rust
        addresses = []
        timestamps_list = []
        volumes_list = []
        counterparties_list = []

        for node in graph.nodes():
            node_data = timestamp_data.get(node, [])
            if not node_data:
                continue
            addresses.append(node)
            timestamps_list.append([t['timestamp'] for t in node_data])
            volumes_list.append([t['volume'] for t in node_data])
            counterparties_list.append([t['counterparty'] for t in node_data])

        logger.debug(f"Prepared {len(addresses)} addresses for burst detection")

        # Call Rust for parallel burst detection
        rust_graph = RustGraph()
        time_window_ms = time_window_seconds * 1000

        bursts = rust_graph.detect_temporal_bursts(
            addresses,
            timestamps_list,
            volumes_list,
            counterparties_list,
            time_window_ms,
            min_burst_transactions,
            min_burst_intensity,
            z_score_threshold
        )

        logger.debug(f"Detected {len(bursts)} node-level bursts")

        # Convert Rust results to patterns
        for addr, start_ms, end_ms, tx_count, volume, intensity, z_score, counterparties in bursts:
            pattern_hash = generate_pattern_hash(
                PatternType.TEMPORAL_BURST,
                [addr, str(start_ms)]
            )

            if pattern_hash in patterns_by_hash:
                continue

            pattern_id = generate_pattern_id(
                PatternType.TEMPORAL_BURST, pattern_hash
            )

            addresses_involved = [addr] + counterparties

            pattern = {
                'pattern_id': pattern_id,
                'pattern_type': PatternType.TEMPORAL_BURST,
                'pattern_hash': pattern_hash,
                'addresses_involved': sorted(set(addresses_involved)),
                'address_roles': {addr: 'burst_source'},
                'transaction_ids': [],
                'total_amount_usd': volume,
                'detection_method': DetectionMethod.TEMPORAL_ANALYSIS,
                'confidence_score': self._calculate_burst_confidence(z_score, intensity, tx_count),
                'severity': self._determine_burst_severity(
                    addr, {
                        'counterparties': counterparties,
                        'burst_volume_usd': volume,
                        'burst_intensity': intensity,
                        'z_score': z_score,
                    }
                ),
                'evidence': {
                    'burst_start_ms': start_ms,
                    'burst_end_ms': end_ms,
                    'tx_count': tx_count,
                    'volume_usd': volume,
                    'addresses_involved': addresses_involved,
                    'intensity_score': intensity,
                },
                'window_days': window_days,
                'processing_date': processing_date,
                'network': self.network or '',
            }

            patterns_by_hash[pattern_hash] = pattern

        # Detect chain bursts (sequential transfers through multiple hops)
        logger.debug(f"Starting chain burst detection with {graph.edge_count()} edges")
        try:
            chain_patterns = self._detect_chain_bursts(
                graph_data, timestamp_data, window_days, processing_date
            )
            logger.debug(f"Chain detection returned {len(chain_patterns)} patterns")
            for pattern in chain_patterns:
                if pattern['pattern_hash'] not in patterns_by_hash:
                    patterns_by_hash[pattern['pattern_hash']] = pattern
        except Exception as e:
            logger.error(f"Chain detection failed: {e}")
            import traceback
            logger.error(traceback.format_exc())

        logger.info(f"Detected {len(patterns_by_hash)} burst patterns")
        return list(patterns_by_hash.values())

    def _detect_chain_bursts(
        self,
        graph_data: GraphData,
        timestamp_data: Dict[str, List[Dict[str, Any]]],
        window_days: int,
        processing_date: str
    ) -> List[Dict[str, Any]]:
        """
        Detect rapid sequential transfers through multiple hops (flash loan patterns).

        A chain burst is a sequence A→B→C→D where all transfers happen
        within a tight time window (often in the same block).

        Uses Rust backend (petgraph + rayon) for parallel processing.
        """
        min_chain_length = self._get_config_value(
            'burst_detection', 'min_chain_length', 5
        )
        chain_time_window_ms = self._get_config_value(
            'burst_detection', 'chain_time_window_ms', 120000
        )
        max_chains = self._get_config_value(
            'burst_detection', 'max_chains', 1000
        )

        graph = graph_data.graph

        # Build edge instances: keep ALL transfers, not just min timestamp
        # Each transfer is a separate instance that could be part of a chain
        sources_list = []
        targets_list = []
        timestamps_list = []
        volumes_list = []

        # OPTIMIZATION: Pre-build edge set for O(1) lookup instead of calling
        # graph.get_edge_weight() for each record (which traverses Rust graph)
        if graph_data.edge_attrs:
            edge_set = set(graph_data.edge_attrs.keys())
        else:
            # Fallback: build from graph edges if attrs weren't stored
            edge_set = {(src, tgt) for src, tgt, _ in graph.edges()}

        for node, records in timestamp_data.items():
            for record in records:
                counterparty = record['counterparty']
                # Check if edge exists using O(1) set lookup
                if (node, counterparty) in edge_set:
                    sources_list.append(node)
                    targets_list.append(counterparty)
                    timestamps_list.append(record['timestamp'])
                    volumes_list.append(record.get('volume', 0))

        if len(sources_list) < min_chain_length:
            return []

        logger.debug(f"Chain detection: {len(sources_list)} edge instances")

        # Use Rust backend for parallel chain detection
        rust_graph = RustGraph()

        # Call Rust find_chain_bursts
        chains = rust_graph.find_chain_bursts(
            sources_list,
            targets_list,
            timestamps_list,
            volumes_list,
            chain_time_window_ms,
            min_chain_length,
            max_chains
        )

        logger.debug(f"Chain detection found {len(chains)} chains")

        # Convert Rust results to pattern dicts
        patterns = []
        seen_pattern_hashes = set()

        for chain_addresses, total_volume, start_ts, end_ts in chains:
            if len(chain_addresses) < min_chain_length:
                continue

            pattern_hash = generate_pattern_hash(
                PatternType.TEMPORAL_BURST,
                [f"chain_{start_ts}"] + sorted(chain_addresses)[:3]
            )

            if pattern_hash in seen_pattern_hashes:
                continue
            seen_pattern_hashes.add(pattern_hash)

            pattern_id = generate_pattern_id(
                PatternType.TEMPORAL_BURST, pattern_hash
            )

            pattern = {
                'pattern_id': pattern_id,
                'pattern_type': PatternType.TEMPORAL_BURST,
                'pattern_hash': pattern_hash,
                'addresses_involved': sorted(set(chain_addresses)),
                'address_roles': {
                    chain_addresses[0]: 'chain_start',
                    chain_addresses[-1]: 'chain_end',
                },
                'transaction_ids': [],
                'total_amount_usd': total_volume,
                'detection_method': DetectionMethod.TEMPORAL_ANALYSIS,
                'confidence_score': min(0.5 + len(chain_addresses) * 0.05, 0.95),
                'severity': Severity.HIGH if total_volume > 50000 else Severity.MEDIUM,
                'evidence': {
                    'burst_start_ms': start_ts,
                    'burst_end_ms': end_ts,
                    'chain_length': len(chain_addresses),
                    'tx_count': len(chain_addresses) - 1,
                    'volume_usd': total_volume,
                    'addresses_involved': sorted(set(chain_addresses)),
                    'chain_type': 'sequential',
                },
                'window_days': window_days,
                'processing_date': processing_date,
                'network': self.network or '',
            }

            patterns.append(pattern)

        logger.debug(f"Detected {len(patterns)} chain burst patterns")
        return patterns

    def _calculate_burst_confidence(
        self,
        z_score: float,
        intensity: float,
        tx_count: int
    ) -> float:
        """Calculate confidence score for burst pattern."""
        confidence = 0.5

        if z_score > 4:
            confidence += 0.2
        elif z_score > 3:
            confidence += 0.15
        elif z_score > 2:
            confidence += 0.1

        if intensity > 5:
            confidence += 0.15
        elif intensity > 3:
            confidence += 0.1

        if tx_count > 20:
            confidence += 0.1
        elif tx_count > 10:
            confidence += 0.05

        return min(confidence, 1.0)

    def _determine_burst_severity(
        self,
        node: str,
        burst: Dict[str, Any]
    ) -> str:
        """Determine severity based on burst characteristics."""
        if self._is_fraudulent_address(node):
            return Severity.CRITICAL

        counterparties = burst.get('counterparties', [])
        has_fraudulent = any(
            self._is_fraudulent_address(addr)
            for addr in counterparties
        )
        if has_fraudulent:
            return Severity.HIGH

        volume = burst.get('burst_volume_usd', 0)
        intensity = burst.get('burst_intensity', 0)
        z_score = burst.get('z_score', 0)

        if volume > 100000 and intensity > 5:
            return Severity.HIGH

        if volume > 50000 or z_score > 4:
            return Severity.MEDIUM

        return Severity.LOW
