"""
Spoofing and integrity protection.

This module:
 - Verifies message HMAC (simulated/shared secret demo)
 - Performs simple sanity checks on telemetry values (range, rate-of-change)
 - Cross-checks driver telemetry against an engineer-reported value (when both exist)
 - Flags anomalies and returns structured results

Design choices:
 - Use HMAC-SHA256 with a shared secret for message integrity in this demo.
 - Real systems should use mutual authentication, per-device keys, certificate chains, and secure channels.
"""
import hmac
import hashlib
import logging
import time
from typing import Dict, Any

logger = logging.getLogger(__name__)

# For demo/simulation only. Replace with secure key management in production.
SIMULATED_SHARED_SECRET = b"demo_shared_secret_change_me"

class SpoofingProtector:
    # physical limits / sanity constraints (example values tuned for race cars)
    MAX_SPEED_MPH = 450.0
    MIN_SPEED_MPH = 0.0
    MAX_RPM = 20000

    # allowable small discrepancies between driver dash and engineer dash (mph)
    CROSSCHECK_THRESHOLD_MPH = 10.0

    def __init__(self, secret: bytes = SIMULATED_SHARED_SECRET):
        self.secret = secret
        self.last_values: Dict[str, Any] = {}
        self.last_ts = time.time()

    @staticmethod
    def sign(payload: Dict[str, Any], secret: bytes) -> str:
        """
        Create an HMAC hex digest for the payload. For simplicity we canonicalize by
        using sorted keys and str() of values. In real systems use canonical serialization
        (e.g., JSON canonical form) and include timestamps/nonces.
        """
        # Optimize string building using list comprehension and single join
        # More efficient than generator in join for small to medium payloads
        parts = [f"{k}={payload[k]}" for k in sorted(payload.keys())]
        normalized = ",".join(parts)
        hm = hmac.new(secret, normalized.encode("utf-8"), hashlib.sha256)
        return hm.hexdigest()

    def verify_hmac(self, payload: Dict[str, Any], tag: str) -> bool:
        if not tag:
            return False
        expected = self.sign(payload, self.secret)
        return hmac.compare_digest(expected, tag)

    def inspect_message(self, msg_type: str, source: str, payload: Dict[str, Any], hmac_tag: str) -> Dict[str, Any]:
        """
        Inspect an incoming message. Returns a dict:
          - valid: bool
          - reason: optional string for invalidity
          - anomaly_score: heuristic score (0 normal, larger = more anomalous)
        """
        result = {"valid": True, "reason": None, "anomaly_score": 0.0}

        # 1) Check HMAC
        if not self.verify_hmac(payload, hmac_tag):
            result["valid"] = False
            result["reason"] = "hmac_invalid_or_missing"
            logger.warning("HMAC check failed for source=%s payload=%s", source, payload)
            return result

        # 2) If telemetry, check ranges and rate-of-change
        if msg_type == "telemetry":
            speed = payload.get("speed_mph")
            rpm = payload.get("rpm")
            if speed is not None:
                if not (self.MIN_SPEED_MPH <= speed <= self.MAX_SPEED_MPH):
                    result["valid"] = False
                    result["reason"] = "speed_out_of_bounds"
                    logger.warning("Speed out of bounds: %s", speed)
                    return result

                # rate of change check (compared to last recorded speed)
                last_speed = self.last_values.get("speed_mph")
                if last_speed is not None:
                    # if suddenly enormous change, flag
                    if abs(speed - last_speed) > 100.0:  # arbitrary threshold for demo
                        result["valid"] = False
                        result["reason"] = "sudden_speed_change"
                        logger.warning("Sudden speed change detected: last=%s now=%s", last_speed, speed)
                        return result

            if rpm is not None:
                if not (0 <= rpm <= self.MAX_RPM):
                    result["valid"] = False
                    result["reason"] = "rpm_out_of_bounds"
                    logger.warning("RPM out of bounds: %s", rpm)
                    return result

            # Save last seen values (timestamped) - optimize by avoiding redundant comprehension
            # Only update values that are present in payload
            if speed is not None:
                self.last_values["speed_mph"] = speed
            if rpm is not None:
                self.last_values["rpm"] = rpm
            self.last_ts = time.time()

        # 3) If engineer dashboard telemetry exists elsewhere in system, cross-check
        # NOTE: In this demo there is no persistent cross-source store. In integration,
        # you would compare driver telemetry vs engineer dashboard telemetry to detect spoofed driver messages.
        # For now we just set a neutral anomaly score.
        result["anomaly_score"] = 0.0
        return result
