"""Headless Avatar prompt + session runtime for ui_bridge (reuses StatusAdapter, no GTK)."""

from __future__ import annotations

import threading
import uuid
from dataclasses import dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import Any


def _utc_now_iso() -> str:
    return datetime.now(tz=timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")


def _format_chat_message(chat_result: Any, *, lang: str) -> str:
    from ..ui.i18n import translate

    key = str(getattr(chat_result, "response_key", "") or "")
    params = getattr(chat_result, "response_params", None) or {}
    text = translate(lang, key) if key else ""
    if params and isinstance(params, dict):
        try:
            text = text.format(**params)
        except Exception:
            pass
    if not text.strip() and isinstance(params, dict):
        answer = str(params.get("answer") or "").strip()
        if answer:
            return answer
    return text.strip() or key or "ok"


@dataclass
class AvatarJob:
    session_id: str
    prompt: str
    status: str = "queued"  # queued|running|done|error|cancelled
    message: str = ""
    events: list[dict[str, object]] = field(default_factory=list)
    plan: dict[str, object] | None = None
    error: str = ""
    started_at: str = field(default_factory=_utc_now_iso)
    finished_at: str | None = None

    @property
    def working(self) -> bool:
        return self.status in {"queued", "running"}


class AvatarRuntime:
    def __init__(self, *, config_path: Path | None) -> None:
        self.config_path = config_path
        self._lock = threading.RLock()
        self._jobs: dict[str, AvatarJob] = {}
        self._prior_user_text: str | None = None
        self._plan_running = False

    def _adapter(self):
        if self.config_path is None or not self.config_path.is_file():
            return None
        from ..ui.status_adapter import StatusAdapter

        return StatusAdapter(config_path=self.config_path)

    def session_snapshot(self) -> dict[str, object]:
        adapter = self._adapter()
        active_sessions: list[dict[str, object]] = []
        if adapter is not None:
            try:
                active_sessions = list(adapter.list_active_action_sessions() or [])
            except Exception:
                active_sessions = []
        with self._lock:
            jobs = list(self._jobs.values())
            plan_running = self._plan_running or any(j.working for j in jobs)
            events: list[dict[str, object]] = []
            for job in sorted(jobs, key=lambda j: j.started_at, reverse=True)[:5]:
                events.extend(job.events[-20:])
                events.append(
                    {
                        "id": job.session_id,
                        "label": job.prompt[:80],
                        "status": job.status,
                        "message": job.message[:200],
                    }
                )
        try:
            from ..ui.avatar_chat_input_bar import count_working_items

            working_count = count_working_items(
                plan_running=plan_running,
                active_session_count=len(active_sessions),
            )
        except Exception:
            working_count = int(plan_running) + len(active_sessions)
        return {
            "ok": True,
            "working_count": int(working_count),
            "events": events[-40:],
            "active_sessions": len(active_sessions),
            "stub": adapter is None,
            "phase": "C.2",
            "computed_at": _utc_now_iso(),
        }

    def submit_prompt(self, payload: dict[str, Any]) -> dict[str, object]:
        prompt = str(payload.get("prompt") or "").strip()
        if not prompt:
            return {"ok": False, "stub": False, "error": "prompt_required", "message": ""}
        lang = str(payload.get("language") or "en").strip() or "en"
        mode = str(payload.get("mode") or "Auto").strip() or "Auto"
        tags = payload.get("tags") if isinstance(payload.get("tags"), list) else []
        auto_mode = mode.lower() in {"auto", "авто", "automat"}

        adapter = self._adapter()
        if adapter is None:
            from .snapshots import build_avatar_prompt_stub

            return build_avatar_prompt_stub(payload)

        try:
            chat_result, plan_fetch_meta = self._build_chat_result(adapter, prompt)
        except Exception as exc:
            return {
                "ok": False,
                "stub": False,
                "error": f"avatar_build_failed:{exc}",
                "message": "",
                "phase": "C.2",
            }

        message = _format_chat_message(chat_result, lang=lang)
        session_id = f"web-{uuid.uuid4().hex[:12]}"
        plan = chat_result.execution_plan if isinstance(chat_result.execution_plan, dict) else None
        response: dict[str, object] = {
            "ok": True,
            "session_id": session_id,
            "stub": False,
            "phase": "C.2",
            "message": message,
            "response_key": chat_result.response_key,
            "advisory_only": bool(chat_result.advisory_only),
            "plan_preview": bool(chat_result.plan_preview),
            "request_type": chat_result.request_type,
            "planner_mode": plan_fetch_meta.get("planner_mode") or "",
            "plan_fetch_error": chat_result.plan_fetch_error or plan_fetch_meta.get("error"),
            "tags": tags,
            "mode": mode,
            "auto_started": False,
        }
        if plan and plan.get("steps"):
            response["plan_steps"] = len(plan.get("steps") or [])

        snap = adapter.read_dashboard_snapshot()
        ui_enabled = adapter.read_ui_capability_enabled_map()
        trust_state = str(snap.get("trust_state") or "")

        from ..ui.avatar_auto_execute import should_auto_execute_action, should_auto_execute_plan

        should_run_plan = bool(
            auto_mode
            and plan
            and (
                chat_result.plan_resume
                or should_auto_execute_plan(plan, trust_state=trust_state, ui_caps=ui_enabled)
            )
        )
        should_run_action = bool(
            auto_mode
            and chat_result.suggested_action is not None
            and not chat_result.advisory_only
            and should_auto_execute_action(
                chat_result.suggested_action,
                trust_state=trust_state,
                ui_caps=ui_enabled,
            )
        )

        job = AvatarJob(session_id=session_id, prompt=prompt, message=message, plan=plan)
        with self._lock:
            self._jobs[session_id] = job
            self._prior_user_text = prompt

        if should_run_plan and plan is not None:
            response["auto_started"] = True
            job.status = "queued"
            job.events.append({"id": f"{session_id}-queued", "label": "plan_queued", "status": "queued"})
            self._start_plan_job(adapter, job, plan, start_step_index=int(chat_result.resume_step_index or 0))
        elif should_run_action and chat_result.suggested_action is not None:
            response["auto_started"] = True
            job.status = "queued"
            self._start_action_job(adapter, job, chat_result.suggested_action)
        else:
            job.status = "done"
            job.finished_at = _utc_now_iso()
            if plan and plan.get("steps") and not should_run_plan:
                response["message"] = message or "Plan ready — auto-exec requires Trusted + grants (or press Execute in C.3)."
                response["execution_deferred"] = True

        return response

    def _build_chat_result(self, adapter: Any, user_text: str) -> tuple[Any, dict[str, object]]:
        from iuve_connect_agents.shared.avatar_llm_intent import resolve_avatar_intent_for_chat

        from ..active.orchestra_decision_client import (
            fetch_orchestra_decision,
            should_use_orchestra_decision_first,
        )
        from ..ui.avatar_chat import (
            attach_orchestra_recovery_to_plan,
            build_avatar_chat_result,
            build_orchestra_policy_hints_for_text,
        )
        from ..ui.status_adapter import ExecutionPlanFetchResult

        snap = adapter.read_dashboard_snapshot()
        ui_enabled = adapter.read_ui_capability_enabled_map()
        enabled = sum(1 for value in ui_enabled.values() if value)
        intent_resolution = resolve_avatar_intent_for_chat(
            user_text,
            trust_state=str(snap.get("trust_state") or ""),
        )
        prior_text = self._prior_user_text
        orchestra_policy_hints = build_orchestra_policy_hints_for_text(
            user_text,
            prior_user_text=prior_text,
        )
        orchestra_decision_payload: dict | None = None
        plan_fetch: ExecutionPlanFetchResult

        if should_use_orchestra_decision_first():
            decision_result = fetch_orchestra_decision(
                user_text,
                trust_state=str(snap.get("trust_state") or ""),
                policy_hints=orchestra_policy_hints or None,
            )
            if decision_result.ok:
                contract = (
                    decision_result.payload.get("execution_contract")
                    if isinstance(decision_result.payload.get("execution_contract"), dict)
                    else {}
                )
                orchestra_decision_payload = {
                    "ok": True,
                    "decision": decision_result.decision,
                    "execution_mode": decision_result.execution_mode,
                    "needs_planner": decision_result.needs_planner,
                    "planner_type": decision_result.planner_type,
                    "answer": decision_result.answer,
                    "execution_contract": contract,
                    "recovery_hints": decision_result.recovery_hints,
                    "replan_recommended": decision_result.replan_recommended,
                }
                if decision_result.execution_plan:
                    enriched_plan = attach_orchestra_recovery_to_plan(
                        dict(decision_result.execution_plan),
                        orchestra_decision=orchestra_decision_payload,
                        recovery_hints=decision_result.recovery_hints,
                    )
                    plan_fetch = ExecutionPlanFetchResult(
                        plan=enriched_plan,
                        planner_mode=str(contract.get("planner_mode") or "orchestra_decision"),
                    )
                elif decision_result.decision == "answer_only":
                    plan_fetch = ExecutionPlanFetchResult()
                else:
                    plan_fetch = adapter.fetch_execution_plan_v2(
                        user_text,
                        is_compound_hint=intent_resolution.compound.is_compound,
                    )
            else:
                plan_fetch = adapter.fetch_execution_plan_v2(
                    user_text,
                    is_compound_hint=intent_resolution.compound.is_compound,
                )
        else:
            plan_fetch = adapter.fetch_execution_plan_v2(
                user_text,
                is_compound_hint=intent_resolution.compound.is_compound,
            )

        chat_result = build_avatar_chat_result(
            user_text,
            trust_state=str(snap.get("trust_state") or ""),
            capabilities_enabled=enabled,
            ui_capability_enabled=ui_enabled,
            plan_blocked_ui_keys=adapter.read_plan_blocked_ui_keys(),
            cloud_suggestions=adapter.read_cloud_suggested_actions(),
            execution_plan=plan_fetch.plan,
            plan_fetch_error=plan_fetch.error,
            planner_mode=plan_fetch.planner_mode,
            prior_user_text=prior_text,
            orchestra_decision=orchestra_decision_payload,
        )
        meta = {
            "planner_mode": plan_fetch.planner_mode,
            "error": plan_fetch.error,
        }
        return chat_result, meta

    def _start_plan_job(
        self,
        adapter: Any,
        job: AvatarJob,
        plan: dict[str, object],
        *,
        start_step_index: int = 0,
    ) -> None:
        def _run() -> None:
            with self._lock:
                self._plan_running = True
                job.status = "running"
            try:
                plan_id = str(plan.get("plan_id") or job.session_id)
                try:
                    from ..ui.avatar_plan_session import detect_plan_session_preset

                    preset = detect_plan_session_preset(plan) or "standard"
                    adapter.create_plan_bound_action_session(
                        preset_key=preset,
                        plan_id=plan_id,
                        plan=plan,
                    )
                except Exception:
                    pass

                def step_callback(event: Any = None, **kwargs: Any) -> None:
                    payload = event if isinstance(event, dict) else dict(kwargs)
                    with self._lock:
                        job.events.append(
                            {
                                "id": f"{job.session_id}-step-{len(job.events)}",
                                "label": str(
                                    payload.get("label")
                                    or payload.get("command_type")
                                    or payload.get("status")
                                    or "step"
                                ),
                                "status": str(payload.get("status") or "running"),
                            }
                        )

                outcome = adapter.execute_avatar_plan(
                    plan,
                    step_callback=step_callback,
                    start_step_index=start_step_index,
                    vision_recovery_callback=None,
                )
                with self._lock:
                    job.status = "done" if getattr(outcome, "ok", False) else "error"
                    job.message = str(getattr(outcome, "message", "") or job.message)
                    job.error = "" if getattr(outcome, "ok", False) else str(getattr(outcome, "status", "error"))
                    job.finished_at = _utc_now_iso()
                    job.events.append(
                        {
                            "id": f"{job.session_id}-done",
                            "label": "plan_finished",
                            "status": job.status,
                        }
                    )
                try:
                    adapter.revoke_plan_bound_action_session(plan_id, reason="plan_completed")
                except Exception:
                    pass
            except Exception as exc:
                with self._lock:
                    job.status = "error"
                    job.error = str(exc)
                    job.finished_at = _utc_now_iso()
            finally:
                with self._lock:
                    self._plan_running = False

        threading.Thread(target=_run, name=f"iuve-avatar-{job.session_id}", daemon=True).start()

    def _start_action_job(self, adapter: Any, job: AvatarJob, action: Any) -> None:
        def _run() -> None:
            with self._lock:
                self._plan_running = True
                job.status = "running"
            try:
                prepared = adapter.prepare_avatar_action(
                    command_type=action.command_type,
                    payload=dict(action.payload or {}),
                    scope=dict(action.scope or {}),
                )
                if not prepared.ok or prepared.envelope is None or prepared.decision is None:
                    with self._lock:
                        job.status = "error"
                        job.error = str(prepared.message or prepared.status or "prepare_failed")
                        job.message = job.error
                        job.finished_at = _utc_now_iso()
                    return
                outcome = adapter.execute_avatar_action(
                    prepared.envelope,
                    prepared.decision,
                    confirmation="approve",
                )
                with self._lock:
                    job.status = "done" if getattr(outcome, "ok", False) else "error"
                    job.message = str(getattr(outcome, "message", "") or job.message)
                    job.error = "" if getattr(outcome, "ok", False) else str(getattr(outcome, "status", "error"))
                    job.finished_at = _utc_now_iso()
            except Exception as exc:
                with self._lock:
                    job.status = "error"
                    job.error = str(exc)
                    job.finished_at = _utc_now_iso()
            finally:
                with self._lock:
                    self._plan_running = False

        threading.Thread(target=_run, name=f"iuve-avatar-act-{job.session_id}", daemon=True).start()


_RUNTIMES: dict[str, AvatarRuntime] = {}
_RUNTIMES_LOCK = threading.Lock()


def get_avatar_runtime(config_path: Path | None) -> AvatarRuntime:
    key = str(config_path.resolve()) if config_path is not None else ""
    with _RUNTIMES_LOCK:
        runtime = _RUNTIMES.get(key)
        if runtime is None:
            runtime = AvatarRuntime(config_path=config_path)
            _RUNTIMES[key] = runtime
        return runtime
