"""Step definitions for TDD Issue #822 — checkpoint rollback is simulated.

These steps exercise ``CheckpointService.rollback_to_checkpoint()`` against
a real git repository to prove that the method does **not** execute a
``git reset --hard`` and therefore leaves the filesystem unchanged.

On ``master`` (before the fix), ``rollback_to_checkpoint()`` constructs a
``RollbackResult`` with the checkpoint metadata but skips the actual git
operation.  Files modified after the checkpoint remain modified after
rollback, which is the bug.

The feature is tagged ``@tdd_expected_fail`` so the assertion failure
(proving the bug) is inverted to a pass by the Behave environment hook.
"""

from __future__ import annotations

import os
import shutil
import subprocess
import tempfile
from pathlib import Path

from behave import given, then, when
from behave.runner import Context

from cleveragents.application.services.checkpoint_service import CheckpointService

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

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


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


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()


# ---------------------------------------------------------------------------
# Given steps
# ---------------------------------------------------------------------------


@given("a temporary git workspace with an initial committed file")
def step_create_workspace(context: Context) -> None:
    """Create a temp directory with a git repo containing a tracked file."""
    tmpdir = tempfile.mkdtemp(prefix="tdd_checkpoint_822_")
    context.workspace_dir = tmpdir

    # Register cleanup early to avoid leaks if subsequent steps fail
    def _cleanup() -> None:
        shutil.rmtree(tmpdir, ignore_errors=True)

    context.add_cleanup(_cleanup)

    # Initialise git repo
    _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)

    # Create and commit the tracked file
    tracked_path = os.path.join(tmpdir, _TRACKED_FILENAME)
    Path(tracked_path).write_text(_INITIAL_CONTENT)
    _run_git(["add", _TRACKED_FILENAME], cwd=tmpdir)
    _run_git(["commit", "-m", "Initial commit"], cwd=tmpdir)

    context.tracked_file_path = tracked_path
    context.initial_sha = _get_head_sha(tmpdir)


@given("a checkpoint is created from the current commit")
def step_create_checkpoint(context: Context) -> None:
    """Create a checkpoint referencing the current HEAD commit."""
    plan_id_value = "01JBG822CHKPT000PXAN000000"
    sandbox_ref = context.initial_sha

    service = CheckpointService()
    service.register_sandbox(plan_id_value, context.workspace_dir)

    checkpoint = service.create_checkpoint(
        plan_id=plan_id_value,
        sandbox_ref=sandbox_ref,
        reason="TDD checkpoint for bug #822",
        checkpoint_type="manual",
    )

    context.checkpoint_service = service
    context.checkpoint = checkpoint
    context.plan_id = plan_id_value


@given("the tracked file is modified after the checkpoint")
def step_modify_tracked_file(context: Context) -> None:
    """Overwrite the tracked file and commit the change."""
    Path(context.tracked_file_path).write_text(_MODIFIED_CONTENT)
    _run_git(["add", _TRACKED_FILENAME], cwd=context.workspace_dir)
    _run_git(["commit", "-m", "Modify tracked file"], cwd=context.workspace_dir)


@given("a new file is added and committed after the checkpoint")
def step_add_new_file(context: Context) -> None:
    """Create a new file and commit it after the checkpoint."""
    new_path = os.path.join(context.workspace_dir, _NEW_FILENAME)
    Path(new_path).write_text("new file content\n")
    _run_git(["add", _NEW_FILENAME], cwd=context.workspace_dir)
    _run_git(["commit", "-m", "Add new file"], cwd=context.workspace_dir)
    context.new_file_path = new_path


# ---------------------------------------------------------------------------
# When steps
# ---------------------------------------------------------------------------


@when("I invoke rollback_to_checkpoint targeting the checkpoint")
def step_invoke_rollback(context: Context) -> None:
    """Call rollback_to_checkpoint and store the result."""
    service: CheckpointService = context.checkpoint_service
    result = service.rollback_to_checkpoint(
        plan_id=context.plan_id,
        checkpoint_id=context.checkpoint.checkpoint_id,
    )
    context.rollback_result = result


# ---------------------------------------------------------------------------
# Then steps
# ---------------------------------------------------------------------------


@then("the tracked file content should match the checkpoint state")
def step_assert_file_reverted(context: Context) -> None:
    """Assert that the tracked file has been reverted to its initial content.

    This assertion FAILS on master because rollback_to_checkpoint() does
    not execute git reset --hard — the file still contains the modified
    content.  The failure proves bug #822 exists.
    """
    actual = Path(context.tracked_file_path).read_text()
    assert actual == _INITIAL_CONTENT, (
        f"Expected tracked file to be reverted to checkpoint content.\n"
        f"Expected: {_INITIAL_CONTENT!r}\n"
        f"Actual:   {actual!r}\n"
        f"Bug #822: rollback_to_checkpoint() did not execute git reset --hard."
    )


@then("the new file should not exist in the workspace")
def step_assert_new_file_removed(context: Context) -> None:
    """Assert that the file added after the checkpoint no longer exists.

    This assertion FAILS on master because rollback_to_checkpoint() does
    not execute git reset --hard — the new file remains on disk.  The
    failure proves bug #822 exists.
    """
    assert not os.path.exists(context.new_file_path), (
        f"Expected new file to be removed after rollback.\n"
        f"File still exists: {context.new_file_path}\n"
        f"Bug #822: rollback_to_checkpoint() did not execute git reset --hard."
    )
