"""Desktop client for Orchestra Decision Engine — decision-before-plan when online."""

from __future__ import annotations

import json
import logging
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

try:
    from ...shared.orchestra_runtime_version import ORCHESTRA_CONNECTED_RUNTIME_VERSION
except ModuleNotFoundError:
    try:
        _contract = (
            Path(__file__).resolve().parents[3] / "orchestra_desktop_contract.json"
        )
        with _contract.open(encoding="utf-8") as _f:
            _data = json.load(_f)
        ORCHESTRA_CONNECTED_RUNTIME_VERSION = str(
            _data.get("orchestra_connected_runtime_version")
            or _data.get("schema_version")
            or "0.2.2.81"
        )
    except Exception:
        ORCHESTRA_CONNECTED_RUNTIME_VERSION = "0.2.2.81"
except ImportError:
    ORCHESTRA_CONNECTED_RUNTIME_VERSION = "0.2.2.81"

logger = logging.getLogger(__name__)

EXPECTED_ORCHESTRA_SCHEMA_VERSION = ORCHESTRA_CONNECTED_RUNTIME_VERSION


@dataclass
class OrchestraDecisionResult:
    ok: bool = False
    error: str | None = None
    decision: str = ""
    execution_mode: str = ""
    needs_planner: bool = False
    planner_type: str | None = None
    execution_plan: dict[str, Any] | None = None
    answer: str | None = None
    goal_state: dict[str, Any] | None = None
    recovery_hints: dict[str, Any] | None = None
    replan_recommended: bool = False
    autonomous_replan: bool = False
    payload: dict[str, Any] = field(default_factory=dict)


def _paired_client():
    from ...shared.avatar_llm_cloud import load_desktop_cloud_credentials
    from ...core.api_client import PassiveApiClient

    creds = load_desktop_cloud_credentials()
    if creds is None:
        return None, None
    base_url, token = creds
    return PassiveApiClient(base_url=base_url), token


def _load_runtime_context() -> dict[str, Any]:
    try:
        from .runtime_context import runtime_context_store

        return runtime_context_store().load().safe_metadata()
    except Exception:
        return {}


def _load_goal_memory() -> dict[str, Any]:
    try:
        from .goal_memory import goal_memory_store

        return goal_memory_store().load().as_dict()
    except Exception:
        return {}


def _load_capability_snapshot(*, trust_state: str = "") -> dict[str, Any]:
    try:
        from .capability_resolver import build_capability_snapshot
        from ...core.version import AGENT_MVP_VERSION

        return build_capability_snapshot(
            trust_state=trust_state or "trusted",
            grants={},
            app_version=AGENT_MVP_VERSION,
        )
    except Exception:
        return {}


def _persist_goal_state(payload: dict[str, Any]) -> dict[str, Any] | None:
    goal_state = payload.get("goal_state")
    if not isinstance(goal_state, dict):
        world_state = payload.get("world_state")
        if isinstance(world_state, dict) and isinstance(world_state.get("goal_state"), dict):
            goal_state = world_state["goal_state"]
    if not isinstance(goal_state, dict) or not goal_state:
        return None
    try:
        from .goal_memory import goal_memory_store

        goal_memory_store().record_goal_state(goal_state)
    except Exception:
        logger.debug("goal_state_persist_failed", exc_info=True)
    return goal_state


