"""
AgentDrop Shield -- Layer 5b: Cross-Transfer Behavioral Tracker.

Tracks scanning patterns across multiple files from the same sender
to detect behavioral anomalies that are invisible in single-file scans:

- Gradual escalation: sender starts clean then injects later
- Burst flooding: rapid file submission to overwhelm review
- Reputation decay: trust erodes as suspicious patterns accumulate

All state is in-memory with automatic 24-hour decay.  Thread-safe.

Uses only Python stdlib.
"""

from __future__ import annotations

import logging
import threading
import time
from collections import deque
from dataclasses import dataclass, field
from typing import Optional

from .models import ThreatLevel

logger = logging.getLogger("agentdrop.shield.behavioral_tracker")


# =====================================================================
# Data Structures
# =====================================================================

@dataclass
class ScanRecord:
    """A single scan result recorded for a sender."""
    timestamp: float
    threat_level: ThreatLevel
    filename: str
    injection_count: int = 0
    intent_score: float = 0.0


@dataclass
class BehavioralAssessment:
    """Assessment from the behavioral tracker for a given scan."""

    escalation: ThreatLevel = ThreatLevel.CLEAN
    """Threat level escalation to apply based on sender history."""

    reputation: float = 1.0
    """Sender reputation score: 1.0 = fully trusted, 0.0 = fully distrusted."""

    flags: list[str] = field(default_factory=list)
    """Human-readable flags explaining behavioral findings."""

    burst_detected: bool = False
    """True if the sender is submitting files in a suspicious burst."""

    history_size: int = 0
    """Number of records in the sender's history after decay."""


# =====================================================================
# Tracker
# =====================================================================

# Limits
_MAX_RECORDS_PER_SENDER = 100
_DECAY_SECONDS = 24 * 60 * 60  # 24 hours
_BURST_THRESHOLD_COUNT = 10
_BURST_THRESHOLD_SECONDS = 60


