from __future__ import annotations

import queue
import threading
from collections.abc import Callable
from typing import TypeVar

T = TypeVar("T")


def run_with_hard_timeout(operation: Callable[[], T], *, timeout_sec: int, operation_name: str) -> T:
    result_queue: queue.Queue[tuple[bool, object]] = queue.Queue(maxsize=1)

    def _runner() -> None:
        try:
            result_queue.put((True, operation()))
        except BaseException as exc:  # noqa: BLE001
            result_queue.put((False, exc))

    thread = threading.Thread(target=_runner, name=f"hard-timeout:{operation_name}", daemon=True)
    thread.start()
    thread.join(timeout=max(1, int(timeout_sec)))
    if thread.is_alive():
        raise TimeoutError(f"{operation_name} exceeded hard timeout ({timeout_sec}s)")
    if result_queue.empty():
        raise TimeoutError(f"{operation_name} finished without a result")
    ok, value = result_queue.get_nowait()
    if ok:
        return value  # type: ignore[return-value]
    raise value  # type: ignore[misc]

