"""Motif pattern detector - fan-in and fan-out patterns.

Uses Rust (petgraph) for motif detection - no NetworkX dependency.
"""

from typing import Dict, List, Any

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,
)


class MotifDetector(BasePatternDetector):
    """Detect fan-in and fan-out motif patterns using Rust backend."""

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

    def detect(
        self,
        graph_data: GraphData,
        address_labels: Dict[str, Dict[str, Any]],
        window_days: int,
        processing_date: str
    ) -> 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 = {}

        # Get config values
        min_spoke_count = self._get_config_value(
            'motif_detection', 'min_spoke_count', 5
        )
        fanin_max_out_degree = self._get_config_value(
            'motif_detection', 'fanin_max_out_degree', 3
        )
        fanout_max_in_degree = self._get_config_value(
            'motif_detection', 'fanout_max_in_degree', 3
        )
        degree_percentile = self._get_config_value(
            'motif_detection', 'degree_percentile_threshold', 90
        )

        # Get degree map from Rust (efficient)
        degree_map = graph.degree_map()

        if not degree_map:
            return []

        # Calculate thresholds
        in_degrees = [d[0] for d in degree_map.values()]
        out_degrees = [d[1] for d in degree_map.values()]

        in_degree_threshold = max(
            np.percentile(in_degrees, degree_percentile),
            min_spoke_count
        )
        out_degree_threshold = max(
            np.percentile(out_degrees, degree_percentile),
            min_spoke_count
        )

        # Detect patterns using Rust
        # Fan-in patterns: high in-degree, low out-degree
        fanin_motifs = graph.detect_fan_in_motifs(
            min_sources=int(in_degree_threshold),
            min_volume=0.0
        )

        for center, sources, total_volume, edge_count in fanin_motifs:
            # Check out-degree constraint
            out_deg = degree_map.get(center, (0, 0))[1]
            if out_deg > fanin_max_out_degree:
                continue

            all_addresses = [center] + sources
            pattern_hash = generate_pattern_hash(
                PatternType.MOTIF_FANIN, sorted(all_addresses)
            )

            if pattern_hash in patterns_by_hash:
                continue

            address_roles = {center: 'center'}
            for addr in sources:
                address_roles[addr] = 'source'

            patterns_by_hash[pattern_hash] = {
                'pattern_id': generate_pattern_id(PatternType.MOTIF_FANIN, pattern_hash),
                'pattern_type': PatternType.MOTIF_FANIN,
                'pattern_hash': pattern_hash,
                'addresses_involved': all_addresses,
                'address_roles': address_roles,
                'transaction_ids': [],
                'total_amount_usd': total_volume,
                'detection_method': DetectionMethod.MOTIF_DETECTION,
                'confidence_score': self._calculate_motif_confidence(edge_count, total_volume),
                'severity': self._determine_motif_severity(all_addresses, total_volume, edge_count),
                'evidence': {
                    'center_address': center,
                    'spoke_count': edge_count,
                    'total_volume_usd': total_volume,
                    'time_concentration': 0.5,  # TODO: Add temporal analysis
                },
                'window_days': window_days,
                'processing_date': processing_date,
                'network': self.network or '',
            }

        # Fan-out patterns: high out-degree, low in-degree
        fanout_motifs = graph.detect_fan_out_motifs(
            min_targets=int(out_degree_threshold),
            min_volume=0.0
        )

        for center, targets, total_volume, edge_count in fanout_motifs:
            # Check in-degree constraint
            in_deg = degree_map.get(center, (0, 0))[0]
            if in_deg > fanout_max_in_degree:
                continue

            all_addresses = [center] + targets
            pattern_hash = generate_pattern_hash(
                PatternType.MOTIF_FANOUT, sorted(all_addresses)
            )

            if pattern_hash in patterns_by_hash:
                continue

            address_roles = {center: 'center'}
            for addr in targets:
                address_roles[addr] = 'destination'

            patterns_by_hash[pattern_hash] = {
                'pattern_id': generate_pattern_id(PatternType.MOTIF_FANOUT, pattern_hash),
                'pattern_type': PatternType.MOTIF_FANOUT,
                'pattern_hash': pattern_hash,
                'addresses_involved': all_addresses,
                'address_roles': address_roles,
                'transaction_ids': [],
                'total_amount_usd': total_volume,
                'detection_method': DetectionMethod.MOTIF_DETECTION,
                'confidence_score': self._calculate_motif_confidence(edge_count, total_volume),
                'severity': self._determine_motif_severity(all_addresses, total_volume, edge_count),
                'evidence': {
                    'center_address': center,
                    'spoke_count': edge_count,
                    'total_volume_usd': total_volume,
                    'time_concentration': 0.5,  # TODO: Add temporal analysis
                },
                'window_days': window_days,
                'processing_date': processing_date,
                'network': self.network or '',
            }

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

    def _calculate_motif_confidence(
        self,
        spoke_count: int,
        volume: float
    ) -> float:
        """Calculate confidence score based on spoke count and volume."""
        confidence = 0.5

        if spoke_count >= 10:
            confidence += 0.2
        elif spoke_count >= 5:
            confidence += 0.1

        if volume > 100000:
            confidence += 0.2
        elif volume > 10000:
            confidence += 0.1

        return min(confidence, 1.0)

    def _determine_motif_severity(
        self,
        addresses: List[str],
        volume: float,
        spoke_count: int
    ) -> str:
        """Determine severity based on address labels and pattern characteristics."""
        center = addresses[0] if addresses else None
        if center and self._is_fraudulent_address(center):
            return Severity.CRITICAL

        has_fraudulent = any(
            self._is_fraudulent_address(addr)
            for addr in addresses[1:]
        )
        if has_fraudulent:
            return Severity.HIGH

        if volume > 100000 and spoke_count >= 20:
            return Severity.HIGH

        if volume > 50000 or spoke_count >= 30:
            return Severity.MEDIUM

        return Severity.LOW
