"""
Anti-Gaming Detection (Ωc Composite Score)

Detects coordination attacks, market manipulation, burst attacks,
and out-of-distribution inputs in multi-agent systems.

Production hardening: Protects CHOIR merges from gaming attacks.
"""

from typing import List, Dict, Any, Optional
from datetime import datetime, timedelta
import math
import statistics
from receipts.models_merge import AntiGamingSignals, ParentReceipt, MergeReceiptV2


def compute_contradiction_score(parent_values: List[Any]) -> float:
    """
    Compute Ωc (omega_c) contradiction score.

    Measures how contradictory the parent values are:
    - 0.0: All agree (homogeneous)
    - 1.0: Maximum disagreement (contradictory)

    Args:
        parent_values: List of values from parent receipts

    Returns:
        float: Contradiction score [0.0, 1.0]
    """
    if not parent_values or len(parent_values) < 2:
        return 0.0

    # Count unique values
    unique_values = len(set(str(v) for v in parent_values))
    total_values = len(parent_values)

    # Normalized disagreement
    if total_values == 1:
        return 0.0

    # Gini impurity-style measure
    # Perfect agreement: 0.0, max disagreement: approaches 1.0
    return 1.0 - (1.0 / unique_values)


def compute_schism_score(parent_receipts: List[ParentReceipt], merge_result: Any) -> float:
    """
    Compute schism score (disagreement severity).

    Measures how severely the ensemble disagrees across agents:
    - 0.0: Consensus
    - 1.0: Complete schism (e.g., 50/50 split)

    Args:
        parent_receipts: Parent receipt metadata
        merge_result: Merged result

    Returns:
        float: Schism score [0.0, 1.0]
    """
    if len(parent_receipts) < 2:
        return 0.0

    # Group by agent_id
    agent_ids = [p.agent_id for p in parent_receipts]
    unique_agents = len(set(agent_ids))

    # Simple schism: more unique agents = higher schism
    # (In production, would check digest similarity)
    if unique_agents == 1:
        return 0.0  # All from same agent (no schism)

    # Normalized: 50/50 split is max schism (1.0)
    # Entropy-based measure
    total = len(parent_receipts)
    max_possible_disagreement = total / 2.0
    actual_disagreement = unique_agents / total

    # Normalize to [0, 1]
    return min(actual_disagreement / 0.5, 1.0)


def compute_burst_zscore(
    recent_merge_timestamps: List[datetime],
    current_timestamp: datetime,
    lookback_window: timedelta = timedelta(hours=1)
) -> float:
    """
    Compute burst detection z-score.

    Detects sudden spikes in merge activity (burst attacks):
    - z-score < 2.0: Normal activity
    - z-score 2.0-3.0: Elevated activity
    - z-score > 3.0: Burst attack (anomaly)

    Args:
        recent_merge_timestamps: Recent merge timestamps
        current_timestamp: Current merge timestamp
        lookback_window: Time window to analyze

    Returns:
        float: Z-score (can be negative, typically [-3, 10])
    """
    if not recent_merge_timestamps:
        return 0.0

    # Filter to lookback window
    cutoff_time = current_timestamp - lookback_window
    recent = [t for t in recent_merge_timestamps if t >= cutoff_time]

    if len(recent) < 2:
        return 0.0

    # Compute inter-arrival times (in seconds)
    sorted_times = sorted(recent)
    inter_arrival_times = [
        (sorted_times[i+1] - sorted_times[i]).total_seconds()
        for i in range(len(sorted_times) - 1)
    ]

    if not inter_arrival_times:
        return 0.0

    # Compute mean and std of inter-arrival times
    mean_interval = statistics.mean(inter_arrival_times)

    if len(inter_arrival_times) < 2:
        return 0.0

    std_interval = statistics.stdev(inter_arrival_times)

    if std_interval == 0:
        return 0.0

    # Current interval (time since last merge)
    if len(sorted_times) < 2:
        return 0.0

    current_interval = (current_timestamp - sorted_times[-1]).total_seconds()

    # Z-score: how many std devs from mean
    # Low interval (rapid merges) = high positive z-score
    z_score = -(current_interval - mean_interval) / std_interval  # Negative to flip

    return z_score


def compute_ood_score(
    parent_count: int,
    expected_parent_count: int = 3,
    parent_count_std: float = 1.0
) -> float:
    """
    Compute out-of-distribution (OOD) score.

    Detects unusual parent counts (e.g., merging 100 parents when typical is 2-4):
    - 0.0: Expected distribution
    - 1.0: Highly unusual (OOD)

    Args:
        parent_count: Number of parents in this merge
        expected_parent_count: Expected number of parents
        parent_count_std: Standard deviation of parent count

    Returns:
        float: OOD score [0.0, 1.0]
    """
    # Z-score for parent count
    z = abs(parent_count - expected_parent_count) / parent_count_std

    # Convert to [0, 1] using sigmoid
    # z=0 → 0.0, z=3 → 0.95, z=5 → 0.99
    ood_score = 1.0 / (1.0 + math.exp(-z + 2))

    return min(ood_score, 1.0)


