"""Main entry point for the meeting system (LangGraph version)."""

import argparse
import asyncio
import sys
import time
import random
from typing import Dict, Any, Optional

from dotenv import load_dotenv
load_dotenv()  # Load environment variables (including LangSmith config)

from rich.console import Console
from rich.text import Text

from thetable_poc.core import ParticipantFactory, Role
from thetable_poc.graph.workflow import create_workflow
from thetable_poc.config import settings, load_participants
from thetable_poc.graph.time_management import get_phase_time_info, TimeUrgency
from thetable_poc.graph.agenda_extraction import format_agenda_status_bar, format_agenda_phase_summary

console = Console()

async def run_graph_with_interrupts(app, initial_state: Dict[str, Any], config: Dict[str, Any]):
    """Run the graph with human-in-the-loop interrupt handling and real-time streaming."""

    input_state: Optional[Dict[str, Any]] = initial_state

    # Build role map for display
    role_map = {p["name"]: p["role"] for p in initial_state.get("participants", [])}

    # Assign random colors to participants (avoiding time urgency colors: red/yellow/green)
    participants_list = initial_state.get("participants", [])
    _SPEAKER_COLORS = ["cyan", "magenta", "blue", "dark_orange", "purple", "bright_white"]
    colors = random.sample(_SPEAKER_COLORS, min(len(participants_list), len(_SPEAKER_COLORS)))
    speaker_color_map = {p["name"]: colors[i] for i, p in enumerate(participants_list)}

    # Build role → name map for dynamic speaker lookup
    role_to_name = {p["role"]: p["name"] for p in initial_state.get("participants", [])}

    # Track previous phase for transition detection
    previous_phase = None

    while True:
        current_node = None
        current_speaker = None
        next_speaker_name = None  # Track who will speak next
        is_streaming = False
        
        # Stream graph execution with real-time token streaming
        async for event in app.astream_events(input_state, config=config, version="v2"):
            event_type = event.get("event")
            
            # Track which node is currently executing
            if event_type == "on_chain_start":
                node_name = event.get("name")
                if node_name in ["opening", "status_check", "speak", "issue_resolution", "closing", "select_speaker"]:
                    current_node = node_name
                if node_name == "select_speaker":
                    console.print("\n🤔 [Host] 다음 발언자 선택 중...", style="dim", end="")
            
            # Real-time token streaming from LLM
            if event_type == "on_chat_model_stream":
                chunk = event.get("data", {}).get("chunk")
                if chunk and hasattr(chunk, "content") and chunk.content:
                    # Print header on first token
                    if not is_streaming:
                        is_streaming = True
                        # Get speaker name from metadata if available
                        metadata = event.get("metadata", {})
                        langgraph_node = metadata.get("langgraph_node", current_node)
                        
                        # Determine speaker based on node (dynamic lookup)
                        if langgraph_node == "opening":
                            current_speaker = role_to_name.get("host", "Host")
                        elif langgraph_node == "status_check":
                            current_speaker = role_to_name.get("pm", "PM")
                        elif langgraph_node == "issue_resolution":
                            current_speaker = role_to_name.get("tech_lead", "Tech Lead")
                        elif langgraph_node == "closing":
                            current_speaker = role_to_name.get("host", "Host")
                        elif langgraph_node == "speak":
                            # Use tracked next_speaker_name
                            current_speaker = next_speaker_name or "AI"
                        elif langgraph_node == "select_speaker":
                            current_speaker = None  # Selection logic, no display
                        
                        if current_speaker:
                            role = role_map.get(current_speaker, "")
                            role_display = f"[{role}]" if role else ""
                            color = speaker_color_map.get(current_speaker, "white")
                            header = Text()
                            header.append(f"\n{current_speaker}", style=f"bold {color}")
                            header.append(f"{role_display}", style=color)
                            header.append(": ")
                            console.print(header, end="")
                    
                    # Stream token in real-time
                    if current_speaker:
                        print(chunk.content, end="", flush=True)
            
            # Handle chain end to finalize output
            if event_type == "on_chain_end":
                node_name = event.get("name")
                output = event.get("data", {}).get("output", {})
                
                if is_streaming and node_name == current_node:
                    print()  # Newline after streaming completes
                    print("-" * 50)
                    is_streaming = False
                
                # Handle select_speaker output - track next_speaker for display
                if node_name == "select_speaker" and isinstance(output, dict):
                    next_spk = output.get("next_speaker")
                    if next_spk:
                        next_speaker_name = next_spk  # Store for speak node
                        color = speaker_color_map.get(next_spk, "white")
                        turn_text = Text("\n[🔄 Turn] Host selected: ")
                        turn_text.append(next_spk, style=f"bold {color}")
                        console.print(turn_text)

                        # Display time information for current phase
                        current_state = app.get_state(config)
                        current_phase_name = "unknown"  # Default value
                        if current_state and current_state.values:
                            state_values = current_state.values

                            # Check for phase transition and display agenda summary
                            current_phase_name = state_values.get("current_phase", "unknown")
                            if previous_phase is not None and previous_phase != current_phase_name:
                                # Phase transition detected
                                agenda_items = state_values.get("agenda_items", [])
                                if agenda_items:
                                    summary = format_agenda_phase_summary(
                                        agenda_items,
                                        previous_phase,
                                        current_phase_name
                                    )
                                    if summary:
                                        console.print(f"\n{summary}\n")

                            # Update previous phase for next iteration
                            previous_phase = current_phase_name
                            elapsed, remaining, percentage, urgency = get_phase_time_info(state_values)
                            current_phase_name = state_values.get("current_phase", "unknown")

                            # Color code based on urgency using rich styles
                            urgency_styles = {
                                TimeUrgency.HARD_CUTOFF: ("red", "🔴"),
                                TimeUrgency.URGENT_WARNING: ("yellow", "🟡"),
                                TimeUrgency.SOFT_WARNING: ("yellow", "🟡"),
                            }
                            style, icon = urgency_styles.get(urgency, ("green", "🟢"))
                            console.print(
                                f"{icon} [시간] {current_phase_name}: {int(elapsed)}초/{int(elapsed+remaining)}초 ({percentage:.0f}%) - {urgency.value}",
                                style=style
                            )

                        # Display debug information using rich dim style
                        debug_info = output.get("selection_debug", {})
                        if debug_info:
                            # Check for time cutoff
                            if debug_info.get("time_cutoff"):
                                elapsed = debug_info.get("elapsed", 0)
                                percentage = debug_info.get("percentage", 0)
                                phase = debug_info.get("phase", "unknown")
                                console.print(f"\n⏰ TIME CUTOFF: {phase} 페이즈 시간 초과 ({percentage}%, {elapsed}초 경과), 강제 종료")
                            else:
                                # Extract debug data
                                last_speaker = debug_info.get("last_speaker", "Unknown")
                                newly_mentioned = debug_info.get("newly_mentioned", [])
                                responded = debug_info.get("responded", [])
                                pending_before = debug_info.get("pending_before", [])
                                pending_after = debug_info.get("pending_after", [])
                                reason = debug_info.get("reason", "알 수 없음")
                                speaker_counts = debug_info.get("speaker_counts", {})
                                selected = debug_info.get("selected_speaker", next_spk)
                                speak_count = speaker_counts.get(selected, 0)

                                # Build debug output using console with dim style
                                console.print(f"  ┌─ Debug: Host's Decision Process", style="dim", end="")
                                console.print(f"\n  │ 📍 Current phase: {current_phase_name}", style="dim", end="")
                                console.print(f"\n  │ 💬 Last speaker: {last_speaker}", style="dim", end="")

                                # Show newly mentioned participants from LAST message only
                                if newly_mentioned:
                                    console.print(f"\n  │ 📝 Mentioned in last message: {', '.join(newly_mentioned)}", style="dim", end="")
                                else:
                                    console.print(f"\n  │ 📝 No mentions in last message", style="dim", end="")

                                # Show who responded from pending list
                                if responded:
                                    console.print(f"\n  │ ✅ Responded from pending: {', '.join(responded)}", style="dim", end="")

                                # Show pending list changes
                                pending_before_str = ', '.join(pending_before) if pending_before else "없음"
                                pending_after_str = ', '.join(pending_after) if pending_after else "없음"
                                console.print(f"\n  │ ⏳ Pending (before): [{pending_before_str}]", style="dim", end="")
                                console.print(f"\n  │ ⏳ Pending (after):  [{pending_after_str}]", style="dim", end="")

                                # Show selection reason and speak count
                                console.print(f"\n  │ 🎯 Selection reason: {reason}", style="dim", end="")
                                console.print(f"\n  │ 🔢 {selected}'s speak count: {speak_count}회 → {speak_count + 1}회 (after this turn)", style="dim", end="")
                                console.print(f"\n  └─", style="dim")

                        # Display agenda status bar
                        current_state = app.get_state(config)
                        if current_state and current_state.values:
                            agenda_items = current_state.values.get("agenda_items", [])
                            if agenda_items:
                                status_bar = format_agenda_status_bar(agenda_items)
                                if status_bar:
                                    console.print(f"\n{status_bar}")

                        if next_spk == "FINISH":
                            print("🛑 Discussion ending -> Moving to Closing Phase")
        
        # Check graph state after streaming completes
        current_state = app.get_state(config)
        
        # If no next nodes, graph is complete
        if not current_state.next:
            print("\n✅ 회의가 종료되었습니다.")
            break
        
        # If interrupted before user_input, prompt for user input
        if "user_input" in current_state.next:
            next_speaker = current_state.values.get("next_speaker", "User")
            # Get role for display
            participants = current_state.values.get("participants", [])
            role_map = {p["name"]: p["role"] for p in participants}
            role = role_map.get(next_speaker, "")
            role_display = f"[{role}]" if role else ""
            color = speaker_color_map.get(next_speaker, "white")
            prompt_text = Text("\n💬 ")
            prompt_text.append(f"{next_speaker}{role_display}", style=f"bold {color}")
            prompt_text.append(" 발언 차례입니다. 입력해주세요:")
            console.print(prompt_text)
            
            try:
                user_input = input("> ").strip()

                if not user_input:
                    user_input = "(패스)"

                # Display user's message immediately with color
                color = speaker_color_map.get(next_speaker, "white")
                msg_text = Text(f"\n")
                msg_text.append(f"{next_speaker}", style=f"bold {color}")
                msg_text.append(f"{role_display}", style=color)
                msg_text.append(f": {user_input}")
                console.print(msg_text)
                print("-" * 50)

                # IMPORTANT: Use update_state to properly inject user input
                # This updates the checkpoint state before the node executes
                app.update_state(config, {"user_input": user_input})

                # Resume graph with None (state already updated)
                input_state = None

            except UnicodeDecodeError:
                print("\n⚠️ 입력 오류가 발생했습니다. 다시 입력해주세요.")
                continue

            except EOFError:
                print("\n⚠️ 입력 스트림이 종료되었습니다. 자동으로 패스합니다.")
                app.update_state(config, {"user_input": "(패스)"})
                input_state = None
        else:
            # Unexpected interrupt - just resume with None
            input_state = None

