import asyncio
import time
from dotenv import load_dotenv
from loguru import logger

from pipecat.pipeline.runner import PipelineRunner

from utils import constants
from utils.session_state import SessionState

from bot.config import load_system_instruction, cleanup_session, BotSessionState, ChunkState, TranscriptManager
from bot.pipeline import create_vad_analyzer, create_transport, create_stt_service, create_llm_service, create_tts_service, create_context_and_tools, create_processors, create_llm_text_processor, create_pipeline, create_task
from bot.session_handlers import create_audio_data_handler, create_transcript_handler, create_client_message_handler, create_cleanup_if_no_user, create_participant_joined_handler, create_idle_timeout_handler, create_app_review_trigger, create_first_participant_joined_handler, create_participant_left_handler, create_client_disconnected_handler, create_client_ready_handler
from fnHandlers import create_generate_image_handler, create_azure_edit_image_handler, create_cf_image_handler, create_video_handler, create_video_from_image_handler

load_dotenv()


async def run_bot_webrtc_global_agent(room_url: str, token: str, device_id: str = None, room_name: str = None, version: str = None, character_id: str = None):
    bot_start_time = time.time()
    device_id = device_id or "common"
    session_id = room_name
    
    session_state = SessionState()
    await session_state.start_queue_processor()
    
    # Initialize bot session
    bot_session = BotSessionState(session_id, device_id, bot_start_time)
    bot_session.character_id = character_id
    
    # Load character info if character_id is provided
    character_voice = None  # Will use default if None
    if character_id:
        from utils import turso_client
        character = await turso_client.get_character_by_id(character_id)
        if character:
            bot_session.character_name = character.get('display_name')
            character_voice = character.get('voice_id')  # Get character's voice
            # Characters don't have image generation - only conversation
            logger.info(f"SESSION_START: character={character_id} name={character.get('display_name')} voice={character_voice} mode=conversation_only")
        else:
            logger.warning(f"CHARACTER_NOT_FOUND: character={character_id} falling_back_to_maya")
            bot_session.character_id = None  # Fall back to Maya
    else:
        # Maya session - full features (images + conversation)
        logger.info(f"SESSION_START: Maya (global agent) with images and conversation")
    
    # Load system instruction based on character
    system_instruction = await load_system_instruction(bot_session.character_id)
    
    # Check if video generation is enabled for this user
    # Video is only enabled if: version == "explore" AND global config allows it AND user is video_premium
    from utils.turso_client import is_video_generation_enabled, is_video_premium_user
    video_global_enabled = is_video_generation_enabled()
    
    # Only consider video if version is "explore"
    is_explore_session = version == "explore"
    
    # Check if user has video_premium access
    is_video_premium = False
    if video_global_enabled and is_explore_session and device_id and device_id != 'common':
        try:
            is_video_premium = await is_video_premium_user(device_id)
        except Exception as e:
            logger.error(f"VIDEO_PREMIUM_CHECK_FAILED: device_id={device_id} error={e}")
            is_video_premium = False
    
    # Video enabled only for video_premium users in explore sessions with global config on
    video_enabled = video_global_enabled and is_explore_session and is_video_premium
    
    logger.info(f"VIDEO_CHECK: session_id={session_id} device_id={device_id} global_enabled={video_global_enabled} version={version} is_explore={is_explore_session} is_video_premium={is_video_premium} video_enabled={video_enabled}")
    
    # Store video_enabled in bot_session so image_upload can check it
    bot_session.video_enabled = video_enabled
    
    vad_analyzer = create_vad_analyzer(session_id)
    bot_session.vad_analyzer = vad_analyzer
    
    # Create services and store references IMMEDIATELY on bot_session after each creation.
    # This ensures cleanup_session can release them even if a later create_* call throws.
    transport = create_transport(room_url, token, vad_analyzer)
    bot_session.transport = transport
    
    stt = create_stt_service()
    bot_session.stt_service = stt
    
    llm = create_llm_service(system_instruction)
    bot_session.llm_service = llm
    
    tts = create_tts_service(speaker=character_voice)  # Use character's voice if available
    session_state.tts_service = tts
    
    # Create context with tools based on character capabilities
    context_aggregator = create_context_and_tools(
        llm, 
        system_instruction, 
        include_video=video_enabled,
        can_generate_images=bot_session.can_generate_images(),
        can_edit_images=bot_session.can_edit_images()
    )
    bot_session.context_aggregator = context_aggregator
    
    rtvi, transcript, audio_buffer = create_processors()
    llm_text_processor = create_llm_text_processor()
    transcript_manager = TranscriptManager()
    chunk_state = ChunkState()
    
    pipeline = create_pipeline(transport, stt, context_aggregator, rtvi, llm, tts, audio_buffer, transcript, llm_text_processor)
    bot_session.pipeline = pipeline
    task = create_task(pipeline, rtvi, bot_session)
    
    audio_buffer.event_handler("on_audio_data")(create_audio_data_handler(bot_session, chunk_state))
    
    trigger_app_review_if_eligible = create_app_review_trigger(bot_session, rtvi)
    
    # Register image generation handlers only if character can generate images (Maya only)
    if bot_session.can_generate_images():
        llm.register_function("generate_image", create_generate_image_handler(
            session_state, rtvi, bot_session, transcript_manager, task, llm, trigger_app_review_if_eligible
        ))
        logger.debug(f"FUNCTION_REGISTERED: generate_image for maya")
        
        try:
            llm.register_function("cf_image", create_cf_image_handler(
                session_state, rtvi, bot_session, transcript_manager, task, llm, trigger_app_review_if_eligible
            ))
            logger.debug(f"FUNCTION_REGISTERED: cf_image for maya")
        except Exception as e:
            logger.error(f"Failed to register cf_image handler: {e}", exc_info=True)
    
    # Register edit handlers only if character can edit images (Maya only)
    if bot_session.can_edit_images():
        try:
            llm.register_function("azure_edit_image", create_azure_edit_image_handler(
                session_state, rtvi, llm, bot_session, transcript_manager, task, trigger_app_review_if_eligible
            ))
            logger.debug(f"FUNCTION_REGISTERED: azure_edit_image for maya")
        except Exception as e:
            logger.error(f"Failed to register azure_edit_image handler: {e}", exc_info=True)
    
    # Register video handlers if enabled (version="explore" AND global config allows it)
    if video_enabled:
        try:
            # Text-to-Video handler
            llm.register_function("generate_video", create_video_handler(
                session_state, rtvi, bot_session, transcript_manager, task, llm
            ))
            # Image-to-Video handler
            llm.register_function("generate_video_from_image", create_video_from_image_handler(
                session_state, rtvi, bot_session, transcript_manager, task, llm
            ))
            logger.info(f"VIDEO_HANDLERS_REGISTERED: session_id={session_id} (t2v + i2v)")
        except Exception as e:
            logger.error(f"Failed to register video handlers: {e}", exc_info=True)
    
    transcript.event_handler("on_transcript_update")(create_transcript_handler(bot_session, transcript_manager, rtvi))
    rtvi.event_handler("on_client_ready")(create_client_ready_handler())
    rtvi.event_handler("on_client_message")(create_client_message_handler(bot_session, rtvi, task, transcript_manager))
    
    cleanup_if_no_user = create_cleanup_if_no_user(bot_session, session_state, task)
    
    transport.event_handler("on_participant_joined")(create_participant_joined_handler(bot_session, task))
    transport.event_handler("on_first_participant_joined")(create_first_participant_joined_handler(bot_session, rtvi, task, audio_buffer, chunk_state))
    transport.event_handler("on_participant_left")(create_participant_left_handler(bot_session, session_state, transcript_manager, chunk_state, task))
    transport.event_handler("on_client_disconnected")(create_client_disconnected_handler(bot_session, task))
    task.event_handler("on_idle_timeout")(create_idle_timeout_handler(bot_session))

    runner = PipelineRunner(handle_sigint=False)
    
    bot_session.no_user_timeout_task = asyncio.create_task(cleanup_if_no_user())
    logger.info(f"NO_USER_TIMEOUT_STARTED: session_id={session_id} timeout={constants.NO_USER_TIMEOUT}s")
    
    try:
        await runner.run(task)
    except Exception as e:
        logger.error(f"PIPELINE_ERROR: session_id={session_id} error={e}", exc_info=True)
        await task.cancel()
    finally:
        logger.debug(f"RUNNER_CLEANUP_START: session_id={session_id}")
        
        bot_session.cancel_timeout_task()
        if bot_session.no_user_timeout_task:
            try:
                await bot_session.no_user_timeout_task
            except asyncio.CancelledError:
                pass
        
        # Timeout cleanup to prevent stuck native threads from blocking forever.
        # If pipeline.cleanup() or transport.cleanup() hangs on C++ thread joins,
        # we force-release references after 30s so the task always completes.
        try:
            await asyncio.wait_for(
                cleanup_session(session_state, bot_session, session_id, transcript_manager, chunk_state),
                timeout=30.0
            )
        except asyncio.TimeoutError:
            logger.error(
                f"CLEANUP_TIMEOUT: session_id={session_id} cleanup hung for 30s, forcing release"
            )
            # Force-null everything to drop references even if native cleanup hung
            bot_session.pipeline = None
            bot_session.transport = None
            bot_session.vad_analyzer = None
            bot_session.stt_service = None
            bot_session.llm_service = None
            bot_session.audio_buffer = None
            bot_session.context_aggregator = None
        
        logger.debug(f"RUNNER_CLEANUP_COMPLETE: session_id={session_id}")
