"""Participant pool loader with YAML configuration and interactive selection."""

import os
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Optional

import questionary
import yaml

from thetable_poc.core import Participant, ParticipantFactory, Role

# Selectable roles for user
SELECTABLE_ROLES: list[str] = [
    "member", "backend", "frontend", "qa", "devops", "security", "tech_lead", "pm"
]

# AI roles that can be replaced by human selection
REPLACEABLE_AI_ROLES: set[str] = {"pm", "tech_lead"}


@dataclass
class ParticipantEntry:
    """YAML에서 파싱된 참여자 항목."""

    name: str
    role: str
    type: str  # "ai" or "human"
    checked: bool = False
    mcp_tools: list[str] = field(default_factory=list)
    prompt_template: Optional[str] = None
    metadata: dict[str, Any] = field(default_factory=dict)


def load_participant_pool(config_path: Optional[str] = None) -> list[ParticipantEntry]:
    """Load participant pool from YAML file.

    Args:
        config_path: Path to YAML file. Priority:
            1. Explicit path argument
            2. PARTICIPANTS_CONFIG environment variable
            3. config/participants.yaml (default)
            4. Hardcoded fallback (4 participants)

    Returns:
        List of ParticipantEntry objects

    Raises:
        ValueError: If YAML validation fails
        FileNotFoundError: If specified config file doesn't exist
    """
    # Determine config path
    if config_path is None:
        config_path = os.getenv("PARTICIPANTS_CONFIG")

    if config_path is None:
        # Default path
        default_path = Path(__file__).parent / "participants.yaml"
        if default_path.exists():
            config_path = str(default_path)
        else:
            # Hardcoded fallback
            print("⚠️  participants.yaml not found, using default 4 participants")
            return _get_default_participants()

    # Load YAML
    config_path_obj = Path(config_path)
    if not config_path_obj.exists():
        raise FileNotFoundError(f"Participants config not found: {config_path}")

    with open(config_path_obj, encoding="utf-8") as f:
        data = yaml.safe_load(f)

    if not data or "participants" not in data:
        raise ValueError("Invalid YAML: 'participants' key is required")

    # Parse entries
    entries = []
    seen_names = set()

    for item in data["participants"]:
        # Validate required fields
        if "name" not in item:
            raise ValueError(f"Participant missing 'name' field: {item}")
        if "role" not in item:
            raise ValueError(f"Participant '{item['name']}' missing 'role' field")
        if "type" not in item:
            raise ValueError(f"Participant '{item['name']}' missing 'type' field")

        # Check name uniqueness
        name = item["name"]
        if name in seen_names:
            raise ValueError(f"Duplicate participant name: '{name}'")
        seen_names.add(name)

        # Validate role
        role_str = item["role"]
        try:
            Role(role_str)  # Check if valid role
        except ValueError:
            valid_roles = ", ".join(r.value for r in Role)
            raise ValueError(
                f"Invalid role '{role_str}' for participant '{name}'. "
                f"Valid roles: {valid_roles}"
            )

        # Validate type
        type_str = item["type"]
        if type_str not in ("ai", "human"):
            raise ValueError(
                f"Invalid type '{type_str}' for participant '{name}'. "
                f"Must be 'ai' or 'human'"
            )

        # Create entry
        entry = ParticipantEntry(
            name=name,
            role=role_str,
            type=type_str,
            checked=item.get("checked", False),
            mcp_tools=item.get("mcp_tools", []),
            prompt_template=item.get("prompt_template"),
            metadata=item.get("metadata", {}),
        )
        entries.append(entry)

    # Validate pool requirements
    _validate_pool(entries)

    return entries


def _get_default_participants() -> list[ParticipantEntry]:
    """Get hardcoded default participants (fallback)."""
    return [
        ParticipantEntry(name="Alex", role="host", type="ai", checked=True),
        ParticipantEntry(name="Sarah", role="pm", type="ai", checked=True),
        ParticipantEntry(name="John", role="tech_lead", type="ai", checked=False),
        ParticipantEntry(name="Mr.Yong", role="member", type="human", checked=True),
    ]


def _validate_pool(entries: list[ParticipantEntry]) -> None:
    """Validate participant pool requirements.

    Args:
        entries: List of participant entries

    Raises:
        ValueError: If validation fails
    """
    # Check HOST exists
    host_count = sum(1 for e in entries if e.role == "host")
    if host_count == 0:
        raise ValueError("HOST 역할 참여자가 풀에 없습니다.")
    if host_count > 1:
        raise ValueError("HOST 역할은 1명만 가능합니다.")

    # Check PM exists
    pm_count = sum(1 for e in entries if e.role == "pm")
    if pm_count == 0:
        raise ValueError("PM 역할 참여자가 풀에 없습니다.")


def select_role_interactive() -> str:
    """Interactively select user's role.

    Returns:
        Selected role string

    Raises:
        KeyboardInterrupt: If user cancels selection
    """
    choices = []
    for role in SELECTABLE_ROLES:
        label = role
        if role in REPLACEABLE_AI_ROLES:
            label = f"{role}    (AI 대체)"
        choices.append(questionary.Choice(title=label, value=role))

    selected = questionary.select(
        "🎭 회의에서 맡을 역할을 선택하세요:",
        choices=choices,
        default="member"
    ).ask()

    if selected is None:
        raise KeyboardInterrupt("Role selection cancelled by user")

    print(f"\n✅ 역할 선택: {selected}")
    return selected


