"""
Hijacking detection and mitigation (soft lockdown).

This module monitors message sources and metadata to detect hijack-like behaviour:
 - multiple distinct controllers suddenly appearing
 - sudden source identity changes
 - burst of unknown control commands
When a hijack-like situation is detected we enter a "soft lockdown" mode:
 - block non-engineer control commands
 - continue to allow authenticated engineers to send commands
 - log / alert engineers

Important: This module is defensive and leaves all hardware integration points abstract.
"""
import logging
import time
from collections import defaultdict, deque
from typing import Optional

logger = logging.getLogger(__name__)

class HijackingProtector:
    SOFT_LOCKDOWN_DURATION = 30.0  # seconds to stay in soft lockdown after detection
    UNKNOWN_SOURCE_THRESHOLD = 3   # number of unknown control senders in window to trigger
    OBS_WINDOW = 10.0              # seconds observation window
    MAX_BLOCKED_LOG_SIZE = 1000    # limit blocked log to prevent unbounded memory growth
    
    # Cache trusted prefixes as class constant for faster lookups
    TRUSTED_PREFIXES = ("driver_device", "engineer_remote", "ecu", "telem_node")

    def __init__(self):
        # track last-seen times and recent sources
        self.source_last_seen = {}
        self.control_sources_window = deque()  # entries: (timestamp, source)
        self.unknown_source_counts = defaultdict(int)
        self.soft_lockdown_until: Optional[float] = None
        self.blocked_log = deque(maxlen=self.MAX_BLOCKED_LOG_SIZE)  # bounded deque for memory efficiency

    def observe_message(self, msg_type: str, source: str, rssi: float = 0.0) -> dict:
        """
        Observe an incoming message metadata for hijacking indicators.
        Returns a dict describing detection state and actions.
        """
        now = time.time()
        self.source_last_seen[source] = now

        # Clean window
        while self.control_sources_window and now - self.control_sources_window[0][0] > self.OBS_WINDOW:
            self.control_sources_window.popleft()

        result = {"action": None, "reason": None}

        if msg_type == "control":
            # Record control source
            self.control_sources_window.append((now, source))
            # If this is an unknown or low-signal source, increase suspicion
            if self._is_untrusted_source(source) or rssi < 0.3:
                self.unknown_source_counts[source] += 1

            # Count unique control sources recently - optimize by calculating once
            uniq_sources = {s for _, s in self.control_sources_window}
            num_sources = len(uniq_sources)
            
            if num_sources >= self.UNKNOWN_SOURCE_THRESHOLD:
                logger.warning("Multiple control sources detected: %s", uniq_sources)
                self._trigger_soft_lockdown(reason="multiple_control_sources")
                result["action"] = "soft_lockdown_active"
                result["reason"] = "multiple_control_sources"
            else:
                # Only check repeated sources if we haven't already triggered lockdown
                # Use max() to avoid repeated iterations
                max_unknown_count = max((self.unknown_source_counts[s] for s in uniq_sources), default=0)
                if max_unknown_count > 2:
                    logger.warning("Repeated unknown sources detected: %s", self.unknown_source_counts)
                    self._trigger_soft_lockdown(reason="repeated_unknown_sources")
                    result["action"] = "soft_lockdown_active"
                    result["reason"] = "repeated_unknown_sources"

        # If in lockdown, report active
        if self.is_soft_lockdown_active():
            result["action"] = "soft_lockdown_active"
            if result["reason"] is None:
                result["reason"] = "previous_detection"

        return result

    def _is_untrusted_source(self, source: str) -> bool:
        """
        Placeholder logic for source trust decisions.
        In real systems, check device whitelists, pairing status, certificate fingerprints, etc.
        """
        # Simple heuristic: sources that don't match a known prefix are untrusted in this demo.
        # Use cached class constant instead of recreating tuple each call
        return not any(source.startswith(p) for p in self.TRUSTED_PREFIXES)

    def _trigger_soft_lockdown(self, reason: str):
        self.soft_lockdown_until = time.time() + self.SOFT_LOCKDOWN_DURATION
        logger.error("SOFT LOCKDOWN triggered for %.1f seconds: %s", self.SOFT_LOCKDOWN_DURATION, reason)
        # In a real deployment, send an immediate alert (SMS/email/ops) and persist the incident.
        # Don't perform destructive actions automatically.

    def is_soft_lockdown_active(self) -> bool:
        if self.soft_lockdown_until is None:
            return False
        if time.time() < self.soft_lockdown_until:
            return True
        self.soft_lockdown_until = None
        return False

    def record_blocked(self, source: str, reasons):
        # Using deque with maxlen ensures automatic size limiting
        self.blocked_log.append({"ts": time.time(), "source": source, "reasons": reasons})