def compute_source_trust(
    parent_receipts: List[ParentReceipt],
    known_agents: Dict[str, float]
) -> float:
    """
    Compute source trust score.

    Measures reliability of sources:
    - 1.0: All sources are highly trusted
    - 0.0: All sources are unknown/untrusted

    Args:
        parent_receipts: Parent receipt metadata
        known_agents: Map of agent_id → trust_score [0.0, 1.0]

    Returns:
        float: Source trust score [0.0, 1.0]
    """
    if not parent_receipts:
        return 0.5  # Neutral

    # Average trust score across parents
    trust_scores = [
        known_agents.get(p.agent_id, 0.5)  # Default: neutral trust
        for p in parent_receipts
    ]

    return statistics.mean(trust_scores)


def classify_gaming_risk(
    omega_c: float,
    schism: float,
    burst_zscore: float,
    ood_score: float,
    source_trust: float
) -> str:
    """
    Classify overall gaming risk level.

    Rules:
    - HIGH: Any critical signal OR multiple elevated signals
    - MEDIUM: Some elevated signals
    - LOW: All signals normal

    Args:
        omega_c: Contradiction score
        schism: Schism score
        burst_zscore: Burst detection z-score
        ood_score: Out-of-distribution score
        source_trust: Source trust score

    Returns:
        str: "LOW", "MEDIUM", or "HIGH"
    """
    # Critical thresholds (any one triggers HIGH)
    if omega_c > 0.8:
        return "HIGH"  # Severe contradiction
    if schism > 0.7:
        return "HIGH"  # Severe schism
    if burst_zscore > 4.0:
        return "HIGH"  # Extreme burst
    if ood_score > 0.9:
        return "HIGH"  # Highly unusual input
    if source_trust < 0.2:
        return "HIGH"  # Untrusted sources

    # Elevated threshold (multiple triggers MEDIUM)
    elevated_count = sum([
        omega_c > 0.5,
        schism > 0.4,
        burst_zscore > 2.5,
        ood_score > 0.6,
        source_trust < 0.5
    ])

    if elevated_count >= 2:
        return "MEDIUM"

    return "LOW"


def compute_anti_gaming_signals(
    merge_receipt: MergeReceiptV2,
    recent_merge_timestamps: Optional[List[datetime]] = None,
    known_agents: Optional[Dict[str, float]] = None,
    parent_values: Optional[List[Any]] = None
) -> AntiGamingSignals:
    """
    Compute composite anti-gaming signals (Ωc) for a merge receipt.

    Args:
        merge_receipt: Merge receipt to analyze
        recent_merge_timestamps: Recent merge timestamps for burst detection
        known_agents: Map of agent_id → trust_score
        parent_values: List of parent values for contradiction analysis

    Returns:
        AntiGamingSignals: Computed gaming signals
    """
    # Default values
    if recent_merge_timestamps is None:
        recent_merge_timestamps = []
    if known_agents is None:
        known_agents = {}
    if parent_values is None:
        parent_values = []

    # Compute individual signals
    omega_c = compute_contradiction_score(parent_values)
    schism = compute_schism_score(merge_receipt.parents, merge_receipt.domain_data)
    burst_zscore = compute_burst_zscore(
        recent_merge_timestamps,
        merge_receipt.transaction_time
    )
    ood_score = compute_ood_score(len(merge_receipt.parents))
    source_trust = compute_source_trust(merge_receipt.parents, known_agents)

    # Classify risk
    gaming_risk = classify_gaming_risk(
        omega_c,
        schism,
        burst_zscore,
        ood_score,
        source_trust
    )

    return AntiGamingSignals(
        omega_c=omega_c,
        schism=schism,
        burst_zscore=burst_zscore,
        ood_score=ood_score,
        source_trust=source_trust,
        gaming_risk=gaming_risk
    )


def analyze_gaming_risk_batch(
    merge_receipts: List[MergeReceiptV2],
    known_agents: Optional[Dict[str, float]] = None
) -> List[AntiGamingSignals]:
    """
    Batch analyze gaming risk for multiple merge receipts.

    Uses temporal context from the batch for burst detection.

    Args:
        merge_receipts: List of merge receipts to analyze
        known_agents: Map of agent_id → trust_score

    Returns:
        List[AntiGamingSignals]: Gaming signals for each receipt
    """
    if not merge_receipts:
        return []

    # Extract timestamps for burst detection
    timestamps = [r.transaction_time for r in merge_receipts]

    results = []
    for i, receipt in enumerate(merge_receipts):
        # Use previous timestamps for context
        recent_timestamps = timestamps[:i] if i > 0 else []

        signals = compute_anti_gaming_signals(
            receipt,
            recent_merge_timestamps=recent_timestamps,
            known_agents=known_agents
        )
        results.append(signals)

    return results
