"""Graph nodes for meeting phases."""

import time
from typing import Any, Dict, Literal
from langchain_core.messages import HumanMessage, AIMessage
from langchain_core.runnables import RunnableConfig
from rich.console import Console

from .state import MeetingState
from .time_management import (
    get_phase_time_info,
    build_time_context_for_host,
    check_extension_request,
    apply_time_extension,
    TimeUrgency
)
from .agenda_extraction import extract_agenda_updates
from thetable_poc.meeting.mention_parser import parse_mentions
from thetable_poc.core import Role

console = Console()

# Lazy singleton for fast LLM (for agenda extraction and other fast tasks)
_fast_llm = None

def _get_fast_llm():
    """Get or create the fast LLM instance for fast tasks."""
    global _fast_llm
    if _fast_llm is None:
        from thetable_poc.config import settings
        _fast_llm = settings.create_fast_llm()
    return _fast_llm

def _get_agent_by_role(role: str, config: RunnableConfig) -> Any:
    """Retrieve agent instance from config by role."""
    # Agents should be passed in config["configurable"]["agents_map"]
    # agents_map: Dict[str, BaseAgent] where key is role name (e.g., "host", "pm")
    agents_map = config["configurable"].get("agents_map", {})
    agent = agents_map.get(role.lower())
    if not agent:
        raise ValueError(f"No agent found for role: {role}")
    return agent

class _SimpleParticipant:
    """Simple participant data structure for mention parsing.

    This is a lightweight alternative to the abstract Participant class,
    used only for mention parsing which doesn't need the full functionality.
    """
    def __init__(self, name: str, role: Role, is_ai: bool):
        self.name = name
        self.role = role
        self.is_ai = is_ai

def node_opening(state: MeetingState, config: RunnableConfig) -> Dict[str, Any]:
    """Execute opening phase."""
    # Get Host Agent
    host_agent = _get_agent_by_role("host", config)
    
    # Update current speaker
    # Create the chain/runnable for this specific task
    # This assumes Agent has a method to generate a response for a specific context
    
    context = {
        "phase": "opening",
        "recent_messages": state["messages"][-5:],
        "task": "opening_remarks",
        "participants": state.get("participants", [])
    }
    
    # Determine input for the chain
    # We might need to refactor Agent to expose a standard invoke method compatible here
    # For now assuming agent.speak_runnable().invoke(context) logic
    
    response = host_agent.generate_response(context) # Hypothetical new method

    # Update speaker counts
    speaker_counts = state.get("speaker_counts", {}).copy()
    speaker_counts[host_agent.name] = speaker_counts.get(host_agent.name, 0) + 1

    return {
        "messages": [AIMessage(content=response, name=host_agent.name)],
        "current_phase": "opening",
        "current_speaker": host_agent.name,
        "speaker_counts": speaker_counts,
        "next_speaker": "pm" # Transition to PM for status check usually
    }

def node_status_check(state: MeetingState, config: RunnableConfig) -> Dict[str, Any]:
    """Execute status check phase."""
    pm_agent = _get_agent_by_role("pm", config)

    # 인간 PM이면 자동 응답 스킵 → select_speaker에서 자연스럽게 발언 기회 부여
    if not pm_agent.is_ai:
        console.print(f"\n📋 [Status Check] {pm_agent.name}(PM)님이 직접 상황을 보고합니다.\n", style="bold cyan")
        return {
            "current_phase": "status_check",
            "phase_start_time": time.time(),
            "time_extended": False,
        }

    # 기존 AI PM 로직 유지
    context = {
        "phase": "status_check",
        "recent_messages": state["messages"][-10:],
        "task": "status_report",
        "participants": state.get("participants", [])
    }

    response = pm_agent.generate_response(context)

    # Update speaker counts
    speaker_counts = state.get("speaker_counts", {}).copy()
    speaker_counts[pm_agent.name] = speaker_counts.get(pm_agent.name, 0) + 1

    return {
        "messages": [AIMessage(content=response, name=pm_agent.name)],
        "current_phase": "status_check",
        "current_speaker": pm_agent.name,
        "speaker_counts": speaker_counts,
        "phase_start_time": time.time(),
        "time_extended": False
    }