def parse_args_and_load_participants():
    """Parse command-line arguments and load participants (synchronous).
    
    This must be called before asyncio.run() to avoid nested event loop conflicts
    with questionary's prompt_toolkit which internally uses asyncio.run().
    
    Returns:
        Tuple of (participants_list, agents_map)
    """
    # Configure stdin to handle UTF-8 encoding errors gracefully
    sys.stdin.reconfigure(errors='replace')

    # Parse CLI arguments
    parser = argparse.ArgumentParser(description="TheTable Meeting System")
    parser.add_argument("--all", action="store_true", help="전원 참여 (선택 스킵)")
    parser.add_argument(
        "--no-select", action="store_true", help="YAML 기본값 사용 (선택 스킵)"
    )
    parser.add_argument("--config", type=str, help="참여자 설정 파일 경로")
    parser.add_argument(
        "--role", type=str,
        choices=["member", "backend", "frontend", "qa", "devops", "security", "tech_lead", "pm"],
        default=None,
        help="사용자 역할 선택 (기본: 인터랙티브 선택)",
    )
    args = parser.parse_args()

    print("🚀 Initializing Meeting System (LangGraph)...")

    # Load and select participants
    try:
        if args.all:
            select_mode = "all"
        elif args.no_select:
            select_mode = "no-select"
        else:
            select_mode = "interactive"

        config_path = args.config or None
        participants_list, agents_map = load_participants(config_path, select_mode, human_role=args.role)

        return participants_list, agents_map

    except Exception as e:
        print(f"Error loading participants: {e}")
        sys.exit(1)


