from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime, timezone
import importlib
import json
from pathlib import Path
import sys
import webbrowser

from ..agent import DesktopAgentMVP
from ...core.api_client import BackendAuthRequiredError
from ...core.version import AGENT_MVP_VERSION
from ..capability_manager import (
    CAPABILITY_DEFINITIONS,
    CRITICAL_CAPABILITIES,
    HIGH_RISK_CAPABILITIES,
    CapabilityStateManager,
)
from ..active.capability_store import CapabilityStore
from ..active.avatar_local_executor import AvatarExecutionOutcome, AvatarLocalExecutor
from ..active.avatar_plan_executor import AvatarPlanExecutor, PlanExecutionOutcome
from ..active.heartbeat_extras_store import HeartbeatExtrasStore
from ..active.action_session_store import ActionSessionStore
from ..active.permission_profile_service import PermissionProfileService
from ..active.command_models import ConfirmationCallback
from ..active.confirmation_bridge import ConfirmationBridge
from .profile_language import (
    default_ui_language_cache_path,
    extract_profile_interface_language,
    load_cached_ui_language,
    save_cached_ui_language,
)
from .i18n import normalize_language


UI_VERSION = AGENT_MVP_VERSION


@dataclass(frozen=True)
class ExecutionPlanFetchResult:
    plan: dict[str, object] | None = None
    error: str | None = None
    planner_mode: str = ""
    planner_request_id: str = ""


def _is_invalid_device_auth_error(last_error: str) -> bool:
    lowered = str(last_error or "").strip().lower()
    return (
        "invalid_device_auth_token" in lowered
        or "invalid_device_auth" in lowered
        or lowered == "invalid_device_auth_token"
    )


def _format_plan_fetch_error(exc: Exception) -> str:
    message = str(exc or "").strip()
    if isinstance(exc, BackendAuthRequiredError):
        if _is_invalid_device_auth_error(message):
            return "invalid_device_auth_token"
        return message or "invalid_device_auth_token"
    if _is_invalid_device_auth_error(message):
        return "invalid_device_auth_token"
    if message == "backend_unreachable":
        return "backend_unreachable"
    if message.startswith("http_"):
        return message
    return message or "plan_fetch_failed"


def normalize_plan_fetch_error_code(error: str | None) -> str:
    """Map plan fetch failures to stable error codes for UI routing."""
    message = str(error or "").strip()
    if not message:
        return ""
    if _is_invalid_device_auth_error(message):
        return "invalid_device_auth_token"
    return message


def _default_desktop_config_path() -> Path:
    return Path.home() / ".config" / "iuve-connect-agent" / "desktop-config.json"


def devices_pairing_actions_visible(*, paired: bool, runtime_state: str) -> bool:
    runtime = str(runtime_state or "").strip().lower()
    return bool(paired) or runtime == "degraded"


def format_invalid_auth_recovery_hint(*, storage_path: str, config_path: str) -> str:
    state_path = str(storage_path or "").strip() or str(
        Path.home() / ".config" / "iuve-connect-agent" / "desktop-state.json"
    )
    cfg_path = str(config_path or "").strip() or str(_default_desktop_config_path())
    return (
        "CLI: iuve-connect-desktop-agent status\n"
        f"Config: {cfg_path}\n"
        f"State reset: remove {state_path} if unpair is unavailable"
    )