def node_select_speaker(state: MeetingState, config: RunnableConfig) -> Dict[str, Any]:
    """Decide who speaks next in the discussion.

    Host selects the next speaker based on context.
    Prioritizes mentioned participants who haven't responded yet.
    """
    # Start timing
    start_time = time.time()
    console.print("\n🔍 [디버그] 다음 발언자 선택 시작...", style="dim")

    # Check for hard time cutoff BEFORE LLM call
    elapsed, remaining, percentage, urgency = get_phase_time_info(state)

    if urgency == TimeUrgency.HARD_CUTOFF:
        # Time exceeded - force FINISH without LLM call
        return {
            "next_speaker": "FINISH",
            "pending_mentions": [],
            "selection_debug": {
                "time_cutoff": True,
                "elapsed": int(elapsed),
                "percentage": int(percentage),
                "phase": state.get("current_phase", "unknown"),
                "reason": "하드 컷오프: 페이즈 시간 초과"
            }
        }

    host_agent = _get_agent_by_role("host", config)

    # Get available candidates (AI + Human)
    participants_data = state["participants"]
    candidates = [p["name"] for p in participants_data]

    # 1. Parse mentions from recent messages
    step1_start = time.time()
    console.print("  ⏱️  1단계: 멘션 파싱 중...", style="dim")
    recent_messages = state["messages"][-10:]

    # Convert participant data to simple participant objects for mention parsing
    participants = []
    for p_data in participants_data:
        # Convert string role to Role enum
        role_str = p_data.get("role", "member")
        role = getattr(Role, role_str.upper(), Role.MEMBER)

        participant = _SimpleParticipant(
            name=p_data["name"],
            role=role,
            is_ai=p_data.get("is_ai", True)
        )
        participants.append(participant)

    # Track pending mentions
    pending_before = set(state.get("pending_mentions", []))
    pending = pending_before.copy()

    # Track who responded to mentions (for debug display)
    responded = set()

    # Process mentions and responses in chronological order
    # (a) First, if speaker is in pending → mark as responded and remove from pending
    # (b) Then, parse mentions from this message and add to pending
    for msg in recent_messages:
        if not (hasattr(msg, 'content') and hasattr(msg, 'name')):
            continue

        speaker = msg.name

        # (a) Check if this speaker was previously mentioned
        if speaker in pending:
            pending.discard(speaker)
            responded.add(speaker)

        # (b) Parse mentions from this message and add to pending
        result = parse_mentions(msg.content, participants)

        # Handle @everyone
        if result.is_everyone:
            # Add all AI participants except the speaker
            for p in participants:
                if p.is_ai and p.name != speaker:
                    pending.add(p.name)
        else:
            # Add individually mentioned participants (except self-mention)
            for p in result.mentioned_participants:
                if p.name != speaker:
                    pending.add(p.name)

    # Track newly mentioned from LAST message only (for debug display)
    newly_mentioned = set()
    if recent_messages:
        last_msg = recent_messages[-1]
        if hasattr(last_msg, 'content') and hasattr(last_msg, 'name'):
            result = parse_mentions(last_msg.content, participants)
            if result.is_everyone:
                for p in participants:
                    if p.is_ai and p.name != last_msg.name:
                        newly_mentioned.add(p.name)
            else:
                for p in result.mentioned_participants:
                    if p.name != last_msg.name:
                        newly_mentioned.add(p.name)

    # pending is already updated, no need for separate "after_response"
    pending_after_response = pending

    step1_end = time.time()
    console.print(f"     ✅ 멘션 파싱 완료 ({step1_end - step1_start:.3f}초)", style="dim")

    # 2. Extract and update agenda items from recent conversation
    current_agenda_items = state.get("agenda_items", [])
    current_phase = state.get("current_phase", "unknown")

    step2_start = time.time()
    console.print("  ⏱️  2단계: 안건 추출 중 (Fast LLM 호출)...", style="dim")
    try:
        agenda_result = extract_agenda_updates(
            llm=_get_fast_llm(),
            messages=state["messages"][-15:],  # Last 15 messages for context
            current_items=current_agenda_items,
            current_phase=current_phase,
        )
        current_agenda_items = agenda_result.items
    except Exception as e:
        # If agenda extraction fails, keep current items
        console.print(f"     ⚠️ 안건 추출 중 오류 발생 (기존 안건 유지): {e}", style="dim")

    step2_end = time.time()
    console.print(f"     ✅ 안건 추출 완료 ({step2_end - step2_start:.3f}초)", style="dim")

    # 3. Build context with mention information and time context
    step3_start = time.time()
    console.print("  ⏱️  3단계: 컨텍스트 구성 중...", style="dim")
    time_context = build_time_context_for_host(state)
    context = {
        "phase": "discussion",
        "recent_messages": recent_messages,
        "participants": state.get("participants", []),
        "pending_mentions": list(pending_after_response),
        "speaker_counts": state.get("speaker_counts", {}),
        "time_context": time_context,
        "agenda_items": current_agenda_items
    }

    step3_end = time.time()
    console.print(f"     ✅ 컨텍스트 구성 완료 ({step3_end - step3_start:.3f}초)", style="dim")

    # 4. Select next speaker with priority for mentioned participants
    step4_start = time.time()
    console.print("  ⏱️  4단계: 다음 발언자 선택 중 (Fast LLM 호출)...", style="dim")
    if pending_after_response:
        # Prioritize mentioned participants
        selected_speaker = host_agent.select_next_speaker(
            context,
            candidates,
            prioritize=list(pending_after_response),
            llm=_get_fast_llm()
        )
    else:
        selected_speaker = host_agent.select_next_speaker(context, candidates, llm=_get_fast_llm())

    # 5. Update pending mentions (remove selected speaker)
    updated_pending = pending_after_response - {selected_speaker}

    # 6. Determine selection reason for debug
    speaker_counts = state.get("speaker_counts", {})
    selected_count = speaker_counts.get(selected_speaker, 0)
    
    # Determine reason with detailed cases
    if selected_speaker == "FINISH":
        reason = "회의 종료 판단"
    elif newly_mentioned and selected_speaker in newly_mentioned:
        reason = f"방금 멘션됨 (@{selected_speaker})"
    elif pending_after_response and selected_speaker in pending_after_response:
        reason = f"이전 멘션에 아직 미응답"
    elif not pending_after_response and not newly_mentioned:
        # No mentions at all - balance speaking
        all_counts = [speaker_counts.get(name, 0) for name in candidates if name != "FINISH"]
        min_count = min(all_counts) if all_counts else 0
        if selected_count == min_count:
            reason = f"발언 기회 균등 배분 (최소 발언자)"
        else:
            reason = f"호스트의 맥락 판단"
    else:
        reason = f"호스트의 판단"

    # 7. Store debug information in state for display in main.py
    debug_info = {
        "newly_mentioned": list(newly_mentioned),
        "responded": list(responded),
        "pending_before": list(pending_before),
        "pending_after": list(updated_pending),
        "selected_speaker": selected_speaker,
        "speaker_counts": speaker_counts,
        "reason": reason,
        "last_speaker": recent_messages[-1].name if recent_messages and hasattr(recent_messages[-1], 'name') else None
    }

    step4_end = time.time()
    console.print(f"     ✅ 다음 발언자 선택 완료 ({step4_end - step4_start:.3f}초)", style="dim")

    # Print total time
    total_time = time.time() - start_time
    console.print(f"✅ [디버그] 발언자 선택 전체 소요 시간: {total_time:.3f}초", style="dim")
    console.print(f"   └─ 선택된 발언자: {selected_speaker}", style="dim")

    return {
        "next_speaker": selected_speaker,
        "pending_mentions": list(updated_pending),
        "selection_debug": debug_info,  # Add debug info to state
        "agenda_items": current_agenda_items  # Update agenda items in state
    }



