#!/usr/bin/env python3
"""
Main simulation/orchestration script.

This file runs a simulated telemetry/control stream and shows how the hijacking and spoofing
protections react. Integration points for real telemetry and Bluetooth stacks are clearly
marked and intentionally abstracted.
"""

# Ensure the repository folder (where this file lives) is on sys.path so local packages
# (like the security package) can be imported when running `python main.py`.
# This is a minimal, safe convenience for local runs. If you prefer not to modify main.py,
# instead create security/__init__.py and run from the repo root (recommended).
import os
import sys
sys.path.insert(0, os.path.dirname(__file__))

import asyncio
import logging
import time
from security.hijacking_protection import HijackingProtector
from security.spoofing_protection import SpoofingProtector
from security.engineer_interface import EngineerInterface, EngineerTokenError

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s")

async def simulated_message_source(queue: asyncio.Queue):
    """
    Simulate incoming messages from telemetry and remote controllers.
    Messages are dictionaries with fields:
      - type: "telemetry" or "control"
      - source: identifier (e.g., device MAC or logical id)
      - payload: dict (telemetry values or control command)
      - mac: simulated signal strength or other metadata (optional)
      - hmac: message authentication tag (for spoofing check)
    This generator simulates normal messages, spoofed messages and hijack-like events.
    """
    # Normal telemetry from driver device and engineer dashboard
    driver_id = "driver_device_01"
    engineer_dash_id = "engineer_remote_01"
    outside_bad_actor = "unknown_remote_66"

    # Simulated shared secret used for HMAC (in real systems use secure key management)
    # SpoofingProtector uses the same secret below in the simulation. In production, keys differ per device.
    # FIX: instantiate the protector and use its secret (module constant is not a class attribute)
    shared_secret = SpoofingProtector().secret

    # 1) Normal stream
    for i in range(3):
        telemetry = {"speed_mph": 150 + i, "rpm": 7000 + i * 10}
        await queue.put(("telemetry", driver_id, telemetry, 0.8, SpoofingProtector.sign(payload=telemetry, secret=shared_secret)))
        control = {"cmd": "set_mode", "mode": "race_preset"}
        await queue.put(("control", engineer_dash_id, control, 0.9, SpoofingProtector.sign(payload=control, secret=shared_secret)))
        await asyncio.sleep(0.5)

    # 2) Spoofing attempt: crazy value
    spoofed_telemetry = {"speed_mph": 3940234902, "rpm": 12345678}
    await queue.put(("telemetry", driver_id, spoofed_telemetry, 0.4, SpoofingProtector.sign(payload=spoofed_telemetry, secret=shared_secret)))
    await asyncio.sleep(0.5)

    # 3) Subtle spoofing: small offset that could cause dangerous decisions (e.g., 270 vs 300)
    subtle_spoof = {"speed_mph": 270, "rpm": 6500}
    # Attacker crafts a message with a matching HMAC (simulated) — in reality they'd need the secret.
    await queue.put(("telemetry", driver_id, subtle_spoof, 0.45, SpoofingProtector.sign(payload=subtle_spoof, secret=shared_secret)))
    await asyncio.sleep(0.5)

    # 4) Hijack attempt: a new remote starts sending control commands non-authenticated
    for i in range(3):
        control_bad = {"cmd": "brake", "value": 1.0}
        # Attacker has no valid HMAC; we simulate missing or invalid HMAC
        await queue.put(("control", outside_bad_actor, control_bad, 0.2, "INVALID_HMAC"))
        await asyncio.sleep(0.3)

    # 5) Legit engineer still operating — should be allowed during soft lockdown if authenticated
    control_engineer = {"cmd": "reduce_risk", "value": 0.5}
    try:
        # Request a token (simulates engineer logging in)
        token = EngineerInterface.request_engineer_token(engineer_id="lead_engineer")
        await queue.put(("control", engineer_dash_id, {"cmd": "engineer_override", "token": token}, 0.95, SpoofingProtector.sign(payload={"cmd": "engineer_override"}, secret=shared_secret)))
    except EngineerTokenError:
        logging.error("Failed to acquire engineer token in simulation")
    await asyncio.sleep(0.5)

    # End of simulation marker
    await queue.put(("system", "simulator", {"event": "END"}, 1.0, ""))


async def main_loop():
    queue = asyncio.Queue()
    protector_hijack = HijackingProtector()
    protector_spoof = SpoofingProtector()
    engineer_iface = EngineerInterface()

    # Start message producer
    producer = asyncio.create_task(simulated_message_source(queue))
    running = True
    while running:
        msg_type, source, payload, rssi, hmac_tag = await queue.get()
        if msg_type == "system" and payload.get("event") == "END":
            running = False
            logging.info("Simulation complete.")
            continue

        # First: spoofing/integrity checks
        spoof_result = protector_spoof.inspect_message(msg_type=msg_type, source=source, payload=payload, hmac_tag=hmac_tag)

        # Second: hijacking checks and actions
        hijack_result = protector_hijack.observe_message(msg_type=msg_type, source=source, rssi=rssi)

        # Decide whether to allow this message to be applied / forwarded to actuators
        allow = True
        reason_blocks = []
        if not spoof_result["valid"]:
            allow = False
            reason_blocks.append(f"spoofing_invalid: {spoof_result['reason']}")
        if hijack_result.get("action") == "soft_lockdown_active":
            # In soft lockdown, only allow messages from authenticated engineers
            if msg_type == "control" and engineer_iface.is_engineer_payload(payload):
                # validate engineer token
                token = payload.get("token")
                if engineer_iface.validate_token(token):
                    allow = True
                else:
                    allow = False
                    reason_blocks.append("engineer_token_invalid")
            else:
                allow = False
                reason_blocks.append("soft_lockdown_blocks_non_engineer")

        # Logging / Alerts
        if not allow:
            logging.warning("Blocked message from %s: reasons=%s payload=%s", source, reason_blocks, payload)
            protector_hijack.record_blocked(source, reason_blocks)
        else:
            logging.info("Allowed message from %s: payload=%s", source, payload)

    await producer


if __name__ == "__main__":
    asyncio.run(main_loop())