async def main(participants_list, agents_map):
    """Run the meeting simulation.
    
    Args:
        participants_list: List of Participant objects
        agents_map: Dict mapping role.value to Participant
    """

    # 2. Compile Graph
    try:
        app = create_workflow()
        print("✅ Workflow compiled successfully.")
    except Exception as e:
        print(f"Error compiling workflow: {e}")
        return
        
    # 3. Execution Config
    config = {
        "configurable": {
            "thread_id": "session_round_table_1",
            "agents_map": agents_map
        }
    }
    
    # 4. Initial State
    current_time = time.time()
    initial_state = {
        "current_phase": "opening",
        "messages": [],
        "participants": [
            {"name": p.name, "role": p.role.value, "is_ai": p.is_ai}
            for p in agents_map.values()
        ],
        "agenda_items": [],
        "pending_mentions": [],  # Track mentioned participants who haven't responded
        "speaker_counts": {},  # Track how many times each participant has spoken
        "start_time": current_time,
        "phase_start_time": current_time,
        "phase_time_limits": {
            "opening": 60,
            "status_check": settings.meeting.default_agenda_time,       # 300초
            "issue_resolution": settings.meeting.default_agenda_time * 2,  # 600초
        },
        "time_extended": False,
    }
    
    # 5. Run Graph with Interrupt Handling
    print("\n🎬 Starting Round Table Meeting...\n" + "="*50)
    
    try:
        await run_graph_with_interrupts(app, initial_state, config)
    except KeyboardInterrupt:
        print("\n\n🛑 Meeting interrupted by user.")
    except Exception as e:
        print(f"\n❌ Error during execution: {e}")
        import traceback
        traceback.print_exc()

def cli():
    """CLI entry point for uvx/pipx."""
    participants_list, agents_map = parse_args_and_load_participants()
    asyncio.run(main(participants_list, agents_map))

if __name__ == "__main__":
    cli()