def node_speak(state: MeetingState, config: RunnableConfig) -> Dict[str, Any]:
    """Execution node for the selected speaker."""
    speaker_name = state.get("next_speaker")
    if not speaker_name:
        return {} # Should not happen

    # Find agent by name
    agents_map = config["configurable"].get("agents_map", {})
    agent = None
    for a in agents_map.values():
        if a.name == speaker_name:
            agent = a
            break

    if not agent:
        # Fallback if name mismatch
        return {"next_speaker": "FINISH"}

    context = {
        "phase": "discussion",
        "recent_messages": state["messages"][-10:],
        "task": "discussion_participation",
        "participants": state.get("participants", [])
    }

    response = agent.generate_response(context)

    # Update speaker counts
    speaker_counts = state.get("speaker_counts", {}).copy()
    speaker_counts[agent.name] = speaker_counts.get(agent.name, 0) + 1

    return {
        "messages": [AIMessage(content=response, name=agent.name)],
        "current_speaker": agent.name,
        "speaker_counts": speaker_counts,
        # After speaking, we loop back to selection, so we don't set next_speaker here
    }

def node_user_input(state: MeetingState, config: RunnableConfig) -> Dict[str, Any]:
    """Process user input (Human-in-the-loop).

    This node is interrupted before execution via interrupt_before.
    After user provides input, the graph resumes with user_input in state.
    """
    user_input = state.get("user_input")
    user_name = state.get("next_speaker")  # The human's name

    if not user_input:
        # This shouldn't happen if properly interrupted and resumed
        return {}

    # Update speaker counts for human participant
    speaker_counts = state.get("speaker_counts", {}).copy()
    speaker_counts[user_name] = speaker_counts.get(user_name, 0) + 1

    # Check for time extension request
    result = {
        "messages": [HumanMessage(content=user_input, name=user_name)],
        "current_speaker": user_name,
        "speaker_counts": speaker_counts,
        "user_input": None  # Clear after processing
    }

    # Detect extension request (only if not already extended)
    if check_extension_request(user_input) and not state.get("time_extended", False):
        updated_limits = apply_time_extension(state)
        current_phase = state.get("current_phase", "unknown")
        old_limit = state.get("phase_time_limits", {}).get(current_phase, 300)
        new_limit = updated_limits.get(current_phase, 300)
        extension_amount = new_limit - old_limit

        print(f"\n⏰ 시간 연장: {current_phase} +{extension_amount}초 (총 {new_limit}초)")

        result["phase_time_limits"] = updated_limits
        result["time_extended"] = True

    return result

