import time
import threading
import random
from datetime import datetime

try:
    import keyboard
except Exception:
    keyboard = None


class AutoKeyEngine:
    """
    PRO Engine:
    - multiple keys (sequence)
    - fixed or random/smart interval
    - stop hotkey
    - thread-safe start/stop
    """

    def __init__(self, log_fn=None, status_fn=None, stats_fn=None):
        self.log_fn = log_fn or (lambda msg, lvl="info": None)
        self.status_fn = status_fn or (lambda status: None)
        self.stats_fn = stats_fn or (lambda presses, runtime_sec: None)

        self._thread = None
        self._stop_event = threading.Event()

        # Settings
        self.keys = ["e"]              # list[str]
        self.stop_key = "f12"

        # interval modes: "fixed" | "random" | "smart"
        self.interval_mode = "fixed"
        self.interval_fixed = 13

        # random interval range
        self.interval_min = 10
        self.interval_max = 18

        # smart jitter: fixed +- jitter%
        self.smart_base = 13
        self.smart_jitter_pct = 12     # percent

        # Runtime stats
        self._presses = 0
        self._started_at = None

    def _log(self, msg: str, lvl="info"):
        ts = datetime.now().strftime("%H:%M:%S")
        self.log_fn(f"[{ts}] {msg}", lvl)

    def _status(self, status: str):
        self.status_fn(status)

    def is_running(self) -> bool:
        return self._thread is not None and self._thread.is_alive()

    def start(self):
        if keyboard is None:
            raise RuntimeError("Missing dependency: 'keyboard'. Install with pip install keyboard")

        if self.is_running():
            self._log("Engine already running", "warning")
            return

        if not self.keys:
            raise ValueError("Keys list is empty.")

        self._stop_event.clear()
        self._presses = 0
        self._started_at = time.time()

        self._thread = threading.Thread(target=self._run, daemon=True)
        self._thread.start()

    def stop(self):
        self._stop_event.set()

    def _pick_next_interval(self) -> float:
        if self.interval_mode == "fixed":
            return float(max(1, int(self.interval_fixed)))

        if self.interval_mode == "random":
            mn = float(max(1, self.interval_min))
            mx = float(max(mn, self.interval_max))
            return random.uniform(mn, mx)

        # smart: base +- jitter%
        base = float(max(1, self.smart_base))
        jitter = float(max(0, self.smart_jitter_pct)) / 100.0
        low = base * (1.0 - jitter)
        high = base * (1.0 + jitter)
        if low < 1:
            low = 1
        return random.uniform(low, high)

    def _runtime_seconds(self) -> int:
        if not self._started_at:
            return 0
        return int(time.time() - self._started_at)

    def _tick_stats(self):
        self.stats_fn(self._presses, self._runtime_seconds())

    def _press_key(self, k: str):
        keyboard.press(k)
        time.sleep(0.03)
        keyboard.release(k)

    def _run(self):
        self._status("RUNNING")
        self._log("Engine started", "success")
        self._log(f"Stop key: {self.stop_key.upper()}", "warning")
        self._log(f"Keys: {', '.join(self.keys)}", "highlight")
        self._log(f"Interval mode: {self.interval_mode}", "highlight")

        try:
            idx = 0
            next_interval = self._pick_next_interval()
            next_fire = time.time() + next_interval

            while not self._stop_event.is_set():
                # Hot stop
                if keyboard.is_pressed(self.stop_key):
                    self._log(f"Stop key '{self.stop_key}' detected", "warning")
                    break

                now = time.time()
                if now >= next_fire:
                    key = self.keys[idx % len(self.keys)]
                    idx += 1

                    try:
                        self._press_key(key)
                        self._presses += 1
                        self._log(f"Pressed '{key}'  | next in ~{next_interval:.1f}s", "info")
                    except Exception as e:
                        self._log(f"Key press error: {e}", "error")

                    self._tick_stats()
                    next_interval = self._pick_next_interval()
                    next_fire = time.time() + next_interval

                # keep CPU low + responsive stop
                time.sleep(0.05)

        except Exception as e:
            self._log(f"Engine crashed: {e}", "error")
        finally:
            self._status("STOPPED")
            self._tick_stats()
            self._log("Engine stopped", "info")