def apply_role_to_pool(pool: list[ParticipantEntry], human_role: str) -> list[ParticipantEntry]:
    """Apply human role to pool and remove replaced AI if needed.

    Args:
        pool: List of participant entries
        human_role: Role selected by human user

    Returns:
        New pool with human role applied (non-destructive)
    """
    # Create a copy of the pool to avoid mutating the original
    new_pool = pool.copy()

    # Find the human entry (should be exactly one)
    human_entries = [e for e in new_pool if e.type == "human"]
    if len(human_entries) != 1:
        print(f"⚠️ Warning: Expected exactly 1 human entry, found {len(human_entries)}")
        if not human_entries:
            return new_pool

    human_entry = human_entries[0]

    # Update human's role
    human_entry.role = human_role

    # If this role replaces an AI, remove the AI from pool
    if human_role in REPLACEABLE_AI_ROLES:
        # Find the AI with this role
        ai_to_remove = None
        for entry in new_pool:
            if entry.type == "ai" and entry.role == human_role:
                ai_to_remove = entry
                break

        if ai_to_remove:
            new_pool.remove(ai_to_remove)
            print(f"🔄 {ai_to_remove.name}(AI {human_role})를 대체합니다.")

    return new_pool


def select_participants_interactive(
    pool: list[ParticipantEntry],
) -> list[ParticipantEntry]:
    """Interactively select participants using checkbox UI.

    Args:
        pool: List of available participants

    Returns:
        List of selected participants

    Raises:
        KeyboardInterrupt: If user cancels selection
    """
    # Build choices
    choices = []
    for entry in pool:
        # HOST and PM are always required (both human and AI)
        is_required = entry.role in ("host", "pm")

        choice = questionary.Choice(
            title=f"{entry.name:<12} {entry.role:<12} {entry.type.upper()}",
            value=entry,
            checked=entry.checked or is_required,
            disabled="필수 참여자" if is_required else None,
        )
        choices.append(choice)

    # Show checkbox
    selected = questionary.checkbox(
        "🎯 참여자를 선택하세요 (스페이스: 선택/해제, Enter: 확인):",
        choices=choices,
        validate=lambda x: len(x) >= 2 or "최소 2명 이상 선택해야 합니다.",
    ).ask()

    if selected is None:
        raise KeyboardInterrupt("Selection cancelled by user")

    print(f"\n✅ 참여자 {len(selected)}명으로 회의를 시작합니다.\n")
    return selected


def create_participants(
    entries: list[ParticipantEntry],
) -> tuple[list[Participant], dict[str, Participant]]:
    """Create Participant objects from entries.

    Args:
        entries: List of participant entries

    Returns:
        Tuple of (participants_list, agents_map)
            - participants_list: List of all participants
            - agents_map: Dict mapping role.value to participant (for graph nodes)
    """
    participants = []
    agents_map = {}

    for entry in entries:
        role = Role(entry.role)

        # Create participant
        if entry.type == "ai":
            participant = ParticipantFactory.create_ai(
                name=entry.name,
                role=role,
                mcp_tools=entry.mcp_tools,
                prompt_template=entry.prompt_template,
            )
        else:  # human
            participant = ParticipantFactory.create_human(name=entry.name, role=role)

        participants.append(participant)

        # Add to agents_map (key: role.value for graph node compatibility)
        role_key = role.value
        if role_key not in agents_map:
            # First participant of this role: use role as key
            agents_map[role_key] = participant
        else:
            # Duplicate role: use name as key (fallback)
            agents_map[entry.name] = participant

    return participants, agents_map


def load_participants(
    config_path: Optional[str] = None,
    select_mode: str = "interactive",
    human_role: Optional[str] = None,
) -> tuple[list[Participant], dict[str, Participant]]:
    """Load and select participants (unified function).

    Args:
        config_path: Path to YAML config (None = use default resolution)
        select_mode: Selection mode
            - "interactive": Show checkbox UI
            - "all": Select all participants
            - "no-select": Use YAML checked status only
        human_role: Role for human participant (None = interactive selection)

    Returns:
        Tuple of (participants_list, agents_map)

    Raises:
        ValueError: If validation fails
        KeyboardInterrupt: If user cancels interactive selection
    """
    # Load pool
    pool = load_participant_pool(config_path)

    # 1단계: 역할 선택 (참여자 선택보다 먼저)
    if human_role is None and select_mode == "interactive":
        human_role = select_role_interactive()

    if human_role:
        pool = apply_role_to_pool(pool, human_role)

    # 2단계: 참여자 선택
    if select_mode == "all":
        selected = pool
        print(f"✅ 전원 참여: {len(selected)}명\n")
    elif select_mode == "no-select":
        selected = [e for e in pool if e.checked]
        print(f"✅ YAML 기본값 사용: {len(selected)}명\n")
    else:  # interactive
        selected = select_participants_interactive(pool)

    # Create participants
    participants_list, agents_map = create_participants(selected)

    return participants_list, agents_map
