"""Step definitions for _get_base_env thread-safety (TOCTOU fix) scenarios.

These steps exercise the double-checked locking pattern introduced in
``git_tools._get_base_env()`` to eliminate the TOCTOU race condition
where two concurrent threads could both observe ``_BASE_ENV is None``
and write potentially different snapshots.
"""

from __future__ import annotations

import threading
from typing import Any

import cleveragents.tool.builtins.git_tools as _git_tools_mod
from behave import then, when
from cleveragents.tool.builtins.git_tools import _get_base_env

__all__: list[str] = []


# ---------------------------------------------------------------------------
# Whens
# ---------------------------------------------------------------------------


@when("I call _get_base_env twice")
def step_when_call_get_base_env_twice(context: Any) -> None:
    # Reset the module-level cache so the test is independent of call order.
    original = _git_tools_mod._BASE_ENV
    _git_tools_mod._BASE_ENV = None
    try:
        context.base_env_first = _get_base_env()
        context.base_env_second = _get_base_env()
    finally:
        _git_tools_mod._BASE_ENV = original


@when("I call _get_base_env")
def step_when_call_get_base_env(context: Any) -> None:
    original = _git_tools_mod._BASE_ENV
    _git_tools_mod._BASE_ENV = None
    try:
        context.base_env_result = _get_base_env()
    finally:
        _git_tools_mod._BASE_ENV = original


@when("I call _get_base_env concurrently from 20 threads")
def step_when_call_get_base_env_concurrent(context: Any) -> None:
    n_threads = 20
    results: list[dict[str, str] | None] = [None] * n_threads
    barrier = threading.Barrier(n_threads)

    # Reset the module-level cache so all threads race on first initialisation.
    original = _git_tools_mod._BASE_ENV
    _git_tools_mod._BASE_ENV = None

    def worker(idx: int) -> None:
        barrier.wait()  # synchronise all threads to maximise race probability
        results[idx] = _get_base_env()

    threads = [threading.Thread(target=worker, args=(i,)) for i in range(n_threads)]
    try:
        for t in threads:
            t.start()
        for t in threads:
            t.join(timeout=10)
    finally:
        _git_tools_mod._BASE_ENV = original

    context.concurrent_results = results


# ---------------------------------------------------------------------------
# Thens
# ---------------------------------------------------------------------------


@then("both calls should return the same dict object")
def step_then_same_dict_object(context: Any) -> None:
    assert context.base_env_first is context.base_env_second, (
        "_get_base_env() returned different dict objects on repeated calls; "
        "caching is broken"
    )


@then('the result should contain key "{key}"')
def step_then_result_contains_key(context: Any, key: str) -> None:
    assert key in context.base_env_result, (
        f"Expected key '{key}' in _get_base_env() result, "
        f"got keys: {list(context.base_env_result.keys())}"
    )


@then("all threads should receive the same dict object")
def step_then_all_threads_same_object(context: Any) -> None:
    results = context.concurrent_results
    assert all(r is not None for r in results), (
        "Some threads did not receive a result from _get_base_env()"
    )
    first = results[0]
    for i, r in enumerate(results[1:], start=1):
        assert r is first, (
            f"Thread {i} received a different dict object than thread 0; "
            "double-checked locking is broken"
        )