class BehavioralTracker:
    """Layer 5b -- cross-transfer behavioral analysis.

    Maintains per-sender scan history and computes reputation scores
    and threat escalations based on behavioral patterns.

    Thread-safe via a reentrant lock.

    Usage::

        tracker = BehavioralTracker()

        # After each scan, record the result:
        tracker.record(sender_id, record)

        # Before or during a scan, assess the sender:
        assessment = tracker.assess(sender_id)

        # Get a simple reputation float:
        rep = tracker.get_sender_reputation(sender_id)
    """

    def __init__(self) -> None:
        self._lock = threading.Lock()
        self._history: dict[str, deque[ScanRecord]] = {}

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def record(
        self,
        sender_id: str,
        threat_level: ThreatLevel,
        filename: str,
        injection_count: int = 0,
        intent_score: float = 0.0,
    ) -> None:
        """Record a scan result for a sender.

        Args:
            sender_id: Unique identifier for the sender.
            threat_level: Final threat level from the scan.
            filename: Name of the scanned file.
            injection_count: Number of injection findings.
            intent_score: Intent classifier score 0.0--1.0.
        """
        record = ScanRecord(
            timestamp=time.time(),
            threat_level=threat_level,
            filename=filename,
            injection_count=injection_count,
            intent_score=intent_score,
        )

        with self._lock:
            if sender_id not in self._history:
                self._history[sender_id] = deque(maxlen=_MAX_RECORDS_PER_SENDER)
            self._history[sender_id].append(record)

    def assess(self, sender_id: str) -> BehavioralAssessment:
        """Assess a sender's behavioral pattern and return escalation advice.

        Args:
            sender_id: Unique identifier for the sender.

        Returns:
            BehavioralAssessment with escalation level, reputation, and flags.
        """
        with self._lock:
            records = self._get_live_records(sender_id)

        if not records:
            return BehavioralAssessment()

        assessment = BehavioralAssessment(history_size=len(records))

        # --- Check 1: Escalation based on finding accumulation ---
        self._check_escalation(records, assessment)

        # --- Check 2: Burst detection ---
        self._check_burst(records, assessment)

        # --- Check 3: Compute reputation ---
        assessment.reputation = self._compute_reputation(records)

        return assessment

    def get_sender_reputation(self, sender_id: str) -> float:
        """Get a sender's trust score.

        Args:
            sender_id: Unique identifier for the sender.

        Returns:
            Float 0.0 (fully distrusted) to 1.0 (fully trusted).
            Returns 1.0 for unknown senders (benefit of the doubt).
        """
        with self._lock:
            records = self._get_live_records(sender_id)

        if not records:
            return 1.0

        return self._compute_reputation(records)

    def clear_sender(self, sender_id: str) -> None:
        """Clear all history for a sender."""
        with self._lock:
            self._history.pop(sender_id, None)

    def clear_all(self) -> None:
        """Clear all sender history."""
        with self._lock:
            self._history.clear()

    # ------------------------------------------------------------------
    # Internal logic
    # ------------------------------------------------------------------

    def _get_live_records(self, sender_id: str) -> list[ScanRecord]:
        """Get non-expired records for a sender (must hold lock)."""
        dq = self._history.get(sender_id)
        if not dq:
            return []

        cutoff = time.time() - _DECAY_SECONDS
        live = [r for r in dq if r.timestamp >= cutoff]

        # Prune expired records from the deque
        if len(live) < len(dq):
            self._history[sender_id] = deque(live, maxlen=_MAX_RECORDS_PER_SENDER)

        return live

    @staticmethod
    def _check_escalation(
        records: list[ScanRecord], assessment: BehavioralAssessment
    ) -> None:
        """Apply escalation rules based on accumulated findings."""

        low_plus = sum(1 for r in records if r.threat_level >= ThreatLevel.LOW)
        medium_plus = sum(1 for r in records if r.threat_level >= ThreatLevel.MEDIUM)
        high_plus = sum(1 for r in records if r.threat_level >= ThreatLevel.HIGH)

        # Rule: 2+ MEDIUM findings -> escalate to HIGH
        if medium_plus >= 2:
            assessment.escalation = max(assessment.escalation, ThreatLevel.HIGH)
            assessment.flags.append(
                f"Sender has {medium_plus} files with MEDIUM+ findings "
                "-- escalating to HIGH"
            )

        # Rule: 3+ LOW findings -> escalate to MEDIUM
        elif low_plus >= 3:
            assessment.escalation = max(assessment.escalation, ThreatLevel.MEDIUM)
            assessment.flags.append(
                f"Sender has {low_plus} files with LOW+ findings "
                "-- escalating to MEDIUM"
            )

        # If sender already has HIGH/CRITICAL history, keep escalating
        if high_plus >= 1:
            assessment.escalation = max(assessment.escalation, ThreatLevel.HIGH)
            assessment.flags.append(
                f"Sender has {high_plus} file(s) with HIGH+ findings in history"
            )

    @staticmethod
    def _check_burst(
        records: list[ScanRecord], assessment: BehavioralAssessment
    ) -> None:
        """Detect rapid burst of file submissions."""
        if len(records) < _BURST_THRESHOLD_COUNT:
            return

        now = time.time()
        recent = sum(
            1 for r in records
            if now - r.timestamp <= _BURST_THRESHOLD_SECONDS
        )

        if recent >= _BURST_THRESHOLD_COUNT:
            assessment.burst_detected = True
            assessment.escalation = max(assessment.escalation, ThreatLevel.HIGH)
            assessment.flags.append(
                f"Burst detected: {recent} files in the last "
                f"{_BURST_THRESHOLD_SECONDS}s (threshold: {_BURST_THRESHOLD_COUNT})"
            )

    @staticmethod
    def _compute_reputation(records: list[ScanRecord]) -> float:
        """Compute a 0.0--1.0 reputation score.

        Reputation starts at 1.0 and degrades with each finding.
        More severe findings cause sharper drops.  Recent findings
        weigh more than older ones.
        """
        if not records:
            return 1.0

        now = time.time()
        reputation = 1.0

        # Penalty per threat level
        penalties = {
            ThreatLevel.CLEAN: 0.0,
            ThreatLevel.LOW: 0.03,
            ThreatLevel.MEDIUM: 0.10,
            ThreatLevel.HIGH: 0.25,
            ThreatLevel.CRITICAL: 0.45,
        }

        for record in records:
            # Time decay factor: recent records have more impact
            age_hours = (now - record.timestamp) / 3600.0
            recency_weight = max(1.0 - (age_hours / 24.0), 0.1)

            penalty = penalties.get(record.threat_level, 0.0) * recency_weight
            reputation -= penalty

        # Clamp to [0.0, 1.0]
        return round(max(0.0, min(1.0, reputation)), 4)