class StatusAdapter:
    """Read-only adapter for console UI backed by canonical Track 1 status."""

    def __init__(self, *, config_path: str | Path) -> None:
        self._config_path = Path(config_path).expanduser()

    @property
    def safe_actions(self) -> tuple[str, ...]:
        return (
            "open_iuve_connect",
            "pair_with_token",
            "unpair_device",
            "open_logs",
            "restart_agent",
            "check_update",
            "export_diagnostics_placeholder",
        )

    def capability_contract(self) -> dict[str, bool]:
        return {
            "remote_shell": False,
            "ssh": False,
            "arbitrary_command_execution": False,
            "file_browsing": False,
            "hidden_remote_control": False,
            "ai_autonomous_control": False,
        }

    def read_devices_snapshot(self) -> dict[str, object]:
        status = self.read_dashboard_snapshot()
        agent = self._agent()
        paired_state = agent.pairing.storage.load_state()
        last_error = str(status.get("last_error") or "")
        storage_path = str(status.get("storage_path") or getattr(agent.pairing.storage, "file_path", "") or "")
        config_path = str(self._config_path)
        paired = bool(status.get("paired", False))
        runtime_state = str(status.get("runtime_state") or "Offline")
        auth_error = _is_invalid_device_auth_error(last_error)
        return {
            "paired": paired,
            "device_id": str(getattr(paired_state, "device_id", "") or ""),
            "device_state": str(status.get("device_state") or "Unknown"),
            "trust_state": str(status.get("trust_state") or "Unpaired"),
            "runtime_state": runtime_state,
            "last_heartbeat_at": str(status.get("last_heartbeat_at") or ""),
            "pairing_timestamp": str(status.get("pairing_timestamp") or ""),
            "backend_base_url": str(status.get("backend_base_url") or ""),
            "admission_mode": "capability_authorization_model_v1",
            "allowlisted_command_channel": True,
            "hidden_actions": False,
            "pre_pair_actions": False,
            "pairing_actions_visible": devices_pairing_actions_visible(
                paired=paired,
                runtime_state=runtime_state,
            ),
            "invalid_auth_error": auth_error,
            "needs_repair": auth_error,
            "auth_recovery_hint": (
                format_invalid_auth_recovery_hint(storage_path=storage_path, config_path=config_path)
                if auth_error
                else ""
            ),
            "storage_path": storage_path,
            "config_path": config_path,
            "last_error": last_error,
        }

    def read_diagnostics_snapshot(self) -> dict[str, object]:
        status = self.read_dashboard_snapshot()
        return {
            "service_status": str(status.get("service_status") or "unknown"),
            "runtime_state": str(status.get("runtime_state") or "Offline"),
            "heartbeat_age_sec": int(status.get("heartbeat_age_sec") or -1),
            "failure_count": int(status.get("failure_count") or 0),
            "last_error": str(status.get("last_error") or ""),
            "logs_path": str(status.get("logs_path") or ""),
            "storage_path": str(status.get("storage_path") or ""),
            "status_source_version": str(status.get("status_source_version") or "v1"),
            "signed_bootstrap_required": bool(self._agent().config.signed_bootstrap_required),
        }

    def read_security_snapshot(self) -> dict[str, object]:
        status = self.read_dashboard_snapshot()
        contract = self.capability_contract()
        return {
            "admission_model": "capability_authorization_model_v1",
            "deny_by_default": True,
            "explicit_user_consent_required": True,
            "pairing_required": True,
            "allowlisted_commands_only": True,
            "root_profile_gated": True,
            "approval_ticket_required": True,
            "rollback_instruction_required": True,
            "active_trust_state": str(status.get("trust_state") or "Unpaired"),
            "active_runtime_state": str(status.get("runtime_state") or "Offline"),
            "capability_contract": contract,
            "required_capabilities": [
                "agent.status.read",
                "agent.diagnostics.read",
                "agent.logs.collect",
                "agent.lifecycle.restart",
                "agent.update.check",
                "agent.ui.notify",
            ],
            "trust_levels": [
                "L0: revoked",
                "L1: pending",
                "L2: connected",
                "L3: stable",
                "L4: trusted (root-gated)",
            ],
        }

    def read_active_ui_grant_counts(self) -> tuple[int, int]:
        """Count enabled UI capability rows (matches Security checkboxes)."""
        from iuve_connect_agents.shared.ui_capability_grants import UI_CAPABILITY_KEYS

        caps = self.read_capability_snapshot()
        blocked = self.read_plan_blocked_ui_keys()
        enabled = sum(
            1
            for key in UI_CAPABILITY_KEYS
            if bool(caps.get(key, {}).get("enabled", False)) and key not in blocked
        )
        return enabled, len(UI_CAPABILITY_KEYS)

    def read_updates_snapshot(self) -> dict[str, object]:
        from ..status_resolver import is_official_channel_update_eligible

        status = self.read_dashboard_snapshot()
        paired = bool(status.get("paired", False))
        trust_state = str(status.get("trust_state") or "").strip().lower()
        trusted = paired and trust_state == "trusted"
        app_version = str(status.get("app_version") or AGENT_MVP_VERSION)
        installed_version = self._installed_agent_version()
        current_version = installed_version or app_version
        channel = str(status.get("update_channel") or "stable")
        channel_disabled = channel == "disabled"
        update_eligible = paired and is_official_channel_update_eligible(trust_state) and not channel_disabled
        remote_update_allowed = update_eligible
        if not paired:
            policy_key = "updates.policy.pairing_required"
        elif channel_disabled:
            policy_key = "updates.policy.channel_disabled"
        elif trust_state in {"revoked", "blocked"}:
            policy_key = "updates.policy.revoked_no_update"
        elif trusted:
            policy_key = "updates.policy.trusted_self_update"
        elif update_eligible:
            policy_key = "updates.policy.paired_official_self_update"
        else:
            policy_key = "updates.policy.pairing_not_trusted"
        latest_version = self.peek_latest_stable_release_version()
        return {
            "update_channel": channel,
            "channel": channel,
            "app_version": app_version,
            "current_version": current_version,
            "latest_stable_version": latest_version,
            "pairing_status": paired,
            "pairing_status_label_key": "label.paired" if paired else "label.pairing_required",
            "remote_update_allowed": remote_update_allowed,
            "signed_bootstrap_required": bool(self._agent().config.signed_bootstrap_required),
            "policy": f"i18n.{policy_key}",
            "note": "i18n.updates.note.discover_troubleshooting",
        }

    def peek_latest_stable_release_version(self) -> str:
        """Best-effort stable channel version for troubleshooting hints (no install)."""
        agent = self._agent()
        state = agent.pairing.storage.load_state()
        if state is not None:
            auth_token = str(getattr(state, "device_auth_token", "") or "").strip()
            if auth_token:
                try:
                    release = agent.pairing.api_client.get_desktop_release_latest(
                        platform="linux",
                        auth_token=auth_token,
                    )
                    version = str(release.get("version") or "").strip()
                    if version:
                        return version
                except Exception:
                    pass
        try:
            import json
            from urllib.request import urlopen

            with urlopen(
                "https://www.iuve.eu/releases/desktop-agent/linux/latest.json",
                timeout=8,
            ) as response:
                payload = json.loads(response.read().decode("utf-8"))
            if isinstance(payload, dict):
                return str(payload.get("version") or "").strip()
        except Exception:
            return ""
        return ""

    def read_dashboard_snapshot(self) -> dict[str, object]:
        status = self._agent().status_details()
        installed_version = self._installed_agent_version()
        status["app_version"] = installed_version or UI_VERSION
        status["service_status"] = self._service_status()
        return status

    def open_iuve_connect(self) -> bool:
        return bool(webbrowser.open("https://www.iuve.eu/connect"))

    def pair_with_token(self, pair_token: str) -> dict[str, object]:
        """Pair device using the same backend flow as CLI `pair --pair-token`."""
        cleaned = str(pair_token or "").strip()
        if not cleaned:
            return {"ok": False, "status": "invalid_token", "detail": "Pair token is empty"}

        agent = self._agent()
        try:
            if hasattr(agent.pairing.storage, "set_ephemeral_pair_token"):
                agent.pairing.storage.set_ephemeral_pair_token(cleaned)
            result = agent.pairing.pair_device(
                pair_token=cleaned,
                app_version=agent.config.app_version,
                device_type="desktop",
            )
        except BackendAuthRequiredError as exc:
            return {"ok": False, "status": "backend_rejected", "detail": str(exc)}
        except RuntimeError as exc:
            return self._map_pairing_runtime_error(exc)
        except Exception as exc:
            return {"ok": False, "status": "backend_rejected", "detail": str(exc)}

        if result.ok:
            self.restart_agent()
            self.ensure_device_capability_scan(force=True, reason="trusted_pair")
            return {"ok": True, "status": "paired", "device_id": result.device_id or ""}

        status = str(result.status or "backend_rejected")
        if status in {"invalid_token", "expired_token", "network_error", "backend_rejected"}:
            mapped = status
        elif status in {"backend_auth_gap", "pairing_contract_error"}:
            mapped = "backend_rejected"
        else:
            mapped = "backend_rejected"
        return {"ok": False, "status": mapped, "detail": str(result.detail or "")}

    @staticmethod
    def _map_pairing_runtime_error(exc: RuntimeError) -> dict[str, object]:
        detail = str(exc)
        lowered = detail.lower()
        if "backend_unreachable" in lowered:
            return {"ok": False, "status": "network_error", "detail": detail}
        if "token_already_used" in lowered:
            return {"ok": False, "status": "invalid_token", "detail": detail}
        if "expired" in lowered:
            return {"ok": False, "status": "expired_token", "detail": detail}
        if "invalid" in lowered or "token_revoked" in lowered:
            return {"ok": False, "status": "invalid_token", "detail": detail}
        return {"ok": False, "status": "backend_rejected", "detail": detail}

    def unpair_device(self, *, reason: str = "user_request") -> dict[str, object]:
        """Revoke local pairing credentials (distinct from capability revoke_all)."""
        agent = self._agent()
        result = agent.unpair_device(reason=reason)
        self.restart_agent()
        return result

    def open_logs(self) -> bool:
        logs_path = str(self.read_dashboard_snapshot().get("logs_path") or "").strip()
        if not logs_path:
            logs_path = str(Path.home() / ".config" / "iuve-connect-agent" / "desktop-agent.log")
        subprocess = importlib.import_module("subprocess")
        return subprocess.run(["xdg-open", logs_path], check=False).returncode == 0

    def restart_agent(self) -> bool:
        subprocess = importlib.import_module("subprocess")
        unit_name = "iuve-connect-desktop-agent.service"
        commands = [
            ["systemctl", "--user", "restart", unit_name],
            ["systemctl", "--user", "enable", "--now", unit_name],
            ["systemctl", "--user", "start", unit_name],
        ]
        for command in commands:
            if subprocess.run(command, check=False).returncode == 0:
                self._invalidate_agent_cache()
                return True
        return False

    def check_update(self, *, dry_run: bool = True) -> dict[str, object]:
        try:
            return self._agent().upgrade_in_place(dry_run=dry_run)
        except Exception as exc:
            return {
                "ok": False,
                "status": "failed",
                "reason": "update_check_exception",
                "error": str(exc),
                "dry_run": bool(dry_run),
            }

    # ------------------------------------------------------------------ #
    #  Capability Control Center — Wave 1                                 #
    # ------------------------------------------------------------------ #

    @property
    def _cap_store(self) -> CapabilityStore:
        """Policy/UI single source of truth — same store as DesktopAgentMVP pipeline."""
        return self._agent()._capability_store

    def read_capability_snapshot(self) -> dict[str, dict]:
        """Returns all capability states for UI display.  Read-only, no side-effects."""
        return self._cap_store.read_ui_snapshot()

    def grant_capability(self, key: str, *, expiry_preset: str = "permanent") -> dict:
        """Grant a capability.  Only local_user authority.  Writes audit event."""
        if key not in CAPABILITY_DEFINITIONS:
            return {"ok": False, "error": "unknown_capability", "key": key}
        cap = self._cap_store.grant_ui(key, expiry_preset=expiry_preset)
        if cap is None:
            return {"ok": False, "error": "grant_failed", "key": key}
        return {
            "ok": True,
            "key": key,
            "enabled": cap.enabled,
            "granted_at": cap.granted_at,
            "expires_at": cap.expires_at,
            "risk_level": cap.risk_level,
            "expiry_preset": cap.expiry_preset,
            "canonical_keys": sorted(self._cap_store.canonical_keys_for_ui(key)),
        }

    def revoke_capability(self, key: str) -> dict:
        """Revoke a capability.  Only local_user authority.  Writes audit event."""
        if key not in CAPABILITY_DEFINITIONS:
            return {"ok": False, "error": "unknown_capability", "key": key}
        cap = self._cap_store.revoke_ui(key)
        if cap is None:
            return {"ok": False, "error": "revoke_failed", "key": key}
        return {
            "ok": True,
            "key": key,
            "enabled": False,
            "revoked_at": cap.revoked_at,
        }

    def is_capability_enabled(self, key: str) -> bool:
        return self._cap_store.is_ui_enabled(key)

    def revoke_all_capabilities(self) -> dict[str, object]:
        """Revoke every UI capability and record revoked_all_at."""
        self._cap_store.revoke_all()
        return {"ok": True, "sessions_revoked": self._action_session_store().revoke_all()}

    def apply_capability_selection(
        self,
        selected_keys: list[str],
        *,
        expiry_preset: str = "permanent",
        sync_backend: bool = True,
    ) -> dict[str, object]:
        """Grant selected UI capabilities and revoke unchecked ones (explicit opt-in)."""
        selected = {str(k).strip() for k in selected_keys if str(k).strip() in CAPABILITY_DEFINITIONS}
        granted: list[str] = []
        revoked: list[str] = []
        for key in CAPABILITY_DEFINITIONS:
            if key in selected:
                result = self.grant_capability(key, expiry_preset=expiry_preset)
                if result.get("ok"):
                    granted.append(key)
            elif self.is_capability_enabled(key):
                result = self.revoke_capability(key)
                if result.get("ok"):
                    revoked.append(key)
        sync_result: dict[str, object] = {"skipped": True}
        if sync_backend:
            sync_result = self.sync_owner_grants_to_backend()
        return {"ok": True, "granted": granted, "revoked": revoked, "backend_sync": sync_result}

    def _permission_profile_service(self) -> PermissionProfileService:
        return PermissionProfileService(
            capability_store=self._cap_store,
            apply_grants=lambda keys, expiry: self.apply_capability_selection(
                list(keys),
                expiry_preset=expiry,
                sync_backend=True,
            ),
        )

    def read_permission_profile_summary(self) -> dict[str, object]:
        snap = self.read_dashboard_snapshot()
        sec = self.read_security_snapshot()
        trust = str(snap.get("trust_state") or sec.get("active_trust_state") or "")
        paired = bool(snap.get("paired", False))
        agent = self._agent()
        state = agent.pairing.storage.load_state()
        authenticated = bool(state and str(getattr(state, "device_auth_token", "") or "").strip())
        return self._permission_profile_service().build_summary(
            trust_state=trust,
            paired=paired,
            authenticated=authenticated,
        )

    def apply_permission_profile(
        self,
        profile_key: str,
        *,
        lifetime: str = "app_exit",
    ) -> dict[str, object]:
        snap = self.read_dashboard_snapshot()
        sec = self.read_security_snapshot()
        trust = str(snap.get("trust_state") or sec.get("active_trust_state") or "")
        paired = bool(snap.get("paired", False))
        agent = self._agent()
        state = agent.pairing.storage.load_state()
        authenticated = bool(state and str(getattr(state, "device_auth_token", "") or "").strip())
        blocked = self.read_plan_blocked_ui_keys()
        result = self._permission_profile_service().apply_profile(
            profile_key,
            lifetime=lifetime,
            trust_state=trust,
            paired=paired,
            authenticated=authenticated,
            blocked_ui_keys=blocked,
        )
        if result.get("ok"):
            self.mark_grant_setup_completed()
        return result

    def revoke_app_exit_permission_grants(self) -> int:
        return self._permission_profile_service().revoke_app_exit_grants()

    def sync_owner_grants_to_backend(self) -> dict[str, object]:
        """Push current local UI grant state to server-side owner grants."""
        agent = self._agent()
        state = agent.pairing.storage.load_state()
        if state is None:
            return {"ok": False, "error": "device_not_paired"}
        auth_token = str(getattr(state, "device_auth_token", "") or "").strip()
        if not auth_token:
            return {"ok": False, "error": "missing_device_auth_token"}
        snapshot = self.read_capability_snapshot()
        grants_payload: list[dict[str, object]] = []
        for key in CAPABILITY_DEFINITIONS:
            cap_state = snapshot.get(key, {})
            grants_payload.append(
                {
                    "ui_key": key,
                    "enabled": bool(cap_state.get("enabled", False)),
                    "expires_in_seconds": 604800,
                }
            )
        try:
            result = agent.pairing.api_client.sync_owner_ui_grants(
                auth_token=auth_token,
                grants=grants_payload,
            )
            return {"ok": True, **(result if isinstance(result, dict) else {})}
        except BackendAuthRequiredError:
            return {"ok": False, "error": "invalid_device_auth_token"}
        except Exception as exc:
            return {"ok": False, "error": str(exc)}

    def read_plan_blocked_ui_keys(self) -> set[str]:
        """UI capabilities blocked by current SaaS plan (not user grant)."""
        from iuve_connect_agents.shared.ui_capability_grants import UI_CAPABILITY_KEYS

        snap = self.read_saas_context_snapshot()
        if not bool(snap.get("available")):
            return set()
        limits = snap.get("ui_capability_limits") or []
        blocked: set[str] = set()
        if isinstance(limits, list):
            for row in limits:
                if not isinstance(row, dict):
                    continue
                ui_key = str(row.get("ui_key") or "").strip()
                if ui_key in UI_CAPABILITY_KEYS and not bool(row.get("allowed_by_plan", True)):
                    blocked.add(ui_key)
        return blocked

    def read_ui_capability_availability(self) -> dict[str, dict[str, object]]:
        """Per-UI-key: granted, blocked_by_plan, session_active."""
        from iuve_connect_agents.shared.ui_capability_grants import UI_CAPABILITY_KEYS

        snapshot = self.read_capability_snapshot()
        blocked = self.read_plan_blocked_ui_keys()
        from iuve_connect_agents.desktop.ui.security_panel import ui_capability_session_active

        session_caps: set[str] = set()
        for session in self.list_active_action_sessions():
            for cap in session.get("capabilities") or []:
                session_caps.add(str(cap))

        result: dict[str, dict[str, object]] = {}
        for key in UI_CAPABILITY_KEYS:
            state = snapshot.get(key, {})
            locally_enabled = bool(state.get("enabled", False))
            plan_blocked = key in blocked
            granted = locally_enabled and not plan_blocked
            result[key] = {
                "granted": granted,
                "locally_enabled": locally_enabled,
                "blocked_by_plan": plan_blocked,
                "session_active": ui_capability_session_active(
                    key,
                    granted=granted,
                    session_canonical_caps=session_caps,
                ),
                "selectable": not plan_blocked,
            }
        return result

    def read_ui_capability_enabled_map(self) -> dict[str, bool]:
        """Effective UI grants for chat/policy (excludes plan-blocked keys)."""
        availability = self.read_ui_capability_availability()
        from iuve_connect_agents.shared.ui_capability_grants import UI_CAPABILITY_KEYS

        return {
            key: bool(availability.get(key, {}).get("granted", False))
            for key in UI_CAPABILITY_KEYS
        }

    def needs_initial_grant_setup(self) -> bool:
        """True when paired but user has never granted any capability."""
        snap = self.read_dashboard_snapshot()
        if not bool(snap.get("paired", False)):
            return False
        granted, _total = self.read_active_ui_grant_counts()
        if granted > 0:
            return False
        marker_path = self.ui_language_cache_path().parent / "grant-setup-completed"
        return not marker_path.exists()

    def mark_grant_setup_completed(self) -> None:
        marker_path = self.ui_language_cache_path().parent / "grant-setup-completed"
        marker_path.parent.mkdir(parents=True, exist_ok=True)
        marker_path.write_text("1", encoding="utf-8")

    @property
    def _session_store(self) -> ActionSessionStore:
        return self._cap_store.action_session_store

    def find_active_plan_bound_session(self, plan_id: str) -> dict[str, object] | None:
        session = self._session_store.find_active_bound_to_plan(str(plan_id or "").strip())
        if session is None:
            return None
        return session.to_dict()

    def infer_active_permission_preset(self) -> str:
        from iuve_connect_agents.shared.action_session_policy import infer_permission_preset
        from iuve_connect_agents.shared.ui_capability_grants import UI_CAPABILITY_KEYS

        granted = {key for key in UI_CAPABILITY_KEYS if self.is_capability_enabled(key)}
        return infer_permission_preset(granted)

    def list_active_action_sessions(self) -> list[dict[str, object]]:
        return [session.to_dict() for session in self._session_store.list_active()]

    def create_action_session(
        self,
        *,
        title: str,
        capabilities: list[str],
        scope: dict[str, object],
        ttl_seconds: int,
        confirmation_mode: str = "first_only",
        bound_to_plan_id: str | None = None,
        permission_preset: str | None = None,
        replace_same_preset: bool = False,
    ) -> dict[str, object]:
        if replace_same_preset:
            from iuve_connect_agents.desktop.ui.security_panel import (
                normalize_session_preset_key,
                sessions_to_replace_for_preset,
            )

            preset_key = normalize_session_preset_key({"scope": scope})
            if preset_key:
                for existing in sessions_to_replace_for_preset(
                    self.list_active_action_sessions(),
                    preset_key,
                ):
                    sid = str(existing.get("session_id") or "")
                    if sid:
                        self._session_store.revoke(sid)
        session = self._session_store.create(
            title=title,
            capabilities=[str(c) for c in capabilities],
            scope={k: v for k, v in dict(scope).items()},
            ttl_seconds=int(ttl_seconds),
            confirmation_mode=str(confirmation_mode),
            bound_to_plan_id=bound_to_plan_id,
            permission_preset=permission_preset,
        )
        return {"ok": True, "session": session.to_dict()}

    def create_plan_bound_action_session(
        self,
        *,
        preset_key: str,
        plan_id: str,
        workspace_path: str | None = None,
        permission_preset: str | None = None,
        plan: dict[str, object] | None = None,
    ) -> dict[str, object]:
        from .avatar_chat import default_workspace_path
        from .avatar_plan_session import detect_plan_session_preset, detect_plan_session_presets

        resolved_preset = str(permission_preset or self.infer_active_permission_preset()).strip().lower()
        detected_presets = sorted(detect_plan_session_presets(dict(plan or {})))
        single_preset = str(
            preset_key or detect_plan_session_preset(dict(plan or {})) or ""
        ).strip().lower()
        if single_preset in {"minimal", "standard", "full", "expert"}:
            single_preset = detect_plan_session_preset(dict(plan or {})) or ""
        spec = ActionSessionStore.plan_bound_session_spec(
            single_preset or None,
            workspace_path=workspace_path or default_workspace_path(),
            permission_preset=resolved_preset,
            preset_keys=detected_presets if len(detected_presets) > 1 else None,
        )
        scope = dict(spec.get("scope") or {})
        bound_id = str(plan_id or "").strip()
        result = self.create_action_session(
            title=str(spec.get("title") or preset_key or "Task session"),
            capabilities=[str(c) for c in spec.get("capabilities") or []],
            scope=scope,
            ttl_seconds=int(spec.get("ttl_seconds") or 1800),
            confirmation_mode=str(spec.get("confirmation_mode") or "never_in_scope"),
            bound_to_plan_id=bound_id or None,
            permission_preset=resolved_preset,
        )
        session = dict(result.get("session") or {})
        sid = str(session.get("session_id") or "")
        if sid:
            self._agent()._command_audit_logger.log_session_start(
                session_id=sid,
                preset_key=str(scope.get("preset_key") or preset_key),
                bound_to_plan_id=bound_id,
                title=str(spec.get("title") or preset_key),
                ttl_seconds=int(spec.get("ttl_seconds") or 3600),
            )
        return result

    def revoke_plan_bound_action_session(self, plan_id: str, *, reason: str = "plan_completed") -> dict[str, object]:
        bound_id = str(plan_id or "").strip()
        session = self._session_store.find_active_bound_to_plan(bound_id) if bound_id else None
        if session is None:
            return {"ok": False, "session_id": "", "plan_id": bound_id}
        preset_key = str(session.scope.get("preset_key") or "")
        ok = self._session_store.revoke(session.session_id)
        if ok:
            self._agent()._command_audit_logger.log_session_end(
                session_id=session.session_id,
                preset_key=preset_key,
                bound_to_plan_id=bound_id,
                reason=reason,
            )
        return {"ok": ok, "session_id": session.session_id, "plan_id": bound_id}

    def revoke_action_session(self, session_id: str) -> dict[str, object]:
        ok = self._session_store.revoke(str(session_id))
        return {"ok": ok, "session_id": session_id}

    def revoke_all_action_sessions(self) -> dict[str, object]:
        count = self._session_store.revoke_all()
        return {"ok": True, "revoked_count": count}

    def read_cloud_suggested_actions(self) -> list[dict[str, object]]:
        return HeartbeatExtrasStore().suggested_actions()

    def fetch_execution_plan_v2(
        self,
        request_text: str,
        *,
        is_compound_hint: bool | None = None,
    ) -> ExecutionPlanFetchResult:
        """Request plan-v2 from backend when execution plan flags are enabled."""
        from iuve_connect_agents.shared.feature_flags import avatar_execution_plan_enabled

        from .avatar_intent_router import detect_compound_intent
        from .avatar_local_plan_fallback import build_local_compound_plan, maybe_correct_misrouted_plan, resolve_local_open_type_plan

        compound = detect_compound_intent(request_text)
        should_fetch_plan = (
            bool(is_compound_hint)
            if is_compound_hint is not None
            else compound.is_compound
        )
        local_open_type = resolve_local_open_type_plan(request_text)
        if local_open_type is not None:
            return ExecutionPlanFetchResult(plan=local_open_type)

        local_fallback = build_local_compound_plan(request_text) if compound.is_compound else None

        if not avatar_execution_plan_enabled():
            if local_fallback:
                return ExecutionPlanFetchResult(plan=local_fallback)
            return ExecutionPlanFetchResult(error="avatar_execution_plan_disabled")

        try:
            agent = self._agent()
            state = agent.pairing.storage.load_state()
            if state is None:
                print("avatar_plan_fetch_error: device_not_paired", file=sys.stderr)
                if local_open_type is not None:
                    return ExecutionPlanFetchResult(plan=local_open_type, error="device_not_paired")
                if local_fallback:
                    return ExecutionPlanFetchResult(plan=local_fallback, error="device_not_paired")
                return ExecutionPlanFetchResult(error="device_not_paired")

            auth_token = str(getattr(state, "device_auth_token", "") or "").strip()
            if not auth_token:
                print("avatar_plan_fetch_error: missing_device_auth_token", file=sys.stderr)
                if local_open_type is not None:
                    return ExecutionPlanFetchResult(plan=local_open_type, error="missing_device_auth_token")
                if local_fallback:
                    return ExecutionPlanFetchResult(plan=local_fallback, error="missing_device_auth_token")
                return ExecutionPlanFetchResult(error="missing_device_auth_token")

            if not should_fetch_plan:
                return ExecutionPlanFetchResult()

            preference_context = ""
            try:
                from ..active.learning_cache import read_local_preference_context

                preference_context = read_local_preference_context()
            except ImportError:
                pass

            plan_context: dict[str, object] = {}
            if preference_context:
                plan_context["preference_context"] = preference_context

            try:
                payload = agent.pairing.api_client.request_execution_plan_v2(
                    auth_token=auth_token,
                    request_text=str(request_text),
                    is_compound=True,
                    context=plan_context,
                )
            except (BackendAuthRequiredError, RuntimeError, TimeoutError, OSError) as exc:
                reason = _format_plan_fetch_error(exc)
                print(f"avatar_plan_fetch_error: {reason}", file=sys.stderr)
                if local_open_type is not None:
                    return ExecutionPlanFetchResult(plan=local_open_type, error=reason)
                if local_fallback:
                    return ExecutionPlanFetchResult(plan=local_fallback, error=reason)
                return ExecutionPlanFetchResult(error=reason)
            except Exception as exc:
                reason = _format_plan_fetch_error(exc)
                print(f"avatar_plan_fetch_error: unexpected:{reason}", file=sys.stderr)
                if local_open_type is not None:
                    return ExecutionPlanFetchResult(plan=local_open_type, error=reason)
                if local_fallback:
                    return ExecutionPlanFetchResult(plan=local_fallback, error=reason)
                return ExecutionPlanFetchResult(error=reason)

            plan = payload.get("execution_plan")
            planner_mode = str(payload.get("planner_mode") or "").strip()
            planner_request_id = str(payload.get("planner_request_id") or "").strip()
            if isinstance(plan, dict) and plan.get("steps"):
                from .avatar_local_plan_fallback import maybe_correct_misrouted_plan, maybe_replace_get_status_stub

                plan_dict = dict(plan)
                plan_dict = maybe_replace_get_status_stub(str(request_text), plan_dict) or plan_dict
                plan_dict = maybe_correct_misrouted_plan(str(request_text), plan_dict) or plan_dict
                if planner_mode:
                    try:
                        from ..orchestra_routing_tracker import record_routing_model
                        from .avatar_execution_status import parse_planner_model_label

                        model_label = parse_planner_model_label(planner_mode)
                        if model_label:
                            record_routing_model(model_label)
                    except ImportError:
                        pass
                return ExecutionPlanFetchResult(
                    plan=plan_dict,
                    planner_mode=planner_mode,
                    planner_request_id=planner_request_id,
                )

            print("avatar_plan_fetch_error: empty_execution_plan", file=sys.stderr)
            if local_open_type is not None:
                return ExecutionPlanFetchResult(plan=local_open_type, error="empty_execution_plan")
            if local_fallback:
                return ExecutionPlanFetchResult(plan=local_fallback, error="empty_execution_plan")
            return ExecutionPlanFetchResult(error="empty_execution_plan")
        except Exception as exc:
            reason = _format_plan_fetch_error(exc)
            print(f"avatar_plan_fetch_error: unexpected:{reason}", file=sys.stderr)
            if local_open_type is not None:
                return ExecutionPlanFetchResult(plan=local_open_type, error=reason)
            if local_fallback:
                return ExecutionPlanFetchResult(plan=local_fallback, error=reason)
            return ExecutionPlanFetchResult(error=reason)

    def _action_session_store(self) -> ActionSessionStore:
        return self._session_store

    def read_canonical_capability_counts(self) -> tuple[int, int]:
        return self._cap_store.read_canonical_counts()

    def read_command_audit_recent(self, n: int = 50) -> list[dict[str, object]]:
        return self._agent().read_command_audit_recent(n)

    def read_capability_audit_recent(self, n: int = 50) -> list[dict[str, object]]:
        try:
            return CapabilityStateManager().read_audit_log(max_lines=n)
        except Exception:
            return []

    def format_permissions_audit_text(self, *, lang: str = "ru", max_lines: int = 50) -> str:
        from .i18n import human_command_step_label, translate

        entries: list[tuple[str, str]] = []

        for record in self.read_capability_audit_recent(max_lines):
            at = str(record.get("at") or record.get("timestamp") or "?")[:19].replace("T", " ")
            event = str(record.get("event") or "?")
            key = str(record.get("key") or "")
            expiry = str(record.get("expiry_preset") or "")
            detail = " ".join(part for part in (key, expiry) if part).strip()
            entries.append((at, f"{event}  {detail}".rstrip()))

        if not entries:
            snapshot = self.read_capability_snapshot()
            for key, state in sorted(snapshot.items()):
                if not bool(state.get("enabled", False)):
                    continue
                granted_at = str(state.get("granted_at") or "?")[:19].replace("T", " ")
                expiry = str(state.get("expiry_preset") or state.get("expiry_type") or "")
                label = translate(lang, "cap.audit.grant_active")
                detail = f"{label}: {key}"
                if expiry:
                    detail = f"{detail} ({expiry})"
                entries.append((granted_at, detail))

        for record in self.read_command_audit_recent(max_lines):
            at = str(record.get("timestamp") or "?")[:19].replace("T", " ")
            event = str(record.get("event") or "?")
            command_type = str(record.get("command_type") or "")
            command_label = (
                human_command_step_label(lang, command_type) if command_type else ""
            )
            capability = str(record.get("capability") or "")
            summary = str(record.get("result_summary") or record.get("policy_decision") or "")
            detail = " ".join(
                part for part in (command_label or command_type, event, capability, summary) if part
            ).strip()
            entries.append((at, detail))

        if not entries:
            return translate(lang, "cap.audit.empty")

        entries.sort(key=lambda item: item[0], reverse=True)
        lines = [f"[{at}]  {body}" for at, body in entries[:max_lines]]
        return "\n".join(lines)

    def register_command_confirmation_callback(
        self,
        callback: ConfirmationCallback | None,
    ) -> None:
        self._agent().register_command_confirmation_callback(callback)

    def read_pending_command_confirmation(self) -> dict[str, object] | None:
        items = self.list_pending_command_confirmations()
        return items[0] if items else None

    def list_pending_command_confirmations(self) -> list[dict[str, object]]:
        bridge = self._confirmation_bridge()
        waiting = bridge.list_pending(state="waiting")
        awaiting = bridge.list_pending(state="awaiting_ui")
        return [dict(item) for item in (waiting + awaiting)]

    def has_command_confirmation_response(self, command_id: str) -> bool:
        return self._confirmation_bridge().read_response(str(command_id)) is not None

    def submit_command_confirmation_response(
        self,
        command_id: str,
        *,
        approve: bool,
        always_allow_command_type: str = "",
    ) -> None:
        result = "approve" if approve else "decline"
        self._confirmation_bridge().submit_response(
            str(command_id),
            result,  # type: ignore[arg-type]
            always_allow_command_type=always_allow_command_type,
        )

    def pending_confirmation_envelope(self, pending: dict[str, object]):
        return ConfirmationBridge.envelope_from_request(pending)  # type: ignore[arg-type]

    def pending_confirmation_decision(self, pending: dict[str, object]):
        return ConfirmationBridge.decision_from_request(pending)  # type: ignore[arg-type]

    def _confirmation_bridge(self) -> ConfirmationBridge:
        return ConfirmationBridge()

    def add_always_allow_command_type(self, command_type: str) -> bool:
        return self._cap_store.add_always_allow_type(command_type)

    def prepare_avatar_action(
        self,
        *,
        command_type: str,
        payload: dict[str, object],
        scope: dict[str, object] | None = None,
    ) -> AvatarExecutionOutcome:
        agent = self._agent()
        executor = AvatarLocalExecutor(
            policy_engine=agent._policy_engine,
            capability_store=agent._capability_store,
            command_executor=agent._command_executor,
            audit_logger=agent._command_audit_logger,
            trust_resolver=agent._resolve_trust_state,
            confirmation_bridge=agent.confirmation_bridge(),
        )
        return executor.prepare(
            command_type=str(command_type),
            payload={k: v for k, v in dict(payload).items()},
            scope={k: v for k, v in dict(scope or {}).items()},
        )

    def execute_avatar_action(
        self,
        envelope,
        decision,
        *,
        confirmation: str | None = None,
    ) -> AvatarExecutionOutcome:
        agent = self._agent()
        executor = AvatarLocalExecutor(
            policy_engine=agent._policy_engine,
            capability_store=agent._capability_store,
            command_executor=agent._command_executor,
            audit_logger=agent._command_audit_logger,
            trust_resolver=agent._resolve_trust_state,
            confirmation_bridge=agent.confirmation_bridge(),
        )
        resolved = None
        if confirmation in {"approve", "decline", "defer"}:
            resolved = confirmation  # type: ignore[assignment]
        return executor.execute(envelope, decision, confirmation=resolved)

    @staticmethod
    def _default_replan_callback():
        from ..active.orchestra_decision_client import (
            fetch_orchestra_revise_plan,
            should_use_orchestra_decision_first,
        )
        from iuve_connect_agents.shared.feature_flags import replanning_enabled

        def _callback(step_failure: dict[str, object]) -> dict[str, object] | None:
            if not replanning_enabled() or not should_use_orchestra_decision_first():
                return None
            if not isinstance(step_failure, dict) or not step_failure.get("step_id"):
                return None
            result = fetch_orchestra_revise_plan(
                correction_prompt=str(step_failure.get("goal_summary") or ""),
                step_failure=dict(step_failure),
            )
            if not result.ok:
                return None
            return {
                "replan_recommended": result.replan_recommended,
                "autonomous_replan": result.autonomous_replan,
                "execution_plan": result.execution_plan,
                "recovery_hints": result.recovery_hints,
                "decision": result.decision,
            }

        return _callback

    def execute_avatar_plan(
        self,
        plan: dict[str, object] | None,
        *,
        step_callback=None,
        vision_recovery_callback=None,
        replan_callback=None,
        start_step_index: int = 0,
        cancel_check=None,
    ) -> PlanExecutionOutcome:
        if not isinstance(plan, dict) or not plan:
            return PlanExecutionOutcome(ok=False, status="missing_plan", message="missing_plan")
        agent = self._agent()
        local = AvatarLocalExecutor(
            policy_engine=agent._policy_engine,
            capability_store=agent._capability_store,
            command_executor=agent._command_executor,
            audit_logger=agent._command_audit_logger,
            trust_resolver=agent._resolve_trust_state,
            confirmation_bridge=agent.confirmation_bridge(),
        )
        executor = AvatarPlanExecutor(
            local_executor=local,
            audit_logger=agent._command_audit_logger,
            confirmation_callback=agent.confirmation_bridge().request,
            vision_recovery_callback=vision_recovery_callback,
            replan_callback=replan_callback or self._default_replan_callback(),
        )
        from iuve_connect_agents.desktop.ui.avatar_chat import default_workspace_path

        return executor.execute_plan(
            plan,
            workspace_scope={"workspace_paths": [default_workspace_path()]},
            step_callback=step_callback,
            start_step_index=start_step_index,
            cancel_check=cancel_check,
        )

    def export_diagnostics_placeholder(self) -> Path:
        output_dir = Path.home() / ".config" / "iuve-connect-agent"
        output_dir.mkdir(parents=True, exist_ok=True)
        out_file = output_dir / "diagnostics-placeholder.json"
        payload = {
            "generated_at": datetime.now(tz=timezone.utc).isoformat(),
            "status": self.read_dashboard_snapshot(),
            "note": "Track 5 diagnostics bundle is not implemented yet.",
            "passive_only": True,
        }
        out_file.write_text(json.dumps(payload, ensure_ascii=True, indent=2, sort_keys=True), encoding="utf-8")
        return out_file

    def read_device_capability_profile(self) -> dict[str, object]:
        from ..active.device_capability_scanner import load_profile

        profile = load_profile()
        return dict(profile) if profile else {}

    def ui_language_cache_path(self) -> Path:
        status = self.read_dashboard_snapshot()
        storage_path = str(status.get("storage_path") or "").strip()
        if storage_path:
            return Path(storage_path).expanduser()
        return default_ui_language_cache_path()

    def load_cached_ui_language(self) -> str | None:
        return load_cached_ui_language(self.ui_language_cache_path())

    def save_cached_ui_language(self, lang: str) -> None:
        save_cached_ui_language(lang, self.ui_language_cache_path())

    def fetch_owner_interface_language(self) -> str | None:
        """Read owner UI language from backend profile (/api/devices/agent/me)."""
        profile = self.fetch_owner_profile()
        return extract_profile_interface_language(profile if isinstance(profile, dict) else None)

    def fetch_owner_profile(self, *, force: bool = False) -> dict[str, object] | None:
        """Fetch device owner profile from /api/devices/agent/me with short-lived cache."""
        if (
            not force
            and hasattr(self, "_owner_profile_cache")
            and isinstance(self._owner_profile_cache, dict)
        ):
            return dict(self._owner_profile_cache)

        agent = self._agent()
        state = agent.pairing.storage.load_state()
        if state is None:
            self._owner_profile_cache = None
            return None
        auth_token = str(getattr(state, "device_auth_token", "") or "").strip()
        if not auth_token:
            self._owner_profile_cache = None
            return None
        try:
            profile = agent.pairing.api_client.get_agent_profile(auth_token=auth_token)
        except Exception:
            cached = getattr(self, "_owner_profile_cache", None)
            return dict(cached) if isinstance(cached, dict) else None
        if isinstance(profile, dict):
            self._owner_profile_cache = dict(profile)
            return dict(profile)
        self._owner_profile_cache = None
        return None

    def read_owner_display_snapshot(self) -> dict[str, object]:
        profile = self.fetch_owner_profile() or {}
        owner_email = str(profile.get("owner_email") or "").strip()
        owner_name = str(
            profile.get("owner_display_name") or profile.get("name") or owner_email or ""
        ).strip()
        return {
            "owner_email": owner_email,
            "owner_name": owner_name,
            "device_name": str(profile.get("name") or "").strip(),
            "interface_language": str(profile.get("interface_language") or "").strip(),
        }

    def refresh_all_console_context(self) -> dict[str, object]:
        """Refresh SaaS, owner profile, grants sync, and device scan metadata."""
        from ..active.device_capability_scanner import trusted_pair_scan_eligible

        profile = self.fetch_owner_profile(force=True)
        saas = self.refresh_saas_context()
        self.ensure_startup_device_scan(force=False)
        if trusted_pair_scan_eligible(self._config_path):
            self.ensure_device_capability_scan(force=False, reason="header_refresh")
        return {
            "owner_profile": profile if isinstance(profile, dict) else {},
            "saas": saas,
        }

    def ensure_startup_device_scan(self, *, force: bool = False) -> bool:
        """Run device capability scan on first launch or when profile is missing/stale."""
        from ..active.device_capability_scanner import get_scan_scheduler, is_profile_stale, load_profile

        profile = load_profile()
        should_force = force or profile is None or is_profile_stale(profile)
        if not should_force:
            return False
        reason = "console_first_run" if profile is None else "console_startup_refresh"
        return get_scan_scheduler().schedule_if_needed(force=should_force, reason=reason)

    def fetch_saas_context(self, *, force: bool = False) -> dict[str, object]:
        """Fetch SaaS billing/entitlements from backend; cache until force refresh."""
        if not force and hasattr(self, "_saas_context_cache") and isinstance(self._saas_context_cache, dict):
            return dict(self._saas_context_cache)

        agent = self._agent()
        state = agent.pairing.storage.load_state()
        if state is None:
            payload = {"available": False, "reason": "device_not_paired"}
            self._saas_context_cache = payload
            return dict(payload)

        auth_token = str(getattr(state, "device_auth_token", "") or "").strip()
        if not auth_token:
            payload = {"available": False, "reason": "missing_device_auth_token"}
            self._saas_context_cache = payload
            return dict(payload)

        try:
            context = agent.pairing.api_client.get_saas_context(auth_token=auth_token)
            payload = {"available": True, **(context if isinstance(context, dict) else {})}
        except BackendAuthRequiredError:
            payload = {"available": False, "reason": "invalid_device_auth_token"}
        except Exception as exc:
            payload = {"available": False, "reason": str(exc)}

        self._saas_context_cache = payload
        return dict(payload)

    def read_saas_context_snapshot(self) -> dict[str, object]:
        return self.fetch_saas_context(force=False)

    def refresh_saas_context(self) -> dict[str, object]:
        return self.fetch_saas_context(force=True)

    def resolve_billing_upgrade_language(self, *, lang: str | None = None) -> str:
        explicit = str(lang or "").strip()
        if explicit:
            return normalize_language(explicit)
        profile_lang = self.fetch_owner_interface_language()
        if profile_lang:
            return normalize_language(profile_lang)
        cached = self.load_cached_ui_language()
        if cached:
            return normalize_language(cached)
        return "en"

    def resolve_billing_upgrade_url(self, *, lang: str | None = None) -> str:
        snap = self.read_saas_context_snapshot()
        code = self.resolve_billing_upgrade_language(lang=lang)
        if code == "ru":
            return str(snap.get("upgrade_url_ru") or "https://www.iuve.eu/billing?lang=ru")
        if code == "en":
            return str(snap.get("upgrade_url") or "https://www.iuve.eu/billing?lang=en")
        return f"https://www.iuve.eu/billing?lang={code}"

    @staticmethod
    def open_external_url(url: str) -> bool:
        cleaned = str(url or "").strip()
        if not cleaned.startswith("https://"):
            return False
        if webbrowser.open(cleaned):
            return True
        subprocess = importlib.import_module("subprocess")
        return subprocess.run(["xdg-open", cleaned], check=False).returncode == 0

    def open_billing_upgrade(self, *, lang: str | None = None) -> bool:
        return self.open_external_url(self.resolve_billing_upgrade_url(lang=lang))

    def update_owner_interface_language(self, lang: str) -> dict[str, object]:
        code = normalize_language(lang)
        self.save_cached_ui_language(code)
        agent = self._agent()
        state = agent.pairing.storage.load_state()
        if state is None:
            return {"ok": False, "status": "device_not_paired", "interface_language": code}
        auth_token = str(getattr(state, "device_auth_token", "") or "").strip()
        if not auth_token:
            return {"ok": False, "status": "missing_device_auth_token", "interface_language": code}
        try:
            payload = agent.pairing.api_client.update_interface_language(
                auth_token=auth_token,
                interface_language=code,
            )
            saved = str(payload.get("interface_language") or code)
            self.save_cached_ui_language(saved)
            return {"ok": True, "status": "updated", "interface_language": saved}
        except Exception as exc:
            return {
                "ok": False,
                "status": "backend_error",
                "detail": str(exc),
                "interface_language": code,
            }

    def ensure_device_capability_scan(self, *, force: bool = False, reason: str = "") -> bool:
        from ..active.device_capability_scanner import get_scan_scheduler, trusted_pair_scan_eligible

        if not trusted_pair_scan_eligible(self._config_path):
            return self.ensure_startup_device_scan(force=force)
        return get_scan_scheduler().schedule_if_needed(force=force, reason=reason or "ui_request")

    def refresh_device_scan(self) -> dict[str, object]:
        """Manual device inventory refresh (Diagnostics tab / dashboard).

        Returns a status dict: started | queued | failed.
        """
        from ..active.device_capability_scanner import get_scan_scheduler
        from ..active.device_scan_scheduler import get_device_scan_scheduler

        scheduler = get_scan_scheduler()
        if scheduler.is_scan_running():
            scheduler.schedule_if_needed(force=True, reason="manual_refresh")
            return {"status": "queued"}

        started = bool(self.ensure_device_capability_scan(force=True, reason="manual_refresh"))
        if not started:
            started = bool(get_device_scan_scheduler().trigger_manual_refresh())

        if started:
            return {"status": "started"}
        if scheduler.is_scan_running():
            return {"status": "queued"}
        return {"status": "failed"}

    def is_device_scan_running(self) -> bool:
        from ..active.device_capability_scanner import get_scan_scheduler

        return get_scan_scheduler().is_scan_running()

    def set_device_capability_scan_callback(self, callback) -> None:
        from ..active.device_capability_scanner import get_scan_scheduler

        get_scan_scheduler().on_profile_updated = callback

    def _invalidate_agent_cache(self) -> None:
        if hasattr(self, "_agent_instance"):
            delattr(self, "_agent_instance")

    def _agent(self) -> DesktopAgentMVP:
        cached = getattr(self, "_agent_instance", None)
        if cached is not None:
            return cached
        self._agent_instance = DesktopAgentMVP.from_config_file(
            self._config_path,
            schedule_capability_scan=False,
        )
        return self._agent_instance

    def _device_auth_token(self) -> str:
        try:
            agent = self._agent()
            state = agent.pairing.storage.load_state()
            if state is None:
                return ""
            return str(getattr(state, "device_auth_token", "") or "").strip()
        except Exception:
            return ""

    def read_cloud_probe_credentials(self) -> tuple[str, str] | None:
        """Return backend URL and device token using the same paths as the running agent."""
        auth_token = self._device_auth_token()
        if not auth_token:
            return None
        try:
            agent = self._agent()
            backend_url = str(getattr(agent.config, "backend_base_url", "") or "").strip().rstrip("/")
            if backend_url:
                return backend_url, auth_token
        except Exception:
            pass
        try:
            from iuve_connect_agents.shared.avatar_llm_cloud import load_desktop_cloud_credentials

            return load_desktop_cloud_credentials(config_path=self._config_path)
        except Exception:
            return None

    def fetch_avatar_providers(self) -> tuple[list[dict[str, object]], str | None]:
        snap = self.read_dashboard_snapshot()
        paired = bool(snap.get("paired", False))
        auth_token = self._device_auth_token()
        if not auth_token:
            if paired:
                return [], "ai_providers.error.invalid_auth"
            return [], "ai_providers.error.auth"
        try:
            agent = self._agent()
            payload = agent.pairing.api_client.list_avatar_providers(auth_token=auth_token)
            from .ai_providers_panel import normalize_provider_rows

            providers = normalize_provider_rows(payload)
            if not providers and isinstance(payload, dict) and "providers" not in payload:
                return [], "ai_providers.error.unavailable"
            return providers, None
        except BackendAuthRequiredError:
            if paired:
                return [], "ai_providers.error.invalid_auth"
            return [], "ai_providers.error.auth"
        except Exception as exc:
            message = str(exc or "")
            if message.startswith("http_503"):
                return [], "ai_providers.error.unavailable"
            return [], "ai_providers.error.unavailable"

    def connect_avatar_provider(
        self,
        provider_id: str,
        *,
        complete: bool = False,
    ) -> dict[str, object]:
        auth_token = self._device_auth_token()
        if not auth_token:
            return {"ok": False, "error": "ai_providers.error.auth"}
        try:
            agent = self._agent()
            result = agent.pairing.api_client.connect_avatar_provider(
                auth_token=auth_token,
                provider_id=str(provider_id),
                consent=True,
                complete=bool(complete),
            )
            return {"ok": True, "result": result}
        except Exception as exc:
            return {"ok": False, "error": str(exc)}

    def disconnect_avatar_provider(self, provider_id: str) -> dict[str, object]:
        auth_token = self._device_auth_token()
        if not auth_token:
            return {"ok": False, "error": "ai_providers.error.auth"}
        try:
            agent = self._agent()
            result = agent.pairing.api_client.disconnect_avatar_provider(
                auth_token=auth_token,
                provider_id=str(provider_id),
                consent=True,
            )
            return {"ok": True, "result": result}
        except Exception as exc:
            return {"ok": False, "error": str(exc)}

    def patch_avatar_provider(
        self,
        provider_id: str,
        *,
        enabled: bool | None = None,
    ) -> dict[str, object]:
        auth_token = self._device_auth_token()
        if not auth_token:
            return {"ok": False, "error": "ai_providers.error.auth"}
        try:
            agent = self._agent()
            result = agent.pairing.api_client.patch_avatar_provider(
                auth_token=auth_token,
                provider_id=str(provider_id),
                enabled=enabled,
            )
            return {"ok": True, "result": result}
        except Exception as exc:
            return {"ok": False, "error": str(exc)}

    @staticmethod
    def _service_status() -> str:
        subprocess = importlib.import_module("subprocess")
        proc = subprocess.run(
            ["systemctl", "--user", "is-active", "iuve-connect-desktop-agent.service"],
            check=False,
            capture_output=True,
            text=True,
        )
        value = (proc.stdout or "").strip().lower()
        if value in {"active", "inactive", "failed", "activating", "deactivating"}:
            return value
        return "unknown"

    @staticmethod
    def _installed_agent_version() -> str:
        subprocess = importlib.import_module("subprocess")
        proc = subprocess.run(
            ["dpkg-query", "-W", "-f=${Version}", "iuve-connect-desktop-agent"],
            check=False,
            capture_output=True,
            text=True,
        )
        if proc.returncode != 0:
            return ""
        return (proc.stdout or "").strip()
