"""SynthesizerMixin — Lead 综合阶段产出 + 单 agent 回退。

# TYPE_CHECKING: 由 TeamOrchestrator 组合，访问 self 共享状态
"""

from __future__ import annotations

import logging
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Awaitable, Callable

from agentkit.core.protocol import TaskMessage, TaskResult

from .expert import Expert
from .plan import PlanPhase, PlanStatus, TeamPlan

if TYPE_CHECKING:
    from .team import ExpertTeam

logger = logging.getLogger(__name__)


class SynthesizerMixin:
    """Mixin: Lead 综合（BEST 策略） + 全失败单 agent 回退。由 TeamOrchestrator 组合。"""

    # Shared state provided by TeamOrchestrator (annotations only)
    _team: ExpertTeam
    _user_context: list[str]

    @staticmethod
    def _concat_results(results: list[dict[str, object] | str]) -> str:
        """Join phase results into a single content string."""
        return "\n\n".join(
            r.get("content", str(r)) if isinstance(r, dict) else str(r) for r in results
        )

    async def _synthesize_results(
        self,
        lead: Expert,
        task: str,
        completed_phases: list[PlanPhase],
        broadcast_callback: Callable[[dict[str, object]], Awaitable[None]] | None = None,
    ) -> dict[str, object]:
        """Lead Expert synthesizes results using BEST strategy.

        The Lead Expert evaluates all completed phase results and produces
        a final synthesized result. Uses LLM when available, otherwise
        concatenates results.

        U3: When ``broadcast_callback`` is provided, the LLM synthesis is
        streamed via ``gateway.chat_stream()`` and each chunk is forwarded to
        the callback (which typically broadcasts ``team_synthesis_chunk``).
        The full dict is still returned for the caller to consume unchanged.
        """
        results = [ph.result or {} for ph in completed_phases]
        if not results:
            return {"content": ""}

        # If only one result, return it directly
        if len(results) == 1:
            content = results[0].get("content", str(results[0]))
            return {
                "content": content,
                "strategy": "best",
                "phases_completed": 1,
            }

        gateway = self._get_llm_gateway(lead)
        if not gateway:
            # Without LLM, concatenate all results
            return {
                "content": self._concat_results(results),
                "strategy": "best",
                "phases_completed": len(results),
            }

        # Build result summaries for LLM evaluation
        # P1 #5: 解析 offloaded 内容 — 从 SharedWorkspace 读取完整内容，而非使用截断摘要
        summaries = []
        for i, ph in enumerate(completed_phases):
            r = ph.result or {}
            # U4: 如果结果被 offloaded，从 workspace 读取完整内容
            if isinstance(r, dict) and r.get("_offloaded"):
                content = await self._read_dependency_output(ph)
            else:
                content = r.get("content", str(r)) if isinstance(r, dict) else str(r)
            summaries.append(
                f"Phase {i + 1}: {ph.name} (by {ph.assigned_expert}, task: {ph.task_description[:100]}):\n"
                f"{content}"
            )

        prompt = (
            f"Original task: {task}\n\n"
            f"Below are {len(results)} phase results from your team members. "
            f"Synthesize them into a single comprehensive final result that "
            f"best addresses the original task.\n\n" + "\n---\n".join(summaries)
        )
        # U4: Append accumulated user context so user guidance influences synthesis
        if self._user_context:
            prompt += "\n\n用户在执行期间补充的指导意见（请在综合时参考）：\n- " + "\n- ".join(
                self._user_context
            )
        prompt += "\n\nProvide the synthesized result directly."

        try:
            if broadcast_callback is not None:
                # U3: 流式综合 — 每次 chunk 调用 callback 广播 team_synthesis_chunk
                chunks: list[str] = []
                async for chunk in gateway.chat_stream(
                    messages=[{"role": "user", "content": prompt}],
                    model=self._get_model(lead),
                ):
                    if chunk.content:
                        chunks.append(chunk.content)
                        await broadcast_callback({"chunk": chunk.content})
                return {
                    "content": "".join(chunks).strip(),
                    "strategy": "best",
                    "phases_completed": len(results),
                }
            response = await gateway.chat(
                messages=[{"role": "user", "content": prompt}],
                model=self._get_model(lead),
            )
            return {
                "content": response.content.strip(),
                "strategy": "best",
                "phases_completed": len(results),
            }
        except Exception as e:
            logger.warning(f"LLM synthesis failed, falling back to concatenation: {e}")
            return {
                "content": self._concat_results(results),
                "strategy": "best",
                "phases_completed": len(results),
            }

    async def _fallback_to_single_agent(
        self,
        task: str,
        plan: TeamPlan,
        phase_results: dict[str, dict[str, object]],
    ) -> dict[str, object]:
        """Fallback to single agent mode when pipeline execution fails.

        Uses the lead expert (or first active expert) to complete the original task.
        """
        plan.status = PlanStatus.FALLBACK
        logger.warning("Falling back to single agent mode")

        expert = self._team.lead_expert
        if not expert or not expert.is_active:
            active = self._team.active_experts
            expert = active[0] if active else None

        fallback_result: dict[str, object] | None = None
        if expert:
            try:
                task_msg = TaskMessage(
                    task_id=f"fallback_{plan.id}",
                    agent_name=expert.config.name,
                    task_type="fallback",
                    priority=0,
                    input_data={
                        "task": task,
                        "phase_results": phase_results,
                        "team_id": self._team.team_id,
                    },
                    callback_url=None,
                    created_at=datetime.now(timezone.utc),
                )
                task_result: TaskResult = await expert.agent.execute(task_msg)
                fallback_result = task_result.output_data or {
                    "content": f"Task completed by {expert.config.name} (fallback mode)"
                }
            except Exception as e:
                logger.error(f"Fallback agent execution failed: {e}")
                fallback_result = {"error": f"Fallback execution failed: {e}"}
        else:
            fallback_result = {"error": "No active expert available for fallback"}

        return {
            "status": "fallback",
            "result": fallback_result,
            "phase_results": phase_results,
            "plan": plan,
        }
