"""Projection of hidden Task/team events into frontend process streams."""

from __future__ import annotations

from datetime import datetime, timezone
from typing import Any


class SessionProcessProjector:
    def __init__(self, session_manager: Any, run_memory_store: Any) -> None:
        self.session_manager = session_manager
        self.run_memory_store = run_memory_store

    def project(self, session_id: str) -> dict[str, Any]:
        records = self.session_manager.get_event_records(session_id)
        run_records = {record.run_id: record for record in self.run_memory_store.list_runs()}
        runs: dict[str, dict[str, Any]] = {}
        events: list[dict[str, Any]] = []
        artifacts: list[dict[str, Any]] = []

        def add_event(
            *,
            event_id: str,
            run_id: str,
            kind: str,
            actor_type: str,
            actor_id: str,
            actor_name: str,
            text: str,
            created_at: str,
            status: str | None = None,
            parent_run_id: str | None = None,
            metadata: dict[str, Any] | None = None,
        ) -> None:
            events.append(
                {
                    "event_id": event_id,
                    "run_id": run_id,
                    "parent_run_id": parent_run_id,
                    "kind": kind,
                    "actor_type": actor_type,
                    "actor_id": actor_id,
                    "actor_name": actor_name,
                    "text": text,
                    "status": status,
                    "metadata": dict(metadata or {}),
                    "created_at": created_at,
                }
            )

        for record in records:
            payload = dict(record.event_payload or {})
            run_record_for_event = run_records.get(str(record.run_id)) if record.run_id else None
            task_id = payload.get("task_id") or getattr(run_record_for_event, "task_id", None)
            if not task_id:
                continue
            attempt_index = int(payload.get("attempt_index") or getattr(run_record_for_event, "attempt_index", None) or 1)
            root_run_id = f"task:{task_id}:attempt:{attempt_index}"
            created_at = _timestamp(record.timestamp)
            root = runs.setdefault(
                root_run_id,
                {
                    "run_id": root_run_id,
                    "parent_run_id": None,
                    "session_id": session_id,
                    "actor_type": "system",
                    "actor_id": "task",
                    "actor_name": "Task Planner",
                    "title": f"Task {task_id[:8]} attempt {attempt_index}",
                    "source": "task_mode",
                    "status": "running",
                    "started_at": created_at,
                    "metadata": {"task_id": task_id, "attempt_index": attempt_index},
                },
            )

            if record.event_type == "assistant_message_added" and record.tool_calls:
                run_id = record.run_id or root_run_id
                parent_run_id = root_run_id if run_id != root_run_id else None
                for index, tool_call in enumerate(record.tool_calls):
                    if not isinstance(tool_call, dict):
                        continue
                    tool_name = _tool_call_name(tool_call)
                    add_event(
                        event_id=f"{_event_id(record, 'tool-call')}:{index}",
                        run_id=run_id,
                        parent_run_id=parent_run_id,
                        kind="tool_call_started",
                        actor_type="tool",
                        actor_id=tool_name,
                        actor_name=tool_name,
                        text=f"Calling tool: {tool_name}.",
                        created_at=created_at,
                        status="running",
                        metadata={
                            "task_id": task_id,
                            "attempt_index": attempt_index,
                            "timeline_type": "tool_call",
                            "tool_name": tool_name,
                            "tool_call_id": tool_call.get("id"),
                            "arguments": _tool_call_arguments(tool_call),
                        },
                    )

            elif record.event_type == "tool_result_recorded":
                run_id = record.run_id or root_run_id
                parent_run_id = root_run_id if run_id != root_run_id else None
                tool_name = str(record.tool_name or payload.get("tool_name") or "tool")
                add_event(
                    event_id=_event_id(record, "tool-result"),
                    run_id=run_id,
                    parent_run_id=parent_run_id,
                    kind="tool_call_finished",
                    actor_type="tool",
                    actor_id=tool_name,
                    actor_name=tool_name,
                    text=_truncate(str(record.content or payload.get("error") or "")),
                    created_at=created_at,
                    status="done" if payload.get("success", True) else "error",
                    metadata={
                        **dict(payload),
                        "task_id": task_id,
                        "attempt_index": attempt_index,
                        "timeline_type": "tool_result",
                        "tool_name": tool_name,
                        "tool_call_id": record.tool_call_id,
                        "result_summary": _truncate(str(record.content or payload.get("error") or "")),
                    },
                )

            elif record.event_type == "task_execution_planned":
                plan_mode = payload.get("plan_mode") or "single"
                strategy = payload.get("strategy") or "single"
                node_ids = payload.get("node_ids") or []
                root["title"] = f"{plan_mode} plan: {strategy}"
                root["summary"] = payload.get("reason") or ""
                root["metadata"] = {
                    **root.get("metadata", {}),
                    "plan_mode": plan_mode,
                    "strategy": strategy,
                    "node_ids": node_ids,
                    "skill_queries": payload.get("skill_queries") or [],
                    "selected_skill_names": payload.get("selected_skill_names") or [],
                    "ephemeral_guidance_ids": payload.get("ephemeral_guidance_ids") or [],
                    "skill_resolution_report": payload.get("skill_resolution_report") or [],
                    "fallback_error": payload.get("fallback_error"),
                }
                add_event(
                    event_id=_event_id(record, "planned"),
                    run_id=root_run_id,
                    kind="task_planned",
                    actor_type="system",
                    actor_id="task",
                    actor_name="Task Planner",
                    text=f"Beaver planned {plan_mode} execution via {strategy}. {payload.get('reason') or ''}".strip(),
                    created_at=created_at,
                    status="running",
                    metadata={
                        **root["metadata"],
                        "timeline_type": "plan",
                        "user_summary": f"Beaver will use {plan_mode} execution for this task.",
                    },
                )
                selected_skill_names = [
                    str(item)
                    for item in payload.get("selected_skill_names") or []
                    if str(item).strip()
                ]
                if selected_skill_names:
                    add_event(
                        event_id=_event_id(record, "skills"),
                        run_id=root_run_id,
                        kind="skill_selected",
                        actor_type="system",
                        actor_id="skill-selector",
                        actor_name="Skill Selector",
                        text=f"Selected skill guidance: {', '.join(selected_skill_names)}.",
                        created_at=created_at,
                        status="done",
                        metadata={
                            "task_id": task_id,
                            "attempt_index": attempt_index,
                            "timeline_type": "skill",
                            "skill_names": selected_skill_names,
                            "reason": payload.get("reason") or "Selected from task planning context.",
                        },
                    )

            elif record.event_type in {"task_team_run_completed", "task_team_run_failed"}:
                team_success = bool(payload.get("team_success"))
                root["status"] = "running"
                team_run_ids = payload.get("team_run_ids") or []
                root["metadata"] = {
                    **root.get("metadata", {}),
                    "team_success": team_success,
                    "team_run_ids": team_run_ids,
                    "team_error": payload.get("error"),
                }
                add_event(
                    event_id=_event_id(record, "team"),
                    run_id=root_run_id,
                    kind="agent_team_created",
                    actor_type="system",
                    actor_id="team",
                    actor_name="Task Team",
                    text=payload.get("error") or ("Team completed" if team_success else "Team completed with failed nodes"),
                    created_at=created_at,
                    status="done" if team_success else "error",
                    metadata={**dict(payload), "timeline_type": "agent_team", "team_run_ids": team_run_ids},
                )
                node_results = payload.get("node_results") or []
                for item in node_results:
                    if not isinstance(item, dict):
                        continue
                    node_run_id = item.get("run_id") or f"{root_run_id}:node:{item.get('node_id')}"
                    status = "done" if item.get("success") else "error"
                    if item.get("finish_reason") == "blocked":
                        status = "waiting"
                    run_record = run_records.get(str(node_run_id))
                    runs[str(node_run_id)] = {
                        "run_id": str(node_run_id),
                        "parent_run_id": root_run_id,
                        "session_id": run_record.session_id if run_record is not None else session_id,
                        "actor_type": "agent",
                        "actor_id": str(item.get("node_id") or "sub-agent"),
                        "actor_name": str(item.get("node_id") or "Sub-agent"),
                        "title": str(item.get("node_id") or "Sub-agent"),
                        "source": "task_team",
                        "status": status,
                        "started_at": run_record.started_at if run_record is not None else created_at,
                        "finished_at": run_record.ended_at if run_record is not None else created_at,
                        "summary": _truncate(str(item.get("output_text") or item.get("error") or "")),
                        "metadata": {
                            "task_id": task_id,
                            "attempt_index": attempt_index,
                            "node_id": item.get("node_id"),
                            "skill_query": item.get("skill_query"),
                            "selected_skill_names": item.get("selected_skill_names") or [],
                            "ephemeral_skill_names": item.get("ephemeral_skill_names") or [],
                            "ephemeral_guidance_id": item.get("ephemeral_guidance_id"),
                            "ephemeral_guidance_name": item.get("ephemeral_guidance_name"),
                            "ephemeral_used": bool(item.get("ephemeral_used")),
                            "finish_reason": item.get("finish_reason"),
                            "error": item.get("error"),
                        },
                    }
                    guidance_id = item.get("ephemeral_guidance_id")
                    if guidance_id:
                        guidance_name = str(item.get("ephemeral_guidance_name") or guidance_id)
                        artifacts.append(
                            {
                                "artifact_id": f"{node_run_id}:ephemeral-guidance:{guidance_id}",
                                "run_id": str(node_run_id),
                                "actor_type": "agent",
                                "actor_id": str(item.get("node_id") or "sub-agent"),
                                "actor_name": str(item.get("node_id") or "Sub-agent"),
                                "title": f"Ephemeral guidance: {guidance_name}",
                                "artifact_type": "markdown",
                                "content": (
                                    f"# Ephemeral guidance\n\n"
                                    f"- Guidance: {guidance_name}\n"
                                    f"- Guidance ID: {guidance_id}\n"
                                    f"- Scope: current delegated sub-agent run only"
                                ),
                                "metadata": {
                                    "task_id": task_id,
                                    "attempt_index": attempt_index,
                                    "node_id": item.get("node_id"),
                                    "ephemeral_guidance_id": guidance_id,
                                    "ephemeral_guidance_name": guidance_name,
                                    "ephemeral_skill_names": item.get("ephemeral_skill_names") or [],
                                },
                                "created_at": created_at,
                            }
                        )
                    add_event(
                        event_id=f"{_event_id(record, 'node')}:{item.get('node_id')}",
                        run_id=str(node_run_id),
                        parent_run_id=root_run_id,
                        kind="agent_finished",
                        actor_type="agent",
                        actor_id=str(item.get("node_id") or "sub-agent"),
                        actor_name=str(item.get("node_id") or "Sub-agent"),
                        text=_truncate(str(item.get("output_text") or item.get("error") or "")),
                        created_at=created_at,
                        status=status,
                        metadata={
                            **dict(item),
                            "task_id": task_id,
                            "attempt_index": attempt_index,
                            "timeline_type": "agent_progress",
                        },
                    )

            elif record.event_type == "task_synthesis_completed":
                main_run_id = str(payload.get("main_run_id") or "")
                if main_run_id:
                    run_record = run_records.get(main_run_id)
                    activated_skill_names = _activated_skill_names(run_record)
                    runs[main_run_id] = {
                        "run_id": main_run_id,
                        "parent_run_id": root_run_id,
                        "session_id": run_record.session_id if run_record is not None else session_id,
                        "actor_type": "agent",
                        "actor_id": "main-agent",
                        "actor_name": "Main Agent",
                        "title": "Final synthesis",
                        "source": "task_synthesis",
                        "status": "done" if (run_record is None or run_record.success) else "error",
                        "started_at": run_record.started_at if run_record is not None else created_at,
                        "finished_at": run_record.ended_at if run_record is not None else created_at,
                        "summary": _truncate(run_record.task_text if run_record is not None else ""),
                        "metadata": {
                            "task_id": task_id,
                            "attempt_index": attempt_index,
                            "skill_names": activated_skill_names,
                        },
                    }
                    if activated_skill_names:
                        add_event(
                            event_id=_event_id(record, "synthesis-skills"),
                            run_id=main_run_id,
                            parent_run_id=root_run_id,
                            kind="skill_selected",
                            actor_type="system",
                            actor_id="skill-selector",
                            actor_name="Skill Selector",
                            text=f"Selected skill guidance: {', '.join(activated_skill_names)}.",
                            created_at=created_at,
                            status="done",
                            metadata={
                                "task_id": task_id,
                                "attempt_index": attempt_index,
                                "timeline_type": "skill",
                                "skill_names": activated_skill_names,
                                "activation_reasons": _activated_skill_reasons(run_record),
                            },
                        )
                    add_event(
                        event_id=_event_id(record, "synthesis"),
                        run_id=main_run_id,
                        parent_run_id=root_run_id,
                        kind="run_finished",
                        actor_type="agent",
                        actor_id="main-agent",
                        actor_name="Main Agent",
                        text="Main Agent synthesized the final user-facing answer.",
                        created_at=created_at,
                        status="done",
                        metadata=dict(payload),
                    )

            elif record.event_type == "task_evidence_recorded":
                root["status"] = "waiting"
                root["finished_at"] = None
                add_event(
                    event_id=_event_id(record, "evidence"),
                    run_id=record.run_id or root_run_id,
                    parent_run_id=root_run_id if record.run_id else None,
                    kind="task_result_ready",
                    actor_type="system",
                    actor_id="evidence-recorder",
                    actor_name="Evidence",
                    text="The task result is ready for user acceptance.",
                    created_at=created_at,
                    status="done",
                    metadata={**dict(payload), "timeline_type": "result"},
                )

            elif record.event_type == "task_acceptance_recorded":
                acceptance_type = str(payload.get("acceptance_type") or payload.get("feedback_type") or "")
                if acceptance_type == "accept":
                    root["status"] = "done"
                    root["finished_at"] = created_at
                elif acceptance_type == "abandon":
                    root["status"] = "cancelled"
                    root["finished_at"] = created_at
                else:
                    root["status"] = "waiting"
                    root["finished_at"] = None
                add_event(
                    event_id=_event_id(record, "acceptance"),
                    run_id=record.run_id or root_run_id,
                    parent_run_id=root_run_id if record.run_id else None,
                    kind="task_acceptance_recorded",
                    actor_type="user",
                    actor_id="user-acceptance",
                    actor_name="User Acceptance",
                    text=f"User acceptance recorded: {acceptance_type or 'unknown'}.",
                    created_at=created_at,
                    status="done",
                    metadata={**dict(payload), "timeline_type": "acceptance"},
                )

        return {
            "runs": sorted(runs.values(), key=lambda item: item.get("started_at") or ""),
            "events": sorted(events, key=lambda item: item.get("created_at") or ""),
            "artifacts": sorted(artifacts, key=lambda item: item.get("created_at") or ""),
            "agents": [],
        }


