"""
Async Runner Utility for Gevent-Compatible Async Execution

This module provides a thread-safe way to run asyncio coroutines from
synchronous code running under gevent workers. It creates a dedicated
background REAL OS thread with its own event loop, isolated from gevent's loop.

CRITICAL: When gevent monkey-patches threading.Thread, it replaces real OS
threads with greenlets. Asyncio event loops CANNOT run in greenlets - they
require real OS threads. This module uses gevent.monkey.get_original() to
obtain the unpatched Thread class.

Usage:
    from utils.async_runner import run_async
    
    # In a sync Flask route:
    result = run_async(some_async_function(arg1, arg2))
"""

import asyncio
import threading
import logging
import traceback

logger = logging.getLogger(__name__)

# Get the REAL OS threading.Thread, not gevent's patched greenlet version.
# This is critical: gevent patches threading.Thread to use greenlets,
# but asyncio event loops require real OS threads to function correctly.
_RealThread = None
try:
    from gevent.monkey import get_original
    _RealThread = get_original('threading', 'Thread')
    logger.info("async_runner: Using real OS thread (gevent monkey-patch detected)")
except (ImportError, AttributeError):
    _RealThread = threading.Thread
    logger.info("async_runner: Using standard threading.Thread (no gevent)")

# Module-level event loop and thread (one per worker process)
_loop = None
_thread = None
_lock = threading.Lock()


def _start_background_loop(loop):
    """Run the event loop in a background thread."""
    asyncio.set_event_loop(loop)
    loop.run_forever()


def get_or_create_loop():
    """
    Get or create a background event loop running in a dedicated REAL OS thread.
    Thread-safe via lock. One loop per worker process.
    """
    global _loop, _thread
    
    with _lock:
        need_new = (
            _loop is None or _loop.is_closed() or
            _thread is None or not _thread.is_alive()
        )
        
        if need_new:
            if _loop is not None and not _loop.is_closed():
                _loop.call_soon_threadsafe(_loop.stop)
            
            _loop = asyncio.new_event_loop()
            _thread = _RealThread(
                target=_start_background_loop,
                args=(_loop,),
                daemon=True,
                name="async-runner-loop"
            )
            _thread.start()
            logger.info("Started background asyncio event loop in real OS thread")
    
    return _loop


def run_async(coro, timeout=300):
    """
    Run an asyncio coroutine from synchronous code.
    
    Safe to call from gevent workers - the coroutine runs in a dedicated
    real OS thread with its own event loop, completely isolated from
    gevent's greenlet-based concurrency.
    
    Args:
        coro: An asyncio coroutine (the result of calling an async function)
        timeout: Maximum seconds to wait for the coroutine to complete.
                 Default 300s (5 min) to match Gunicorn timeout.
    
    Returns:
        The return value of the coroutine.
    
    Raises:
        TimeoutError: If the coroutine doesn't complete within timeout.
        Any exception raised by the coroutine.
    """
    loop = get_or_create_loop()
    future = asyncio.run_coroutine_threadsafe(coro, loop)
    
    try:
        return future.result(timeout=timeout)
    except TimeoutError:
        future.cancel()
        logger.error(f"Async operation timed out after {timeout}s")
        raise
    except Exception as e:
        error_type = type(e).__name__
        error_msg = str(e) or "(no message)"
        logger.error(f"Async operation failed [{error_type}]: {error_msg}")
        logger.error(f"Async operation traceback:\n{traceback.format_exc()}")
        raise
