"""Helper script for checkpoint_real_rollback.robot integration tests.

Each subcommand exercises CheckpointService.rollback_to_checkpoint() against
a real git repository to verify that the method executes ``git reset --hard``
and actually restores file system state.
"""

from __future__ import annotations

import os
import shutil
import subprocess
import sys
import tempfile
from collections.abc import Callable
from pathlib import Path
from typing import NoReturn

# Ensure local source tree AND robot/ directory are importable.
_ROOT = Path(__file__).resolve().parents[1]
_SRC = str(_ROOT / "src")
_ROBOT = str(_ROOT / "robot")
for _p in (_SRC, _ROBOT):
    if _p not in sys.path:
        sys.path.insert(0, _p)

from cleveragents.application.services.checkpoint_service import (  # noqa: E402
    CheckpointService,
)
from cleveragents.infrastructure.events.models import DomainEvent  # noqa: E402
from cleveragents.infrastructure.events.types import EventType  # noqa: E402

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------

_INITIAL_CONTENT = "initial content\n"
_MODIFIED_CONTENT = "modified after checkpoint\n"
_TRACKED_FILENAME = "tracked.txt"
_NEW_FILENAME = "extra.txt"
_PLAN_ID = "01JBG822CHKPT000PXAN000000"


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _fail(msg: str) -> NoReturn:
    """Print failure message to stderr and exit with code 1."""
    print(msg, file=sys.stderr)
    sys.exit(1)


def _run_git(args: list[str], cwd: str) -> subprocess.CompletedProcess[str]:
    """Run a git command and return the completed process."""
    return subprocess.run(
        ["git", *args],
        cwd=cwd,
        capture_output=True,
        text=True,
        check=True,
        timeout=30,
    )


def _get_head_sha(cwd: str) -> str:
    """Return the HEAD commit SHA of a git repository."""
    result = _run_git(["rev-parse", "HEAD"], cwd=cwd)
    return result.stdout.strip()


def _create_workspace() -> str:
    """Create a temporary git workspace with an initial committed file.

    Returns the path to the temporary directory.
    """
    tmpdir = tempfile.mkdtemp(prefix="checkpoint_822_robot_")
    try:
        _run_git(["init", "--initial-branch=main"], cwd=tmpdir)
        _run_git(["config", "user.email", "test@example.com"], cwd=tmpdir)
        _run_git(["config", "user.name", "Test"], cwd=tmpdir)

        tracked_path = os.path.join(tmpdir, _TRACKED_FILENAME)
        Path(tracked_path).write_text(_INITIAL_CONTENT, encoding="utf-8")
        _run_git(["add", _TRACKED_FILENAME], cwd=tmpdir)
        _run_git(["commit", "-m", "Initial commit"], cwd=tmpdir)
    except Exception:
        shutil.rmtree(tmpdir, ignore_errors=True)
        raise
    return tmpdir


class _RecordingEventBus:
    """In-memory event bus that records emitted events."""

    def __init__(self) -> None:
        self.events: list[DomainEvent] = []

    def emit(self, event: DomainEvent) -> None:
        self.events.append(event)

    def subscribe(self, event_type: object, handler: object) -> None:
        pass


# ---------------------------------------------------------------------------
# Subcommands
# ---------------------------------------------------------------------------


def rollback_restores_content() -> None:
    """Verify that rollback reverts a modified tracked file."""
    tmpdir = _create_workspace()
    try:
        tracked_path = os.path.join(tmpdir, _TRACKED_FILENAME)
        initial_sha = _get_head_sha(tmpdir)

        service = CheckpointService()
        service.register_sandbox(_PLAN_ID, tmpdir)
        checkpoint = service.create_checkpoint(
            plan_id=_PLAN_ID,
            sandbox_ref=initial_sha,
            reason="Integration test checkpoint",
            checkpoint_type="manual",
        )

        # Modify the tracked file and commit
        Path(tracked_path).write_text(_MODIFIED_CONTENT)
        _run_git(["add", _TRACKED_FILENAME], cwd=tmpdir)
        _run_git(["commit", "-m", "Modify tracked file"], cwd=tmpdir)

        # Invoke rollback
        service.rollback_to_checkpoint(
            plan_id=_PLAN_ID,
            checkpoint_id=checkpoint.checkpoint_id,
        )

        # Assert file content matches checkpoint state
        actual = Path(tracked_path).read_text()
        if actual != _INITIAL_CONTENT:
            _fail(
                f"Tracked file not reverted after rollback. "
                f"Expected: {_INITIAL_CONTENT!r}, Got: {actual!r}."
            )

        print("checkpoint-rollback-restores-content-ok")
    finally:
        shutil.rmtree(tmpdir, ignore_errors=True)