def node_issue_resolution(state: MeetingState, config: RunnableConfig) -> Dict[str, Any]:
    """Execute issue resolution phase.

    Tech Lead frames the issue discussion after status check phase.
    Resets speaker_counts and pending_mentions for fresh discussion round.
    """
    try:
        tech_lead = _get_agent_by_role("tech_lead", config)
    except ValueError:
        # No Tech Lead available - just transition phase and reset counters
        return {
            "current_phase": "issue_resolution",
            "pending_mentions": [],
            "speaker_counts": {},
        }

    context = {
        "task": "이슈 논의를 시작합니다. 현재 기술적 이슈를 정리하고 논의 방향을 제시해주세요.",
        "phase": "issue_resolution",
        "participants": state.get("participants", []),
        "recent_messages": state.get("messages", [])[-10:],
    }

    response = tech_lead.generate_response(context)
    speaker_name = tech_lead.name

    return {
        "messages": [AIMessage(content=response, name=speaker_name)],
        "current_phase": "issue_resolution",
        "current_speaker": speaker_name,
        "speaker_counts": {speaker_name: 1},  # Reset + Tech Lead's first turn
        "pending_mentions": [],
        "phase_start_time": time.time(),
        "time_extended": False
    }


def node_closing(state: MeetingState, config: RunnableConfig) -> Dict[str, Any]:
    """Execute closing phase (Summary)."""
    host_agent = _get_agent_by_role("host", config)

    context = {
        "phase": "closing",
        "recent_messages": state["messages"][-20:],
        "task": "meeting_summary_and_closing",
        "participants": state.get("participants", []),
        "agenda_items": state.get("agenda_items", [])
    }

    # In a real impl, we might perform a separate map-reduce summary chain here
    response = host_agent.generate_response(context)

    return {
        "messages": [AIMessage(content=response, name=host_agent.name)],
        "current_phase": "closing",
        "current_speaker": host_agent.name
    }