def _timestamp(value: float | None) -> str:
    if value is None:
        return datetime.now(timezone.utc).isoformat()
    return datetime.fromtimestamp(float(value), tz=timezone.utc).isoformat()


def _event_id(record: Any, suffix: str) -> str:
    return f"session-event:{record.message_id or record.timestamp}:{suffix}"


def _truncate(text: str, limit: int = 800) -> str:
    cleaned = text.strip()
    if len(cleaned) <= limit:
        return cleaned
    return cleaned[: limit - 1] + "..."


def _activated_skill_names(run_record: Any | None) -> list[str]:
    if run_record is None:
        return []
    names = []
    for receipt in getattr(run_record, "activated_skills", []) or []:
        skill_name = str(getattr(receipt, "skill_name", "") or "").strip()
        if skill_name:
            names.append(skill_name)
    return list(dict.fromkeys(names))


def _activated_skill_reasons(run_record: Any | None) -> list[str]:
    if run_record is None:
        return []
    reasons = []
    for receipt in getattr(run_record, "activated_skills", []) or []:
        reason = str(getattr(receipt, "activation_reason", "") or "").strip()
        if reason:
            reasons.append(reason)
    return reasons


def _tool_call_name(tool_call: dict[str, Any]) -> str:
    function_payload = tool_call.get("function")
    if isinstance(function_payload, dict):
        name = function_payload.get("name")
        if name:
            return str(name)
    for key in ("name", "tool_name"):
        value = tool_call.get(key)
        if value:
            return str(value)
    return "tool"


def _tool_call_arguments(tool_call: dict[str, Any]) -> Any:
    function_payload = tool_call.get("function")
    if isinstance(function_payload, dict) and "arguments" in function_payload:
        return function_payload.get("arguments")
    if "arguments" in tool_call:
        return tool_call.get("arguments")
    if "args" in tool_call:
        return tool_call.get("args")
    return None
