#!/usr/bin/env python3
"""
Simple Tkinter GUI + asyncio simulation runner.

- Shows driver current speed and RPM.
- Lets the user inject a spoofed telemetry packet (speed & rpm).
- Re-uses SpoofingProtector.sign(...) for HMAC generation.
"""


import asyncio
import threading
import time
import logging
from typing import Any, Dict, Optional
import tkinter as tk
from tkinter import ttk

#import protection modules 
try:
    from security.spoofing_protection import SpoofingProtector
    from security.hijacking_protection import HijackingProtector
    from security.engineer_interface import EngineerInterface
except Exception: 
    class SpoofingProtector:
        secret = "demo-secret"

        @staticmethod
        def sign(payload: Dict[str, Any], secret: str) -> str:
            return "hmac-demo"

        def inspect_message(self, msg_type: str, source: str, payload: Dict[str, Any], hmac_tag: str):
            return {"valid": True, "reason": None}

    class HijackingProtector:
        def observe_message(self, msg_type: str, source: str, rssi: float):
            return {}

        def record_blocked(self, source: str, reasons: list):
            logging.info("(stub) recorded blocked %s: %s", source, reasons)

    class EngineerInterface:
        @staticmethod
        def is_engineer_payload(payload):
            return False

        @staticmethod
        def validate_token(token):
            return False

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

# shared data
shared_state: Dict[str, Dict[str, Any]] = {}
state_lock = threading.Lock()


_async_ctx = {"loop": None, "queue": None, "local_stop": None}

async def producer(queue: asyncio.Queue, stop_event: asyncio.Event):
    driver_id = "driver_device_01"
    protector = SpoofingProtector()
    secret = getattr(protector, "secret", "")
    i = 0
    while not stop_event.is_set():
        telemetry = {"speed_mph": 140 + (i % 10), "rpm": 7000 + (i % 100)}
        tag = SpoofingProtector.sign(payload=telemetry, secret=secret)
        await queue.put(("telemetry", driver_id, telemetry, 0.8, tag))
        if i % 6 == 0:
            control = {"cmd": "set_mode", "mode": "race_preset"}
            await queue.put(("control", "engineer_remote_01", control, 0.9,
                             SpoofingProtector.sign(payload=control, secret=secret)))
        i += 1
        await asyncio.sleep(0.5)
    
    await queue.put(("system", "simulator", {"event": "END"}, 1.0, ""))


async def consumer_loop(queue: asyncio.Queue, stop_event: asyncio.Event):
    protector_spoof = SpoofingProtector()
    protector_hijack = HijackingProtector()
    engineer_iface = EngineerInterface()
    
    running = True
    while running and not stop_event.is_set():
        try:
            msg_type, source, payload, rssi, hmac_tag = await asyncio.wait_for(queue.get(), timeout=1.0)
        except asyncio.TimeoutError:
            continue
        if msg_type == "system" and isinstance(payload, dict) and payload.get("event") == "END":
            running = False
            logger.info("Simulation ending.")
            continue

        spoof_result = protector_spoof.inspect_message(msg_type=msg_type, source=source, payload=payload, hmac_tag=hmac_tag)
        hijack_result = protector_hijack.observe_message(msg_type=msg_type, source=source, rssi=rssi)

        allow = True
        if not spoof_result.get("valid", True):
            allow = False
        if hijack_result.get("action") == "soft_lockdown_active":
            # allow engineers with valid token; else block (minimal logic)
            if not (msg_type == "control" and engineer_iface.is_engineer_payload(payload) and engineer_iface.validate_token(payload.get("token"))):
                allow = False

        if allow:
            if msg_type == "telemetry" and isinstance(payload, dict):
                with state_lock:
                    cur = shared_state.setdefault(source, {})
                    if "speed_mph" in payload:
                        cur["speed_mph"] = payload["speed_mph"]
                    if "rpm" in payload:
                        cur["rpm"] = payload["rpm"]
                    cur["last_update"] = time.time()
        else:
            protector_hijack.record_blocked(source, ["blocked_by_policy"])

    logger.info("Consumer loop exited.")


async def async_main(stop_event: asyncio.Event):
    loop = asyncio.get_running_loop()
    q: asyncio.Queue = asyncio.Queue()
    _async_ctx["queue"] = q
    prod = asyncio.create_task(producer(q, stop_event))
    cons = asyncio.create_task(consumer_loop(q, stop_event))
    await asyncio.gather(prod, cons)



