"""Local avatar-initiated command execution from GTK console (0.1.20)."""

from __future__ import annotations

import secrets
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Any, Callable

from .audit_logger import CommandAuditLogger
from .capability_store import CapabilityStore
from .command_executor import CommandExecutor
from .command_models import CommandEnvelope, ConfirmationCallback, ConfirmationResult, PolicyDecision
from .confirmation_bridge import ConfirmationBridge
from .double_confirm_flow import wrap_confirmation_callback
from .policy_engine import PolicyContext, PolicyEngine


@dataclass(frozen=True)
class AvatarExecutionOutcome:
    ok: bool
    status: str
    message: str = ""
    evidence: dict[str, Any] | None = None
    require_confirmation: bool = False
    envelope: CommandEnvelope | None = None
    decision: PolicyDecision | None = None


class AvatarLocalExecutor:
    """Runs structured avatar actions through policy → confirmation → executor (no backend ack)."""

    def __init__(
        self,
        *,
        policy_engine: PolicyEngine,
        capability_store: CapabilityStore,
        command_executor: CommandExecutor,
        audit_logger: CommandAuditLogger,
        trust_resolver: Callable[[], str],
        confirmation_callback: ConfirmationCallback | None = None,
        confirmation_bridge: ConfirmationBridge | None = None,
    ) -> None:
        self._policy_engine = policy_engine
        self._capability_store = capability_store
        self._command_executor = command_executor
        self._audit_logger = audit_logger
        self._trust_resolver = trust_resolver
        self._confirmation_callback = confirmation_callback
        self._confirmation_bridge = confirmation_bridge or ConfirmationBridge()

    def prepare(
        self,
        *,
        command_type: str,
        payload: dict[str, Any],
        scope: dict[str, Any] | None = None,
    ) -> AvatarExecutionOutcome:
        envelope = self._build_envelope(command_type, payload, scope=scope or {})
        context = PolicyContext(
            trust_state=self._trust_resolver(),
            capability_store=self._capability_store,
            action_session_store=getattr(self._capability_store, "action_session_store", None),
            always_allow_types=self._capability_store.always_allow_types,
        )
        decision = self._policy_engine.evaluate(envelope, context)
        if not decision.allowed:
            return AvatarExecutionOutcome(
                ok=False,
                status=str(decision.reason_code or decision.error_message or "denied"),
                message=str(decision.error_message or decision.reason_code or "denied"),
                envelope=envelope,
                decision=decision,
            )
        return AvatarExecutionOutcome(
            ok=True,
            status="ready",
            require_confirmation=bool(decision.require_confirmation),
            envelope=envelope,
            decision=decision,
        )

    def execute(
        self,
        envelope: CommandEnvelope,
        decision: PolicyDecision,
        *,
        confirmation: ConfirmationResult | None = None,
    ) -> AvatarExecutionOutcome:
        if decision.require_confirmation:
            resolved = confirmation or self._request_confirmation(envelope, decision)
            if resolved != "approve":
                self._audit_denied(
                    envelope,
                    capability_key=decision.capability_key,
                    result_summary="user_declined_confirmation",
                )
                return AvatarExecutionOutcome(
                    ok=False,
                    status="declined",
                    message="permission_denied:user_declined_confirmation",
                    envelope=envelope,
                    decision=decision,
                )

        attempt_id = self._audit_attempt(
            envelope,
            capability_key=decision.capability_key,
            decision=decision,
        )
        from iuve_connect_agents.shared.production_policy import stamp_post_confirm_execution_flags

        stamp_post_confirm_execution_flags(
            envelope.payload,
            stop_zone=decision.stop_zone,
            command_type=envelope.command_type,
        )
        exec_result = self._command_executor.execute(envelope, capability_key=decision.capability_key)
        self._audit_outcome(
            envelope,
            exec_result,
            capability_key=decision.capability_key,
            attempt_audit_id=attempt_id,
        )
        if exec_result.success:
            consume = getattr(self._capability_store, "consume_one_shot", None)
            if callable(consume) and decision.capability_key:
                try:
                    consume(decision.capability_key)
                except Exception:
                    pass
        return AvatarExecutionOutcome(
            ok=bool(exec_result.success),
            status=str(exec_result.execution_result or ("ok" if exec_result.success else "failed")),
            message=str(exec_result.error_message or ""),
            evidence=dict(exec_result.evidence),
            envelope=envelope,
            decision=decision,
        )

    def _request_confirmation(
        self,
        envelope: CommandEnvelope,
        decision: PolicyDecision,
    ) -> ConfirmationResult:
        callback = self._confirmation_callback
        if callback is not None:
            handler = wrap_confirmation_callback(callback)
            try:
                return handler(envelope, decision)
            except Exception:
                return "decline"
        return self._confirmation_bridge.request(envelope, decision)

    def _build_envelope(
        self,
        command_type: str,
        payload: dict[str, Any],
        *,
        scope: dict[str, Any],
    ) -> CommandEnvelope:
        merged = dict(payload)
        if scope:
            merged.setdefault("scope", dict(scope))
        return CommandEnvelope(
            command_id=f"avatar-{secrets.token_hex(8)}",
            command_type=str(command_type).strip().lower(),
            payload=merged,
            requested_at=datetime.now(timezone.utc).isoformat(),
        )

    def _audit_attempt(
        self,
        envelope: CommandEnvelope,
        *,
        capability_key: str,
        decision: PolicyDecision | None = None,
    ) -> str:
        try:
            if decision is not None and str(decision.confirmation_skip_reason or "").strip():
                return self._audit_logger.log_session_covered_execution(
                    command_id=envelope.command_id,
                    command_type=envelope.command_type,
                    capability=capability_key,
                    session_id=str(decision.action_session_id or ""),
                    plan_id=str(decision.plan_id or envelope.payload.get("plan_id") or ""),
                    requested_by=str(envelope.payload.get("source_agent") or "iuve_avatar_chat"),
                    reason=str(decision.confirmation_skip_reason),
                )
            return self._audit_logger.log_command_attempt(
                command_id=envelope.command_id,
                command_type=envelope.command_type,
                capability=capability_key,
                requested_by=str(envelope.payload.get("source_agent") or "iuve_avatar_chat"),
                cwd=str((envelope.scope.get("workspace_paths") or [""])[0]),
                policy_decision="allowed",
            )
        except Exception:
            return ""

    def _audit_denied(
        self,
        envelope: CommandEnvelope,
        *,
        capability_key: str,
        result_summary: str,
    ) -> None:
        try:
            self._audit_logger.log_command_denied(
                command_id=envelope.command_id,
                command_type=envelope.command_type,
                capability=capability_key,
                requested_by=str(envelope.payload.get("source_agent") or "iuve_avatar_chat"),
                cwd=str((envelope.scope.get("workspace_paths") or [""])[0]),
                policy_decision="denied",
                result_summary=result_summary,
            )
        except Exception:
            return

    def _audit_outcome(
        self,
        envelope: CommandEnvelope,
        exec_result: Any,
        *,
        capability_key: str,
        attempt_audit_id: str,
    ) -> None:
        common = {
            "command_id": envelope.command_id,
            "command_type": envelope.command_type,
            "capability": capability_key,
            "requested_by": str(envelope.payload.get("source_agent") or "iuve_avatar_chat"),
            "cwd": str((envelope.scope.get("workspace_paths") or [""])[0]),
            "exit_code": int(getattr(exec_result, "exit_code", 0) or 0),
            "stdout": str((getattr(exec_result, "evidence", {}) or {}).get("stdout") or ""),
            "stderr": str((getattr(exec_result, "evidence", {}) or {}).get("stderr") or ""),
            "result_summary": str(
                getattr(exec_result, "error_message", "")
                or getattr(exec_result, "execution_result", "")
            ),
            "policy_decision": str(getattr(exec_result, "policy_decision", "allowed") or "allowed"),
        }
        try:
            if bool(getattr(exec_result, "success", False)):
                self._audit_logger.log_command_completed(**common, approved_by="local_user")
            else:
                self._audit_logger.log_command_failed(**common)
        except Exception:
            if attempt_audit_id:
                return