def fetch_orchestra_decision(
    prompt: str,
    *,
    conversation_tail: list[dict[str, Any]] | None = None,
    trust_state: str = "",
    capability_snapshot: dict[str, Any] | None = None,
    policy_hints: dict[str, Any] | None = None,
) -> OrchestraDecisionResult:
    """Call POST /api/orchestra/decision when paired and online."""
    client, token = _paired_client()
    if client is None or token is None:
        return OrchestraDecisionResult(error="device_not_paired")

    try:
        from ..orchestra_status_service import OrchestraStatusState, probe_orchestra_status

        status = probe_orchestra_status()
        if status is not None and status.state == OrchestraStatusState.OFFLINE:
            return OrchestraDecisionResult(error="orchestra_offline")
    except Exception:
        pass

    snap = capability_snapshot or _load_capability_snapshot(trust_state=trust_state)
    if trust_state:
        snap = {**snap, "trust_state": trust_state}

    body = {
        "prompt": str(prompt or ""),
        "conversation_tail": list(conversation_tail or [])[-12:],
        "runtime_context": _load_runtime_context(),
        "goal_memory": _load_goal_memory(),
        "capability_snapshot": snap,
    }
    if policy_hints:
        body["policy_hints"] = dict(policy_hints)

    try:
        payload = client.post_orchestra_decision(auth_token=token, body=body)
    except Exception as exc:
        logger.debug("orchestra_decision_fetch_failed: %s", exc)
        return OrchestraDecisionResult(error=str(exc))

    if not isinstance(payload, dict):
        return OrchestraDecisionResult(error="invalid_decision_response")

    response_schema = str(payload.get("schema_version") or "").strip()
    if response_schema and response_schema != EXPECTED_ORCHESTRA_SCHEMA_VERSION:
        logger.warning(
            "orchestra_decision_schema_mismatch expected=%s actual=%s",
            EXPECTED_ORCHESTRA_SCHEMA_VERSION,
            response_schema,
        )

    contract = payload.get("execution_contract") if isinstance(payload.get("execution_contract"), dict) else {}
    plan = contract.get("execution_plan") if isinstance(contract.get("execution_plan"), dict) else None
    answer = contract.get("answer") or payload.get("answer")
    goal_state = _persist_goal_state(payload)
    recovery_hints = payload.get("recovery_hints") if isinstance(payload.get("recovery_hints"), dict) else None
    if recovery_hints is None and isinstance(contract.get("recovery_hints"), dict):
        recovery_hints = contract.get("recovery_hints")
    replan_recommended = bool(
        payload.get("replan_recommended")
        or contract.get("replan_recommended")
        or (recovery_hints or {}).get("replan_recommended")
    )
    autonomous_replan = bool(
        payload.get("autonomous_replan")
        or contract.get("autonomous_replan")
        or (recovery_hints or {}).get("autonomous_replan")
    )

    return OrchestraDecisionResult(
        ok=True,
        decision=str(payload.get("decision") or ""),
        execution_mode=str(payload.get("execution_mode") or ""),
        needs_planner=bool(payload.get("needs_planner")),
        planner_type=str(payload.get("planner_type") or "") or None,
        execution_plan=plan,
        answer=str(answer) if answer else None,
        goal_state=goal_state,
        recovery_hints=recovery_hints,
        replan_recommended=replan_recommended,
        autonomous_replan=autonomous_replan,
        payload=payload,
    )


def fetch_orchestra_revise_plan(
    *,
    correction_prompt: str,
    step_failure: dict[str, Any],
    trust_state: str = "",
    capability_snapshot: dict[str, Any] | None = None,
) -> OrchestraDecisionResult:
    """Request revise_plan decision with structured step-failure context."""
    policy_hints = {
        "step_failure": dict(step_failure),
        "replan_request": True,
        "autonomous_replan": True,
    }
    prompt = str(correction_prompt or "").strip()
    if not prompt:
        prompt = (
            f"Revise plan: step {step_failure.get('step_id')} failed "
            f"({step_failure.get('status') or 'failed'}) — {step_failure.get('message') or ''}"
        ).strip()
    return fetch_orchestra_decision(
        prompt,
        trust_state=trust_state,
        capability_snapshot=capability_snapshot,
        policy_hints=policy_hints,
    )


def should_use_orchestra_decision_first() -> bool:
    """True when device is paired — Orchestra is brain when online."""
    client, token = _paired_client()
    return client is not None and token is not None
