"""BoardOrchestrator - 私董会讨论引擎

驱动 BoardTeam 执行多轮群聊式讨论：

1. 主持人开场介绍议题和讨论规则
2. 循环 max_rounds 轮：
   - 所有非主持人专家并行生成发言（基于共享讨论历史 + 角色 prompt）
   - 主持人小结本轮要点
   - 检查用户干预和停止命令
3. 主持人最终总结（决策建议、共识点、分歧点）

终止条件：
- 正常终止：达到最大轮次
- 用户终止：用户发送 /stop
- 异常终止：LLM 不可用或所有专家发言失败
"""

from __future__ import annotations

import asyncio
import logging

from .expert import Expert
from .board import BoardTeam, BoardStatus

logger = logging.getLogger(__name__)


class BoardOrchestrator:
    """Board meeting orchestration engine.

    The moderator (lead expert) facilitates the discussion:
    - Opens with topic introduction
    - Summarizes each round
    - Gives final decision advice

    Member experts give speeches each round based on shared history.
    """

    STOP_COMMANDS = frozenset({"/stop", "停止讨论", "stop", "结束讨论"})

    def __init__(self, team: BoardTeam) -> None:
        self._team = team

    async def execute(self, topic: str) -> dict[str, object]:
        """Execute a board meeting discussion.

        Flow:
        1. Broadcast board_started event
        2. Moderator opens the discussion
        3. Loop max_rounds times:
           - Parallel generate member speeches
           - Moderator summarizes the round
           - Check for user intervention / stop
        4. Moderator gives final conclusion
        5. Broadcast board_concluded event

        Returns:
            Dict with status, summary, decision_advice, total_rounds,
            consensus_points, dissent_points
        """
        moderator = self._team.moderator
        if not moderator or not moderator.is_active:
            active = self._team.active_experts
            if not active:
                return {
                    "status": "failed",
                    "summary": "",
                    "decision_advice": "",
                    "total_rounds": 0,
                    "consensus_points": [],
                    "dissent_points": [],
                    "error": "No active expert available",
                }
            # Promote first active expert to moderator
            self._team._moderator_name = active[0].config.name
            moderator = active[0]
            logger.warning(
                f"Moderator not available, falling back to '{moderator.config.name}'"
            )

        self._team.set_status(BoardStatus.DISCUSSING)

        # 1. Broadcast board_started event
        await self._broadcast_event(
            "board_started",
            {
                "team_id": self._team.team_id,
                "topic": topic,
                "experts": [
                    {
                        "name": e.config.name,
                        "avatar": e.config.avatar,
                        "color": e.config.color,
                        "is_moderator": e.config.name == self._team._moderator_name,
                        "persona": e.config.persona[:100],
                    }
                    for e in self._team.active_experts
                ],
                "max_rounds": self._team.max_rounds,
            },
        )

        try:
            # 2. Moderator opens the discussion
            opening = await self._generate_moderator_opening(moderator, topic)
            if opening:
                await self._team.add_to_history(0, moderator.config.name, opening, "moderator")
                await self._broadcast_event(
                    "expert_speech",
                    {
                        "expert_name": moderator.config.name,
                        "expert_avatar": moderator.config.avatar,
                        "expert_color": moderator.config.color,
                        "content": opening,
                        "round": 0,
                        "role": "moderator",
                    },
                )

            # 3. Discussion rounds
            for round_num in range(1, self._team.max_rounds + 1):
                self._team.increment_round()

                # Check for stop command before starting the round
                interventions = self._team.consume_user_interventions()
                if self._has_stop_command(interventions):
                    logger.info(f"Discussion stopped by user at round {round_num}")
                    break

                # Generate member speeches in parallel
                members = self._team.member_experts
                if members:
                    speech_results = await asyncio.gather(
                        *[self._generate_expert_speech(e, round_num) for e in members],
                        return_exceptions=True,
                    )

                    # Broadcast speeches in order (not parallel broadcast)
                    for expert, result in zip(members, speech_results):
                        if isinstance(result, Exception):
                            logger.warning(
                                f"Expert '{expert.config.name}' speech failed: {result}"
                            )
                            continue

                        await self._team.add_to_history(
                            round_num, expert.config.name, result, "expert"
                        )
                        await self._broadcast_event(
                            "expert_speech",
                            {
                                "expert_name": expert.config.name,
                                "expert_avatar": expert.config.avatar,
                                "expert_color": expert.config.color,
                                "content": result,
                                "round": round_num,
                                "role": "expert",
                            },
                        )

                # Moderator summarizes the round
                summary = await self._generate_moderator_summary(moderator, round_num)
                if summary:
                    await self._team.add_to_history(
                        round_num, moderator.config.name, summary, "moderator"
                    )
                    await self._broadcast_event(
                        "round_summary",
                        {
                            "moderator_name": moderator.config.name,
                            "content": summary,
                            "round": round_num,
                            "continue": round_num < self._team.max_rounds,
                        },
                    )

                # Check history length and compress if needed
                gateway = self._get_llm_gateway(moderator)
                if gateway and len(self._team.history) > 20:
                    await self._team.compress_history(moderator, gateway)

            # 4. Final conclusion
            self._team.set_status(BoardStatus.CONCLUDING)
            conclusion = await self._generate_final_conclusion(moderator, topic)

            self._team.set_status(BoardStatus.COMPLETED)

            # 5. Broadcast board_concluded event
            await self._broadcast_event(
                "board_concluded",
                {
                    "summary": conclusion.get("summary", ""),
                    "decision_advice": conclusion.get("decision_advice", ""),
                    "total_rounds": self._team.current_round,
                    "consensus_points": conclusion.get("consensus_points", []),
                    "dissent_points": conclusion.get("dissent_points", []),
                },
            )

            return {
                "status": "completed",
                "summary": conclusion.get("summary", ""),
                "decision_advice": conclusion.get("decision_advice", ""),
                "total_rounds": self._team.current_round,
                "consensus_points": conclusion.get("consensus_points", []),
                "dissent_points": conclusion.get("dissent_points", []),
            }

        except Exception as e:
            logger.error(f"Board meeting execution failed: {e}")
            self._team.set_status(BoardStatus.DISSOLVED)

            # Try to give a fallback conclusion
            fallback = await self._generate_fallback_conclusion(moderator, topic)

            await self._broadcast_event(
                "board_concluded",
                {
                    "summary": fallback.get("summary", ""),
                    "decision_advice": fallback.get("decision_advice", ""),
                    "total_rounds": self._team.current_round,
                    "consensus_points": [],
                    "dissent_points": [],
                    "error": str(e),
                },
            )

            return {
                "status": "failed",
                "summary": fallback.get("summary", ""),
                "decision_advice": fallback.get("decision_advice", ""),
                "total_rounds": self._team.current_round,
                "consensus_points": [],
                "dissent_points": [],
                "error": str(e),
            }

    async def _generate_moderator_opening(self, moderator: Expert, topic: str) -> str:
        """Generate moderator's opening speech.

        The moderator introduces the topic and sets the stage for discussion.
        """
        gateway = self._get_llm_gateway(moderator)
        if not gateway:
            return f"欢迎来到私董会。今天的讨论主题是：{topic}。请各位专家发表看法。"

        prompt = (
            f"你是私董会主持人 {moderator.config.name}。\n"
            f"你的角色：{moderator.config.persona}\n"
            f"你的表达风格：{moderator.config.speaking_style}\n\n"
            f"讨论主题：{topic}\n\n"
            "请作为主持人开场，介绍议题并邀请各位专家发表看法。"
            "开场应该简洁有力，2-3 段话，点明讨论的核心问题。"
        )

        try:
            response = await gateway.chat(
                messages=[{"role": "user", "content": prompt}],
                model="default",
            )
            return response.content.strip()
        except Exception as e:
            logger.warning(f"Moderator opening generation failed: {e}")
            return f"欢迎来到私董会。今天的讨论主题是：{topic}。请各位专家发表看法。"

    async def _generate_expert_speech(self, expert: Expert, round: int) -> str:
        """Generate an expert's speech for the current round.

        The speech is based on:
        - Expert's persona, thinking_style, speaking_style, decision_framework
        - Full discussion history
        - Current round / max rounds
        """
        gateway = self._get_llm_gateway(expert)
        if not gateway:
            return f"[{expert.config.name} 因 LLM 不可用无法发言]"

        history_text = self._team.get_history_text()

        prompt = (
            f"你是 {expert.config.name}，正在参加私董会讨论。\n\n"
            f"你的角色：{expert.config.persona}\n"
            f"你的思维风格：{expert.config.thinking_style}\n"
            f"你的表达风格：{expert.config.speaking_style}\n"
            f"你的决策框架：{expert.config.decision_framework}\n\n"
            f"讨论主题：{self._team.topic}\n"
            f"当前轮次：第 {round} 轮 / 共 {self._team.max_rounds} 轮\n\n"
        )

        if history_text:
            prompt += f"之前的讨论历史：\n{history_text}\n\n"

        prompt += (
            "请基于你的角色和决策框架，就当前讨论主题发表你的看法。"
            "要求：\n"
            "- 保持角色一致性，用你的思维方式和表达风格发言\n"
            "- 2-4 段话，简洁但有洞察力\n"
            "- 可以引用或反驳之前发言者的观点\n"
            "- 给出明确的立场或建议\n"
        )

        response = await gateway.chat(
            messages=[{"role": "user", "content": prompt}],
            model="default",
        )
        return response.content.strip()

    async def _generate_moderator_summary(self, moderator: Expert, round: int) -> str:
        """Generate moderator's round summary.

        The moderator summarizes the key points of the current round.
        """
        gateway = self._get_llm_gateway(moderator)
        if not gateway:
            return f"[第 {round} 轮小结因 LLM 不可用无法生成]"

        # Get only current round's speeches
        round_history = [
            h for h in self._team.history if h["round"] == round
        ]
        if not round_history:
            return ""

        round_text = "\n\n".join(
            f"[{h['expert_name']}]: {h['content']}" for h in round_history
        )

        prompt = (
            f"你是私董会主持人 {moderator.config.name}。\n"
            f"你的角色：{moderator.config.persona}\n"
            f"你的表达风格：{moderator.config.speaking_style}\n\n"
            f"讨论主题：{self._team.topic}\n"
            f"当前轮次：第 {round} 轮 / 共 {self._team.max_rounds} 轮\n\n"
            f"本轮发言：\n{round_text}\n\n"
            "请作为主持人小结本轮讨论：\n"
            "- 归纳各方核心观点（2-3 句话）\n"
            "- 指出共识点和分歧点\n"
            "- 提示下一轮可以深入的方向\n"
            "- 保持简洁，3-5 句话\n"
        )

        try:
            response = await gateway.chat(
                messages=[{"role": "user", "content": prompt}],
                model="default",
            )
            return response.content.strip()
        except Exception as e:
            logger.warning(f"Moderator summary generation failed: {e}")
            return f"[第 {round} 轮讨论完成，主持人小结生成失败]"

    async def _generate_final_conclusion(self, moderator: Expert, topic: str) -> dict[str, object]:
        """Generate moderator's final conclusion.

        The moderator gives:
        - Overall summary of the discussion
        - Decision advice
        - Consensus points
        - Dissent points
        """
        gateway = self._get_llm_gateway(moderator)
        if not gateway:
            return {
                "summary": "讨论已完成，但 LLM 不可用无法生成总结。",
                "decision_advice": "建议参考讨论历史自行判断。",
                "consensus_points": [],
                "dissent_points": [],
            }

        history_text = self._team.get_history_text()

        prompt = (
            f"你是私董会主持人 {moderator.config.name}。\n"
            f"你的角色：{moderator.config.persona}\n"
            f"你的表达风格：{moderator.config.speaking_style}\n"
            f"你的决策框架：{moderator.config.decision_framework}\n\n"
            f"讨论主题：{topic}\n"
            f"总轮次：{self._team.current_round}\n\n"
            f"完整讨论历史：\n{history_text}\n\n"
            "请作为主持人给出最终总结。输出 JSON 格式：\n"
            "```json\n"
            "{\n"
            '  "summary": "整体讨论总结，3-5句话",\n'
            '  "decision_advice": "基于讨论的决策建议，明确给出你的推荐",\n'
            '  "consensus_points": ["共识点1", "共识点2"],\n'
            '  "dissent_points": ["分歧点1", "分歧点2"]\n'
            "}\n"
            "```\n"
            "只输出 JSON，不要其他文字。"
        )

        try:
            import json
            import re

            response = await gateway.chat(
                messages=[{"role": "user", "content": prompt}],
                model="default",
            )
            content = response.content.strip()

            # Extract JSON from response
            json_match = re.search(r"\{.*\}", content, re.DOTALL)
            if json_match:
                result = json.loads(json_match.group(0))
                return {
                    "summary": result.get("summary", ""),
                    "decision_advice": result.get("decision_advice", ""),
                    "consensus_points": result.get("consensus_points", []),
                    "dissent_points": result.get("dissent_points", []),
                }

            # If JSON parsing fails, return raw content as summary
            return {
                "summary": content,
                "decision_advice": "",
                "consensus_points": [],
                "dissent_points": [],
            }
        except Exception as e:
            logger.warning(f"Final conclusion generation failed: {e}")
            return {
                "summary": f"讨论已完成（{self._team.current_round}轮），总结生成失败。",
                "decision_advice": "建议参考讨论历史自行判断。",
                "consensus_points": [],
                "dissent_points": [],
            }

    async def _generate_fallback_conclusion(self, moderator: Expert, topic: str) -> dict[str, object]:
        """Generate a fallback conclusion when execution fails.

        Uses existing discussion history to provide a basic summary.
        """
        history_text = self._team.get_history_text()
        if not history_text:
            return {
                "summary": "讨论未能正常完成，无历史记录。",
                "decision_advice": "",
            }

        gateway = self._get_llm_gateway(moderator)
        if not gateway:
            # Return truncated history as summary
            return {
                "summary": f"讨论异常终止。已有历史（{len(self._team.history)}条）：\n"
                + history_text[:500],
                "decision_advice": "建议参考讨论历史自行判断。",
            }

        prompt = (
            f"你是私董会主持人 {moderator.config.name}。\n"
            f"讨论主题：{topic}\n"
            f"讨论因异常终止，已完成 {self._team.current_round} 轮。\n\n"
            f"已有讨论历史：\n{history_text}\n\n"
            "请基于已有历史给出总结和决策建议。输出 JSON：\n"
            "```json\n"
            '{"summary": "...", "decision_advice": "..."}\n'
            "```\n"
        )

        try:
            import json
            import re

            response = await gateway.chat(
                messages=[{"role": "user", "content": prompt}],
                model="default",
            )
            content = response.content.strip()
            json_match = re.search(r"\{.*\}", content, re.DOTALL)
            if json_match:
                result = json.loads(json_match.group(0))
                return {
                    "summary": result.get("summary", content),
                    "decision_advice": result.get("decision_advice", ""),
                }
            return {"summary": content, "decision_advice": ""}
        except Exception:
            return {
                "summary": f"讨论异常终止，已完成 {self._team.current_round} 轮。",
                "decision_advice": "",
            }

    def _has_stop_command(self, interventions: list[str]) -> bool:
        """Check if any user intervention contains a stop command."""
        for msg in interventions:
            msg_lower = msg.strip().lower()
            if msg_lower in self.STOP_COMMANDS:
                return True
        return False

    def _get_llm_gateway(self, expert: Expert | None = None) -> object:
        """Get LLM gateway from the given expert or the moderator's agent.

        Falls back to other active experts if the primary target has no gateway.
        """
        target = expert or self._team.moderator
        if target and hasattr(target, "agent") and hasattr(target.agent, "_llm_gateway"):
            gateway = target.agent._llm_gateway
            if gateway is not None:
                return gateway
        # Fallback: try first active expert with a gateway
        for exp in self._team.active_experts:
            if hasattr(exp, "agent") and hasattr(exp.agent, "_llm_gateway"):
                gateway = exp.agent._llm_gateway
                if gateway is not None:
                    return gateway
        return None

    async def _broadcast_event(self, event_type: str, data: dict[str, object]) -> None:
        """Broadcast a board event to the team channel.

        Events are emitted via handoff_transport for WebSocket relay.
        """
        if self._team.handoff_transport:
            try:
                await self._team.handoff_transport.send(
                    self._team.team_channel, {"type": event_type, **data}
                )
            except Exception as e:
                logger.warning(f"Failed to broadcast event '{event_type}': {e}")