def rollback_removes_added_files() -> None:
    """Verify that rollback removes files added after the checkpoint."""
    tmpdir = _create_workspace()
    try:
        initial_sha = _get_head_sha(tmpdir)

        service = CheckpointService()
        service.register_sandbox(_PLAN_ID, tmpdir)
        checkpoint = service.create_checkpoint(
            plan_id=_PLAN_ID,
            sandbox_ref=initial_sha,
            reason="Integration test checkpoint",
            checkpoint_type="manual",
        )

        # Add a new file and commit
        new_path = os.path.join(tmpdir, _NEW_FILENAME)
        Path(new_path).write_text("new file content\n")
        _run_git(["add", _NEW_FILENAME], cwd=tmpdir)
        _run_git(["commit", "-m", "Add new file"], cwd=tmpdir)

        # Invoke rollback
        service.rollback_to_checkpoint(
            plan_id=_PLAN_ID,
            checkpoint_id=checkpoint.checkpoint_id,
        )

        # Assert the new file no longer exists
        if os.path.exists(new_path):
            _fail(f"New file still exists after rollback: {new_path}.")

        print("checkpoint-rollback-removes-added-files-ok")
    finally:
        shutil.rmtree(tmpdir, ignore_errors=True)


def rollback_emits_event() -> None:
    """Verify that rollback emits a CHECKPOINT_RESTORED domain event."""
    tmpdir = _create_workspace()
    try:
        initial_sha = _get_head_sha(tmpdir)
        event_bus = _RecordingEventBus()

        service = CheckpointService(event_bus=event_bus)  # type: ignore[arg-type]
        service.register_sandbox(_PLAN_ID, tmpdir)
        checkpoint = service.create_checkpoint(
            plan_id=_PLAN_ID,
            sandbox_ref=initial_sha,
            reason="Integration test checkpoint",
            checkpoint_type="manual",
        )

        # Modify and commit so rollback has something to do
        tracked_path = os.path.join(tmpdir, _TRACKED_FILENAME)
        Path(tracked_path).write_text(_MODIFIED_CONTENT)
        _run_git(["add", _TRACKED_FILENAME], cwd=tmpdir)
        _run_git(["commit", "-m", "Modify tracked file"], cwd=tmpdir)

        service.rollback_to_checkpoint(
            plan_id=_PLAN_ID,
            checkpoint_id=checkpoint.checkpoint_id,
        )

        restored_events = [
            e for e in event_bus.events if e.event_type == EventType.CHECKPOINT_RESTORED
        ]
        if len(restored_events) != 1:
            _fail(f"Expected 1 CHECKPOINT_RESTORED event, got {len(restored_events)}.")

        print("checkpoint-rollback-emits-event-ok")
    finally:
        shutil.rmtree(tmpdir, ignore_errors=True)


def rollback_rejects_nonexistent_sandbox() -> None:
    """Verify that rollback rejects a nonexistent sandbox path."""
    from cleveragents.core.exceptions import BusinessRuleViolation

    tmpdir = _create_workspace()
    try:
        initial_sha = _get_head_sha(tmpdir)

        service = CheckpointService()
        service.register_sandbox(_PLAN_ID, "/tmp/nonexistent_sandbox_path_822")
        checkpoint = service.create_checkpoint(
            plan_id=_PLAN_ID,
            sandbox_ref=initial_sha,
            reason="Integration test checkpoint",
            checkpoint_type="manual",
        )

        try:
            service.rollback_to_checkpoint(
                plan_id=_PLAN_ID,
                checkpoint_id=checkpoint.checkpoint_id,
            )
            _fail("Expected BusinessRuleViolation for nonexistent sandbox path")
        except BusinessRuleViolation as exc:
            if "sandbox path does not exist" not in str(exc):
                _fail(f"Unexpected error message: {exc}")

        print("checkpoint-rollback-rejects-nonexistent-sandbox-ok")
    finally:
        shutil.rmtree(tmpdir, ignore_errors=True)


# ---------------------------------------------------------------------------
# Dispatcher
# ---------------------------------------------------------------------------

_COMMANDS: dict[str, Callable[[], None]] = {
    "rollback-restores-content": rollback_restores_content,
    "rollback-removes-added-files": rollback_removes_added_files,
    "rollback-emits-event": rollback_emits_event,
    "rollback-rejects-nonexistent-sandbox": rollback_rejects_nonexistent_sandbox,
}

if __name__ == "__main__":
    if len(sys.argv) < 2 or sys.argv[1] not in _COMMANDS:
        print(
            f"Usage: {sys.argv[0]} <{'|'.join(_COMMANDS)}>",
            file=sys.stderr,
        )
        sys.exit(1)
    cmd = _COMMANDS[sys.argv[1]]
    cmd()
