"""Active command pipeline: poll → policy → stub exec → ack (0.1.19 Phase 3)."""

from __future__ import annotations

from dataclasses import dataclass
import os
from typing import Any, Callable, Literal, Protocol

from ...core.pairing import PairingCoordinator
from .audit_logger import CommandAuditLogger
from .capability_store import CapabilityStore
from .command_executor import CommandExecutor
from .command_models import (
    CommandEnvelope,
    ConfirmationCallback,
    ConfirmationResult,
    ExecutionResult,
    PolicyDecision,
)
from .confirmation_bridge import (
    ConfirmationBridge,
    build_service_confirmation_callback,
    default_state_dir,
)
from .policy_engine import PolicyContext, PolicyEngine


class RuntimeSnapshotView(Protocol):
    status: str
    failure_count: int
    last_error: str
    last_heartbeat_at: str


TrustResolver = Callable[[], str]
SnapshotProvider = Callable[[], RuntimeSnapshotView]


@dataclass
class ActiveCommandPipeline:
    """Orchestrates poll response handling through policy and command execution."""

    pairing: PairingCoordinator
    policy_engine: PolicyEngine
    capability_store: CapabilityStore
    trust_resolver: TrustResolver
    app_version: str
    command_executor: CommandExecutor | None = None
    snapshot_provider: SnapshotProvider | None = None
    audit_logger: CommandAuditLogger | None = None
    confirmation_callback: ConfirmationCallback | None = None

    def process_one_command(self) -> bool:
        """Poll backend and process at most one delivered command."""
        if self.try_resume_deferred_confirmation():
            return True
        response = self.pairing.poll_next_command()
        return self.process_poll_response(response)

    def try_resume_deferred_confirmation(self) -> bool:
        """Resume a deferred MEDIUM+ command after UI response (cross-process bridge)."""
        try:
            bridge = ConfirmationBridge(state_dir=default_state_dir())
        except Exception:
            return False

        for request in bridge.list_pending():
            request_id = str(
                request.get("request_id") or request.get("command_id") or ""
            ).strip()
            if not request_id:
                continue
            state = str(request.get("state") or "").strip().lower()
            if state not in {"waiting", "awaiting_ui"}:
                continue
            response = bridge.read_response(request_id)
            if response is None:
                continue
            try:
                envelope = ConfirmationBridge.envelope_from_request(request)
                decision = ConfirmationBridge.decision_from_request(request)
            except ValueError:
                bridge.clear_request(request_id)
                continue
            bridge.clear_request(request_id)
            always_allow = str(response.get("always_allow_command_type") or "").strip()
            if always_allow:
                self._apply_always_allow_type(always_allow)
            result = str(response.get("result") or "").strip().lower()
            if result != "approve":
                audit_record_id = self._audit_denied(
                    envelope,
                    capability_key=decision.capability_key,
                    result_summary="user_declined_confirmation",
                )
                self._submit_deny(
                    envelope,
                    reason_code="user_declined_confirmation",
                    error_message="permission_denied:user_declined_confirmation",
                    capability_key=decision.capability_key,
                    audit_record_id=audit_record_id,
                )
                return True
            self._execute_after_confirmation(envelope, decision)
            return True
        return False

    def process_poll_response(self, response: dict[str, Any]) -> bool:
        """Parse poll response, evaluate policy, execute or deny, always ack."""
        if str(response.get("status", "")).strip().lower() != "ok":
            return False

        try:
            envelope = CommandEnvelope.from_poll_response(response)
        except ValueError:
            return False

        self._process_envelope(envelope)
        return True

    def _process_envelope(self, envelope: CommandEnvelope) -> None:
        command_id = envelope.command_id
        requested_by = self._requested_by(envelope)
        cwd = self._resolve_cwd(envelope)
        try:
            trust_state = self._resolve_trust_state()
            context = PolicyContext(
                trust_state=trust_state,
                capability_store=self.capability_store,
                action_session_store=getattr(self.capability_store, "action_session_store", None),
                always_allow_types=self._resolve_always_allow_types(),
            )
            decision = self.policy_engine.evaluate(envelope, context)

            if not decision.allowed:
                audit_record_id = self._audit_denied(
                    envelope,
                    capability_key=decision.capability_key,
                    result_summary=decision.reason_code or decision.error_message,
                )
                self._submit_deny(envelope, decision, audit_record_id=audit_record_id)
                return

            if decision.require_confirmation:
                confirmation = self._request_user_confirmation(envelope, decision)
                if confirmation == "defer":
                    return
                if confirmation != "approve":
                    return

            self._execute_after_confirmation(
                envelope,
                decision,
                requested_by=requested_by,
                cwd=cwd,
            )
        except Exception as exc:
            audit_record_id = ""
            if self.audit_logger is not None:
                try:
                    audit_record_id = self.audit_logger.log_command_failed(
                        command_id=command_id,
                        command_type=envelope.command_type,
                        requested_by=requested_by,
                        cwd=cwd,
                        result_summary=str(exc),
                        policy_decision="allowed",
                    )
                except Exception:
                    audit_record_id = ""
            result: dict[str, Any] = {
                "execution_result": "failed",
                "evidence": {"error": str(exc)},
                "policy_decision": "allowed",
            }
            if audit_record_id:
                result["audit_record_id"] = audit_record_id
            self.pairing.submit_command_result(
                command_id=command_id,
                success=False,
                result=result,
                error_message=f"command_failed:{exc}",
            )

    def _resolve_trust_state(self) -> str:
        try:
            return str(self.trust_resolver() or "").strip() or "unknown"
        except Exception:
            return "unknown"

    def _resolve_always_allow_types(self) -> frozenset[str]:
        store = self.capability_store
        getter = getattr(store, "always_allow_types", None)
        if getter is not None:
            try:
                value = getter() if callable(getter) else getter
                if isinstance(value, (set, frozenset, list, tuple)):
                    return frozenset(str(item) for item in value if str(item).strip())
            except Exception:
                return frozenset()
        return frozenset()

    def _request_user_confirmation(
        self,
        envelope: CommandEnvelope,
        decision: PolicyDecision,
    ) -> ConfirmationResult:
        callback = self.confirmation_callback or build_service_confirmation_callback()

        try:
            result = callback(envelope, decision)
        except Exception:
            result = "decline"

        if result == "defer":
            self._audit_denied(
                envelope,
                capability_key=decision.capability_key,
                result_summary="confirmation_deferred",
            )
            return "defer"

        if result != "approve":
            audit_record_id = self._audit_denied(
                envelope,
                capability_key=decision.capability_key,
                result_summary="user_declined_confirmation",
            )
            self._submit_deny(
                envelope,
                reason_code="user_declined_confirmation",
                error_message="permission_denied:user_declined_confirmation",
                capability_key=decision.capability_key,
                audit_record_id=audit_record_id,
            )
            return "decline"
        return "approve"

    def _execute_after_confirmation(
        self,
        envelope: CommandEnvelope,
        decision: PolicyDecision,
        *,
        requested_by: str = "",
        cwd: str = "",
    ) -> None:
        command_id = envelope.command_id
        requested_by = requested_by or self._requested_by(envelope)
        cwd = cwd or self._resolve_cwd(envelope)
        attempt_audit_id = self._audit_attempt(
            envelope,
            capability_key=decision.capability_key,
            requested_by=requested_by,
            cwd=cwd,
            decision=decision,
        )
        if attempt_audit_id is None:
            self._submit_deny(
                envelope,
                reason_code="audit_write_failed",
                error_message="permission_denied:audit_write_failed",
                capability_key=decision.capability_key,
            )
            return

        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._execute_command(envelope, capability_key=decision.capability_key)
        outcome_audit_id = self._audit_outcome(
            envelope,
            exec_result,
            capability_key=decision.capability_key,
            requested_by=requested_by,
            cwd=cwd,
        )
        exec_result.audit_record_id = outcome_audit_id or attempt_audit_id
        self.pairing.submit_command_result(
            command_id=command_id,
            success=exec_result.success,
            result=exec_result.to_result_payload(),
            error_message=exec_result.error_message or None,
        )

    def _apply_always_allow_type(self, command_type: str) -> None:
        store = self.capability_store
        adder = getattr(store, "add_always_allow_type", None)
        if callable(adder):
            try:
                adder(command_type)
            except Exception:
                return

    def _submit_deny(
        self,
        envelope: CommandEnvelope,
        decision: Any | None = None,
        *,
        reason_code: str = "",
        error_message: str = "",
        capability_key: str = "",
        audit_record_id: str = "",
    ) -> None:
        code = reason_code or getattr(decision, "reason_code", "") or "denied"
        message = error_message or getattr(decision, "error_message", "") or f"permission_denied:{code}"
        cap_key = capability_key or getattr(decision, "capability_key", "")
        result: dict[str, Any] = {
            "execution_result": code,
            "evidence": {},
            "policy_decision": "denied",
        }
        if cap_key:
            result["capability_key"] = cap_key
        if audit_record_id:
            result["audit_record_id"] = audit_record_id
        self.pairing.submit_command_result(
            command_id=envelope.command_id,
            success=False,
            result=result,
            error_message=message,
        )

    def _requested_by(self, envelope: CommandEnvelope) -> str:
        return str(
            envelope.payload.get("source_agent")
            or envelope.payload.get("requested_by")
            or "backend"
        ).strip() or "backend"

    def _resolve_cwd(self, envelope: CommandEnvelope) -> str:
        scope = envelope.scope
        paths = scope.get("workspace_paths")
        if isinstance(paths, list) and paths:
            return str(paths[0])
        return os.getcwd()

    def _audit_denied(
        self,
        envelope: CommandEnvelope,
        *,
        capability_key: str = "",
        result_summary: str = "",
    ) -> str:
        if self.audit_logger is None:
            return ""
        return self.audit_logger.log_command_denied(
            command_id=envelope.command_id,
            command_type=envelope.command_type,
            capability=capability_key,
            requested_by=self._requested_by(envelope),
            cwd=self._resolve_cwd(envelope),
            policy_decision="denied",
            result_summary=result_summary,
        )

    def _audit_attempt(
        self,
        envelope: CommandEnvelope,
        *,
        capability_key: str = "",
        requested_by: str = "",
        cwd: str = "",
        decision: PolicyDecision | None = None,
    ) -> str | None:
        if self.audit_logger is None:
            return ""
        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=requested_by,
                    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=requested_by,
                cwd=cwd,
                policy_decision="allowed",
            )
        except Exception:
            return None

    def _audit_outcome(
        self,
        envelope: CommandEnvelope,
        exec_result: ExecutionResult,
        *,
        capability_key: str = "",
        requested_by: str = "",
        cwd: str = "",
    ) -> str:
        if self.audit_logger is None:
            return ""
        evidence = exec_result.evidence
        stdout = str(evidence.get("stdout") or "")
        stderr = str(evidence.get("stderr") or "")
        summary = exec_result.error_message or exec_result.execution_result
        common = {
            "command_id": envelope.command_id,
            "command_type": envelope.command_type,
            "capability": capability_key,
            "requested_by": requested_by,
            "cwd": cwd,
            "exit_code": exec_result.exit_code,
            "stdout": stdout,
            "stderr": stderr,
            "result_summary": summary,
            "policy_decision": exec_result.policy_decision,
        }
        if exec_result.success:
            return self.audit_logger.log_command_completed(
                **common,
                approved_by="local_user",
            )
        return self.audit_logger.log_command_failed(**common)

    def _execute_command(
        self,
        envelope: CommandEnvelope,
        *,
        capability_key: str = "",
    ) -> ExecutionResult:
        if self.command_executor is not None:
            return self.command_executor.execute(envelope, capability_key=capability_key)
        return self._legacy_execute(envelope, capability_key=capability_key)

    def _legacy_execute(
        self,
        envelope: CommandEnvelope,
        *,
        capability_key: str = "",
    ) -> ExecutionResult:
        """Fallback when no CommandExecutor is wired (tests / minimal bootstrap)."""
        command_type = envelope.command_type.strip().lower()
        payload = envelope.payload
        snapshot = self.snapshot_provider() if self.snapshot_provider else None

        if command_type == "get_status":
            evidence = {
                "runtime_status": getattr(snapshot, "status", "Unknown"),
                "last_heartbeat_at": getattr(snapshot, "last_heartbeat_at", ""),
                "failure_count": getattr(snapshot, "failure_count", 0),
            }
            return ExecutionResult(
                success=True,
                capability_key=capability_key,
                evidence=evidence,
            )

        if command_type == "get_diagnostics":
            evidence = {
                "runtime_status": getattr(snapshot, "status", "Unknown"),
                "last_error": getattr(snapshot, "last_error", ""),
                "failure_count": getattr(snapshot, "failure_count", 0),
                "heartbeat_at": getattr(snapshot, "last_heartbeat_at", ""),
            }
            return ExecutionResult(success=True, capability_key=capability_key, evidence=evidence)

        if command_type == "collect_logs":
            return ExecutionResult(
                success=True,
                capability_key=capability_key,
                evidence={
                    "log_collection": "not_available_in_core_runtime",
                    "max_lines": int(payload.get("max_lines") or 200),
                },
            )

        if command_type == "restart_agent":
            return ExecutionResult(
                success=True,
                capability_key=capability_key,
                evidence={
                    "policy": "manual_restart_required",
                    "confirm_restart": bool(payload.get("confirm_restart", True)),
                    "approval_ticket": str(payload.get("approval_ticket") or ""),
                },
            )

        if command_type == "check_update":
            return ExecutionResult(
                success=True,
                capability_key=capability_key,
                evidence={
                    "current_version": self.app_version,
                    "update_available": False,
                },
            )

        if command_type == "open_local_console_notification_only":
            message = str(payload.get("message") or "Open local console from tray menu")
            return ExecutionResult(
                success=True,
                capability_key=capability_key,
                evidence={"notification": message},
            )

        raise RuntimeError("unsupported_command_type")