def start_async_thread() -> threading.Thread:
    def thread_target():
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        local_stop = asyncio.Event()
        _async_ctx["local_stop"] = local_stop
        _async_ctx["loop"] = loop
        try:
            loop.run_until_complete(async_main(local_stop))
        finally:
            _async_ctx["queue"] = None
            _async_ctx["loop"] = None
            _async_ctx["local_stop"] = None
            loop.close()

    th = threading.Thread(target=thread_target, daemon=True, name="async-sim")
    th.start()
    # wait for queue to be present (short timeout)
    waited = 0.0
    while (_async_ctx.get("loop") is None or _async_ctx.get("queue") is None) and waited < 3.0:
        time.sleep(0.05)
        waited += 0.05
    if _async_ctx.get("loop") is None or _async_ctx.get("queue") is None:
        raise RuntimeError("Failed to start async thread")
    return th


def stop_async_thread():
    loop = _async_ctx.get("loop")
    local_stop = _async_ctx.get("local_stop")
    if loop and local_stop:
        try:
            loop.call_soon_threadsafe(local_stop.set)
        except Exception:
            logger.exception("Failed to signal async loop")


def inject_spoofed_telemetry(speed: float, rpm: int) -> bool:
    loop = _async_ctx.get("loop")
    queue = _async_ctx.get("queue")
    if not loop or not queue:
        logger.error("Simulation not running")
        return False
    protector = SpoofingProtector()
    payload = {"speed_mph": float(speed), "rpm": int(rpm)}
    tag = SpoofingProtector.sign(payload=payload, secret=getattr(protector, "secret", ""))
    try:
        fut = asyncio.run_coroutine_threadsafe(queue.put(("telemetry", "driver_device_01", payload, 0.3, tag)), loop)
        fut.result(timeout=1.0)
        return True
    except Exception as exc:
        logger.exception("Failed to inject: %s", exc)
        return False



class SimpleGUI:
    def __init__(self, root: tk.Tk):
        self.root = root
        root.title("Simple Race GUI")
        self._async_thread: Optional[threading.Thread] = None

        self.speed_var = tk.StringVar(value="—")
        self.rpm_var = tk.StringVar(value="—")

        frm = ttk.Frame(root, padding=10)
        frm.grid(sticky="nsew")
        ttk.Label(frm, text="Driver: driver_device_01").grid(column=0, row=0, sticky="w")

        ttk.Label(frm, text="Speed (mph):").grid(column=0, row=1, sticky="w")
        ttk.Label(frm, textvariable=self.speed_var, width=10).grid(column=1, row=1, sticky="w")

        ttk.Label(frm, text="RPM:").grid(column=0, row=2, sticky="w")
        ttk.Label(frm, textvariable=self.rpm_var, width=10).grid(column=1, row=2, sticky="w")

        
        ttk.Separator(frm).grid(column=0, row=3, columnspan=2, pady=8, sticky="ew")
        ttk.Label(frm, text="Inject spoofed telemetry").grid(column=0, row=4, sticky="w")
        self.inject_speed = tk.DoubleVar(value=200.0)
        self.inject_rpm = tk.IntVar(value=8000)
        ttk.Entry(frm, textvariable=self.inject_speed, width=10).grid(column=1, row=5, sticky="w")
        ttk.Entry(frm, textvariable=self.inject_rpm, width=10).grid(column=1, row=6, sticky="w")
        ttk.Button(frm, text="Inject", command=self.on_inject).grid(column=0, row=7, pady=6)
        ttk.Button(frm, text="Start", command=self.start).grid(column=0, row=8, pady=6)
        ttk.Button(frm, text="Stop & Exit", command=self.on_quit).grid(column=1, row=8, pady=6)

        
        self._poll_ui()

    def start(self):
        if _async_ctx.get("loop") is not None:
            logger.info("Already running")
            return
        self._async_thread = start_async_thread()
        logger.info("Started async simulation")

    def on_inject(self):
        s = self.inject_speed.get()
        r = self.inject_rpm.get()
        ok = inject_spoofed_telemetry(s, r)
        if ok:
            logger.info("Injected spoofed telemetry: speed=%s rpm=%s", s, r)
        else:
            logger.error("Injection failed")

    def _poll_ui(self):
        with state_lock:
            drv = shared_state.get("driver_device_01", {})
            sp = drv.get("speed_mph", "—")
            rp = drv.get("rpm", "—")
        self.speed_var.set(str(sp))
        self.rpm_var.set(str(rp))
        self.root.after(200, self._poll_ui)

    def on_quit(self):
        logger.info("Shutting down...")
        try:
            stop_async_thread()
        except Exception:
            pass
        if self._async_thread and self._async_thread.is_alive():
            self._async_thread.join(timeout=1.0)
        try:
            self.root.quit()
            self.root.destroy()
        except Exception:
            pass


def main():
    root = tk.Tk()
    SimpleGUI(root)
    root.mainloop()


if __name__ == "__main__":
    main()

