"""RollbackExecutor — 阶段失败后执行回滚命令 (G9/U4)

复用 VerificationLoop 的 asyncio.create_subprocess_shell 模式 (KTD7)：
绕过 ShellTool，避免 confirm_callback 对 `git checkout` 的拦截。

设计依据：
- KTD6: 回滚是 opt-in 行为，未配置 rollback_command 时不会执行
- KTD7: 不走 ShellTool，避免 _is_dangerous 触发 confirm_callback
- R21: checkpoint.save 仅在回滚校验通过后调用
"""

from __future__ import annotations

import asyncio
import logging
from dataclasses import dataclass

logger = logging.getLogger(__name__)


@dataclass
class RollbackResult:
    """单次 subprocess 执行结果"""

    passed: bool
    exit_code: int
    stdout: str
    stderr: str
    command: str


class RollbackExecutor:
    """执行 validation_command / rollback_command 的子进程封装

    与 VerificationLoop 同构，但语义不同：
    - validate(): 返回 passed=False 表示校验失败，需要触发 rollback
    - execute(): 返回 passed=False 表示回滚本身失败，需跳过 checkpoint.save (R21)
    """

    def __init__(
        self,
        working_dir: str | None = None,
        timeout: float = 30.0,
    ) -> None:
        self._working_dir = working_dir
        self._timeout = timeout

    async def _run(self, command: str) -> RollbackResult:
        try:
            proc = await asyncio.create_subprocess_shell(
                command,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
                cwd=self._working_dir,
            )
        except Exception as e:  # noqa: BLE001 - subprocess spawn failure surface
            return RollbackResult(
                passed=False,
                exit_code=-1,
                stdout="",
                stderr=f"Failed to spawn command: {e}",
                command=command,
            )

        try:
            stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=self._timeout)
        except asyncio.TimeoutError:
            try:
                proc.kill()
            except ProcessLookupError:
                pass
            await proc.wait()
            return RollbackResult(
                passed=False,
                exit_code=-1,
                stdout="",
                stderr=f"Command timed out after {self._timeout}s: {command}",
                command=command,
            )

        out_str = stdout.decode("utf-8", errors="replace") if stdout else ""
        err_str = stderr.decode("utf-8", errors="replace") if stderr else ""
        return RollbackResult(
            passed=proc.returncode == 0,
            exit_code=proc.returncode if proc.returncode is not None else -1,
            stdout=out_str,
            stderr=err_str,
            command=command,
        )

    async def validate(self, command: str) -> RollbackResult:
        """运行 validation_command，passed=False 表示需要触发 rollback"""
        return await self._run(command)

    async def execute(self, command: str) -> RollbackResult:
        """运行 rollback_command，passed=False 表示回滚本身失败 (R21)"""
        return await self._run(command)
