"""Vision verify failure recovery — retry capture+verify (0.2.2 V-13)."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Callable

from iuve_connect_agents.desktop.active.execution_plan import ExecutionPlan, ExecutionPlanStep
from iuve_connect_agents.desktop.active.plan_step_context import PlanStepContext
from iuve_connect_agents.shared.feature_flags import avatar_desktop_vision_enabled

VISION_RECOVERY_MAX_ATTEMPTS = 1

VisionRecoveryCallback = Callable[[dict[str, Any]], str]

_RECOVERY_AI_METADATA: dict[str, tuple[str, str]] = {
    "capture_screenshot": ("agent.screen.capture", "medium"),
    "verify_screen_contains_text": ("agent.screen.verify", "low"),
}

_DOCUMENT_PLAN_MARKERS = (
    "libreoffice",
    "open_document_edit",
    "writer",
    "документ",
    "document",
    ".odt",
    ".docx",
)


def is_document_plan(plan: ExecutionPlan) -> bool:
    """True for LibreOffice / document-edit plans where vision recovery rarely helps."""
    blob_parts = [str(plan.goal_summary or "")]
    for step in plan.steps:
        blob_parts.append(str(step.command_type or ""))
        payload = dict(step.payload or {})
        blob_parts.append(str(payload.get("target_app") or ""))
        blob_parts.append(str(payload.get("editor") or ""))
        blob_parts.append(str(payload.get("path") or ""))
    blob = " ".join(blob_parts).lower()
    return any(marker in blob for marker in _DOCUMENT_PLAN_MARKERS)


def vision_recovery_max_attempts_for_plan(plan: ExecutionPlan) -> int:
    if is_document_plan(plan):
        return 0  # skip LibreOffice / document vision recovery loops
    return int(VISION_RECOVERY_MAX_ATTEMPTS)


@dataclass(frozen=True)
class VisionRecoveryResult:
    recovered: bool
    verify_outcome: Any
    retry_outcomes: list[Any]


def enrich_recovery_command_payload(
    payload: dict[str, Any],
    *,
    command_type: str,
    plan: ExecutionPlan,
    step_id: str,
) -> dict[str, Any]:
    """Attach policy-required AI metadata for recovery capture/verify commands."""
    enriched = dict(payload)
    capability, risk = _RECOVERY_AI_METADATA.get(command_type, ("agent.status.read", "low"))
    enriched.setdefault("required_capability", capability)
    enriched.setdefault("capability_risk_level", risk)
    enriched.setdefault("user_consent_state", "consented_via_local_avatar")
    enriched.setdefault("source_agent", "iuve_avatar_plan_executor")
    enriched.setdefault("model_source", "avatar_plan_executor_v0.2.2.28")
    enriched.setdefault(
        "reasoning_summary",
        f"Vision recovery {command_type}:{step_id} for {plan.goal_summary[:120]}",
    )
    enriched.setdefault("intended_action", f"{command_type}:{step_id}")
    enriched.setdefault("plan_id", plan.plan_id)
    enriched.setdefault("step_id", step_id)
    return enriched


def resolve_capture_dependency_step_id(
    verify_step: ExecutionPlanStep,
    verify_payload: dict[str, Any],
) -> str:
    depends_on_step = str(verify_payload.get("depends_on_step") or "").strip()
    if depends_on_step:
        return depends_on_step
    if verify_step.depends_on:
        return verify_step.depends_on[0]
    return f"{verify_step.step_id}_capture"


def resolve_fallback_capture_evidence(
    *,
    context: PlanStepContext,
    verify_payload: dict[str, Any],
    verify_step: ExecutionPlanStep,
    initial_outcome: Any | None = None,
) -> dict[str, Any]:
    """Preserve the last screenshot evidence across recovery attempts."""
    candidates: list[dict[str, Any]] = []
    if initial_outcome is not None:
        evidence = dict(getattr(initial_outcome, "evidence", None) or {})
        if evidence.get("screenshot_path") or evidence.get("path"):
            candidates.append(evidence)
    screenshot_path = str(verify_payload.get("screenshot_path") or "").strip()
    if screenshot_path:
        candidates.append(
            {
                "path": screenshot_path,
                "sha256": str(verify_payload.get("screenshot_sha256") or ""),
            }
        )
    depends_on = resolve_capture_dependency_step_id(verify_step, verify_payload)
    dep_evidence = context.get_evidence(depends_on)
    if dep_evidence.get("path"):
        candidates.append(dep_evidence)
    for item in candidates:
        path = str(item.get("screenshot_path") or item.get("path") or "").strip()
        if path:
            return {
                "path": path,
                "sha256": str(item.get("screenshot_sha256") or item.get("sha256") or ""),
            }
    return {}


def build_recovery_capture_payload(
    *,
    plan: ExecutionPlan,
    verify_step: ExecutionPlanStep,
    attempt: int,
) -> tuple[str, dict[str, Any]]:
    capture_step_id = f"{verify_step.step_id}_recovery_capture_{attempt}"
    payload: dict[str, Any] = {
        "region": "full",
        "delay_ms": 500,
        "screen_vision_granted": True,
        "recovery_attempt": attempt,
    }
    return capture_step_id, enrich_recovery_command_payload(
        payload,
        command_type="capture_screenshot",
        plan=plan,
        step_id=capture_step_id,
    )


def build_recovery_verify_payload(
    *,
    plan: ExecutionPlan,
    verify_step: ExecutionPlanStep,
    verify_payload: dict[str, Any],
    capture_step_id: str,
    capture_evidence: dict[str, Any],
    attempt: int,
    fallback_evidence: dict[str, Any] | None = None,
) -> dict[str, Any]:
    evidence = dict(capture_evidence or {})
    if not evidence.get("path") and fallback_evidence:
        evidence = dict(fallback_evidence)
    payload: dict[str, Any] = {
        "expected_text": str(verify_payload.get("expected_text") or "").strip(),
        "depends_on_step": capture_step_id,
        "match_mode": str(verify_payload.get("match_mode") or "contains"),
        "verify_mode": str(verify_payload.get("verify_mode") or "contains"),
        "case_sensitive": bool(verify_payload.get("case_sensitive", False)),
        "min_confidence": float(verify_payload.get("min_confidence", 0.7)),
        "capture_if_missing": False,
        "screen_vision_granted": bool(verify_payload.get("screen_vision_granted", True)),
        "recovery_attempt": attempt,
    }
    if evidence.get("path"):
        payload["screenshot_path"] = evidence["path"]
        payload["screenshot_sha256"] = evidence.get("sha256", "")
    return enrich_recovery_command_payload(
        payload,
        command_type="verify_screen_contains_text",
        plan=plan,
        step_id=verify_step.step_id,
    )


def vision_recovery_enabled(callback: VisionRecoveryCallback | None) -> bool:
    return callback is not None and avatar_desktop_vision_enabled()


def recovery_capture_step_index(*, plan: ExecutionPlan, verify_step: ExecutionPlanStep, verify_payload: dict[str, Any]) -> int:
    """1-based index of the capture step that feeds verify (for recovery UI progress)."""
    capture_id = resolve_capture_dependency_step_id(verify_step, verify_payload)
    for index, step in enumerate(plan.steps, start=1):
        if step.step_id == capture_id:
            return index
    if verify_step.depends_on:
        for index, step in enumerate(plan.steps, start=1):
            if step.step_id == verify_step.depends_on[0]:
                return index
    return max(1, len(plan.steps) - 1)


def recovery_verify_step_index(*, plan: ExecutionPlan, verify_step: ExecutionPlanStep) -> int:
    for index, step in enumerate(plan.steps, start=1):
        if step.step_id == verify_step.step_id:
            return index
    return len(plan.steps)
