from __future__ import annotations

import asyncio
import threading
import time
from typing import Coroutine


class LoopThread:
    def __init__(self, name: str):
        self._name = name
        self._loop: asyncio.AbstractEventLoop | None = None
        self._thread: threading.Thread | None = None
        self._ready = threading.Event()
        self._start()

    def _start(self):
        def run_loop():
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            self._loop = loop
            self._ready.set()
            loop.run_forever()

        thread = threading.Thread(target=run_loop, daemon=True, name=self._name)
        thread.start()
        self._ready.wait()
        self._thread = thread

    @property
    def loop(self) -> asyncio.AbstractEventLoop:
        if self._loop is None:
            raise RuntimeError("Loop not initialized")
        return self._loop

    def submit(self, coro: Coroutine):
        return asyncio.run_coroutine_threadsafe(coro, self.loop)

    def stop(self):
        if self._loop is None:
            return
        self._loop.call_soon_threadsafe(self._loop.stop)


class LoopLagMonitor:
    def __init__(
        self,
        loop: asyncio.AbstractEventLoop,
        name: str,
        interval: float = 1.0,
        warn_threshold: float = 2.0,
        warn_interval: float = 10.0,
    ):
        self._loop = loop
        self._name = name
        self._interval = interval
        self._warn_threshold = warn_threshold
        self._warn_interval = warn_interval
        self._stop = threading.Event()
        self._last_tick = time.time()
        self._last_warn = 0.0
        self._tick_seen = False
        self._thread = threading.Thread(
            target=self._run,
            daemon=True,
            name=f"{name}-watchdog",
        )
        self._thread.start()

    def _tick(self) -> None:
        self._last_tick = time.time()
        self._tick_seen = True

    def _run(self) -> None:
        while not self._stop.is_set():
            try:
                self._loop.call_soon_threadsafe(self._tick)
            except RuntimeError:
                return
            time.sleep(self._interval)
            if not self._tick_seen:
                continue
            lag = time.time() - self._last_tick
            if lag < self._warn_threshold:
                continue
            now = time.time()
            if now - self._last_warn < self._warn_interval:
                continue
            self._last_warn = now
            print(f"[LoopWatchdog] {self._name} blocked for {lag:.2f}s")

    def stop(self) -> None:
        self._stop.set()


_runtime_loop = LoopThread("flownet-runtime-loop")


def get_runtime_loop() -> asyncio.AbstractEventLoop:
    return _runtime_loop.loop


def submit_runtime_coro(coro: Coroutine):
    return _runtime_loop.submit(coro)


def new_user_loop() -> LoopThread:
    return LoopThread("flownet-user-loop")
