"""BoardRouter - 私董会讨论模式路由

解析 @board 前缀，支持指定专家或使用默认私董会模板。

支持格式：
- @board:expert1,expert2 讨论主题 — 指定专家
- @board 讨论主题 — 使用默认 private_board 模板
- @board:private_board 讨论主题 — 显式使用默认模板
"""

from __future__ import annotations

import copy
import logging
import re
from dataclasses import dataclass, field

from .config import ExpertConfig
from .registry import ExpertTemplateRegistry

logger = logging.getLogger(__name__)


# Pattern to match @board or @board:expert1,expert2 prefix
BOARD_PREFIX_PATTERN = re.compile(r"^@board(?::(\S+))?\s*(.*)", re.DOTALL)

# Pattern to extract optional rounds=N option from the topic
BOARD_ROUNDS_PATTERN = re.compile(r"^rounds=(\d+)\s*")

# Valid expert name: alphanumeric, underscore, hyphen, 1-64 chars
_EXPERT_NAME_RE = re.compile(r"^[a-zA-Z0-9_-]{1,64}$")

MAX_EXPERTS = 10  # Maximum number of experts in a board
DEFAULT_TEMPLATE = "private_board"
DEFAULT_MAX_ROUNDS = 5
MAX_ROUNDS_LIMIT = 50


@dataclass
class BoardRoutingResult:
    """Result of board routing resolution.

    Attributes:
        matched: Whether the input matched @board prefix
        board_mode: Whether board mode is activated
        specified_experts: List of expert names specified by user
        topic: Discussion topic extracted from input
        use_default_template: Whether to use default private_board template
        match_method: How the match was made ("explicit_board" | "default_template" | "")
        max_rounds: Optional user-specified max rounds; None means use server default
    """

    matched: bool = False
    board_mode: bool = False
    specified_experts: list[str] = field(default_factory=list)
    topic: str = ""
    use_default_template: bool = False
    match_method: str = ""
    max_rounds: int | None = None


class BoardRouter:
    """Routes user input to Board Meeting mode via @board prefix.

    Supports:
    - @board prefix → trigger board mode with default private_board template
    - @board:expert1,expert2 → specify board members by name
    - @board:private_board → explicitly use default template
    """

    def __init__(self, template_registry: ExpertTemplateRegistry | None = None):
        self._registry = template_registry or ExpertTemplateRegistry()

    def resolve(self, content: str) -> BoardRoutingResult:
        """Resolve user input to a BoardRoutingResult.

        Args:
            content: User's input message

        Returns:
            BoardRoutingResult with routing decision
        """
        result = BoardRoutingResult()

        match = BOARD_PREFIX_PATTERN.match(content.strip())
        if not match:
            result.matched = False
            result.board_mode = False
            result.topic = content.strip()
            return result

        expert_list_str = match.group(1)  # e.g., "expert1,expert2" or None
        topic = match.group(2).strip()  # The actual topic content

        # Extract optional rounds=N option from the start of the topic
        rounds_match = BOARD_ROUNDS_PATTERN.match(topic)
        if rounds_match:
            requested_rounds = int(rounds_match.group(1))
            result.max_rounds = max(1, min(requested_rounds, MAX_ROUNDS_LIMIT))
            topic = topic[rounds_match.end() :].strip()

        result.matched = True
        result.board_mode = True
        result.topic = topic if topic else ""
        result.match_method = "explicit_board"

        if expert_list_str:
            # Check if user specified the default template name
            if expert_list_str.strip() == DEFAULT_TEMPLATE:
                result.use_default_template = True
                result.specified_experts = self._load_default_template_members()
            else:
                # User specified expert names — validate and limit
                raw_names = [name.strip() for name in expert_list_str.split(",")]
                valid_names = [n for n in raw_names if _EXPERT_NAME_RE.match(n)]
                if len(valid_names) != len(raw_names):
                    invalid = set(raw_names) - set(valid_names)
                    logger.warning(f"Invalid expert names rejected: {invalid}")
                if valid_names:
                    result.specified_experts = valid_names[:MAX_EXPERTS]
                    result.use_default_template = False
                else:
                    # All names invalid — fall back to default template
                    logger.warning("All expert names invalid, falling back to default template")
                    result.use_default_template = True
                    result.specified_experts = self._load_default_template_members()
        else:
            # No specific experts — use default template
            result.use_default_template = True
            result.specified_experts = self._load_default_template_members()

        return result

    def resolve_expert_configs(self, specified_experts: list[str]) -> list[ExpertConfig]:
        """Resolve expert names to ExpertConfig instances.

        For names that match templates, use the template config.
        For names that don't match, create a dynamic ExpertConfig.
        The first expert is designated as moderator (is_lead=True).
        """
        configs: list[ExpertConfig] = []
        for i, name in enumerate(specified_experts):
            if not _EXPERT_NAME_RE.match(name):
                logger.warning(f"Skipping invalid expert name: {name}")
                continue

            template = self._registry.get(name)
            if template:
                # Deep-copy to avoid mutating the shared template config
                config = copy.deepcopy(template.config)
                # Override is_lead: first expert is moderator
                config.is_lead = i == 0
                configs.append(config)
            else:
                # Dynamic generation — create a basic ExpertConfig
                config = ExpertConfig(
                    name=name,
                    agent_type="expert",
                    persona=f"Expert in {name}",
                    thinking_style="analytical",
                    bound_skills=[],
                    is_lead=(i == 0),
                    task_mode="llm_generate",
                    prompt={"identity": f"Expert in {name}"},
                )
                configs.append(config)

        # Ensure at least one expert is lead
        if configs and not any(c.is_lead for c in configs):
            configs[0].is_lead = True

        return configs

    def _load_default_template_members(self) -> list[str]:
        """Load member list from the default private_board template.

        The private_board template is stored as an ExpertTemplate with
        a special 'members' field in its config metadata.

        Falls back to a hardcoded list if the template is not found.
        """
        template = self._registry.get(DEFAULT_TEMPLATE)
        if template:
            # The private_board template stores members in config.bound_skills
            # as a reuse of existing field (avoids schema changes)
            members = template.config.bound_skills
            if members:
                return members[:MAX_EXPERTS]

        # Fallback default members
        return ["elon_musk", "jeff_bezos", "allenzhang", "charlie_munger", "paul_graham"]
