"""Expert Team routing — resolves @team prefix to ExpertTeam configuration.

简化说明（U3）：
- 仅通过 @team 前缀触发团队模式
- 移除基于复杂度的自动建议
- 保留 @team:expert1,expert2 指定专家成员
- 保留 resolve_expert_configs 解析专家配置
"""

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 @team or @team:expert1,expert2 prefix
TEAM_PREFIX_PATTERN = re.compile(r"^@team(?::(\S+))?\s*(.*)", re.DOTALL)

# 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 team


@dataclass
class ExpertTeamRoutingResult:
    """Result of expert team routing resolution.

    In hub-and-spoke mode, routing is triggered exclusively by the @team prefix.
    """

    matched: bool = False
    team_mode: bool = False
    specified_experts: list[str] = field(default_factory=list)
    task_content: str = ""
    auto_compose: bool = False  # Deprecated: always False, kept for backward compatibility
    match_method: str = ""  # "explicit_team" | ""


class ExpertTeamRouter:
    """Routes user input to Expert Team mode via @team prefix.

    Supports:
    - @team prefix → trigger team mode (auto-compose members)
    - @team:analyst,strategist → specify team members by name
    - @team:dev_team → use a team template (expands to member list from bound_skills)
    """

    DEFAULT_TEMPLATE = "dev_team"

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

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

        Only @team prefix triggers team mode. No complexity-based suggestion.

        Args:
            content: User's input message

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

        # Check for @team prefix
        match = TEAM_PREFIX_PATTERN.match(content.strip())
        if match:
            expert_list_str = match.group(1)  # e.g., "analyst,strategist" or None
            task = match.group(2).strip()  # The actual task content

            result.matched = True
            result.team_mode = True
            result.task_content = (
                task if task else content
            )  # Fall back to full content when no task after prefix
            result.match_method = "explicit_team"

            if expert_list_str:
                # Check if user specified a single team template name (e.g., "dev_team")
                # Team templates store their member list in config.bound_skills
                stripped = expert_list_str.strip()
                if "," not in stripped:
                    template = self._registry.get(stripped)
                    if template and template.config.bound_skills:
                        # This is a team template — expand to its members
                        members = [n for n in template.config.bound_skills if _EXPERT_NAME_RE.match(n)]
                        if len(members) != len(template.config.bound_skills):
                            invalid = set(template.config.bound_skills) - set(members)
                            logger.warning(f"Invalid expert names in team template rejected: {invalid}")
                        result.specified_experts = members[:MAX_EXPERTS]
                        result.auto_compose = False
                        result.match_method = "explicit_team"
                        return result

                # 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}")
                result.specified_experts = valid_names[:MAX_EXPERTS]
                result.auto_compose = False

                # Validate that specified templates exist
                for name in result.specified_experts:
                    template = self._registry.get(name)
                    if template is None:
                        logger.warning(
                            f"ExpertTemplate '{name}' not found, will be dynamically generated"
                        )
            else:
                # No specific experts — use default dev_team template
                result.specified_experts = self._load_default_template_members()
                result.auto_compose = False

            return result

        # Not a team mode request
        result.matched = False
        result.team_mode = False
        result.task_content = content
        return result

    def can_handle(self, content: str) -> bool:
        """Check whether any registered expert template can handle the given content.

        Returns True if at least one template's name or description overlaps with
        content tokens, or if any templates exist (auto-compose can always form a team).
        """
        if not self._registry or not self._registry._templates:
            return False
        content_lower = content.lower()
        for template in self._registry._templates.values():
            if template.name.lower() in content_lower:
                return True
            if template.description and any(
                word in content_lower
                for word in template.description.lower().split()
                if len(word) > 2
            ):
                return True
        # Auto-compose can form a team from any available templates
        return bool(self._registry._templates)

    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 with the name as persona.
        The first expert is designated as lead.
        """
        configs = []
        for i, name in enumerate(specified_experts):
            # Validate name to prevent prompt injection
            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 lead
                config.is_lead = i == 0
                configs.append(config)
            else:
                # Dynamic generation — create a basic ExpertConfig
                # Name is validated above, safe to use in persona
                config = ExpertConfig(
                    name=name,
                    agent_type="expert",
                    persona=f"Expert in {name}",
                    thinking_style="analytical",
                    bound_skills=[],
                    is_lead=(i == 0 and not any(c.is_lead for c in configs)),
                    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 dev_team template.

        The dev_team template stores its member list in config.bound_skills
        (reusing the existing field to avoid schema changes).

        Falls back to a hardcoded list if the template is not found.
        """
        template = self._registry.get(self.DEFAULT_TEMPLATE)
        if template:
            members = template.config.bound_skills
            if members:
                return members[:MAX_EXPERTS]

        # Fallback default members
        return [
            "tech_lead",
            "frontend_engineer",
            "backend_engineer",
            "qa_engineer",
            "code_reviewer",
        ]
