"""Unit tests for `OutcomeStatus` and the parallel-fold helper.

`OutcomeStatus` is the typed outcome enum (REQ-EXEC-OUTCOME-STATUSES);
`_fold_parallel_statuses` is the three-valued AND-fold the parallel
join uses to combine branch outcomes (REQ-EXEC-PARALLEL-JOIN extended
for the third status value).
"""

from __future__ import annotations

import pytest

from attractor.agent import OutcomeStatus
from attractor.engine.engine import (
    _fold_parallel_statuses,  # pyright: ignore[reportPrivateUsage]
)

# ─── OutcomeStatus.is_success_like ──────────────────────────────────────


# [unit->REQ-EXEC-OUTCOME-STATUSES]
def test_is_success_like_true_for_success() -> None:
    assert OutcomeStatus.SUCCESS.is_success_like is True


# [unit->REQ-EXEC-OUTCOME-STATUSES]
def test_is_success_like_true_for_partial_success() -> None:
    """PARTIAL_SUCCESS satisfies goal_gate per upstream §3.4."""
    assert OutcomeStatus.PARTIAL_SUCCESS.is_success_like is True


# [unit->REQ-EXEC-OUTCOME-NO-OP]
def test_is_success_like_true_for_no_op() -> None:
    """NO_OP is a correct terminal state and satisfies goal gates."""
    assert OutcomeStatus.NO_OP.is_success_like is True


# [unit->REQ-EXEC-OUTCOME-STATUSES]
def test_is_success_like_false_for_failure() -> None:
    assert OutcomeStatus.FAILURE.is_success_like is False


# [unit->REQ-EXEC-OUTCOME-STATUSES]
def test_string_values_are_stable() -> None:
    """`StrEnum` value strings are part of the journal serialization
    contract — changing them would break old runs on disk.
    """
    assert OutcomeStatus.SUCCESS.value == "SUCCESS"
    assert OutcomeStatus.FAILURE.value == "FAILURE"
    assert OutcomeStatus.PARTIAL_SUCCESS.value == "PARTIAL_SUCCESS"
    assert OutcomeStatus.NO_OP.value == "NO_OP"


# ─── _fold_parallel_statuses (the AND-fold for parallel joins) ──────────


# [unit->REQ-EXEC-OUTCOME-STATUSES]
def test_fold_all_success_yields_success() -> None:
    assert _fold_parallel_statuses(
        [OutcomeStatus.SUCCESS, OutcomeStatus.SUCCESS, OutcomeStatus.SUCCESS]
    ) == OutcomeStatus.SUCCESS


# [unit->REQ-EXEC-OUTCOME-STATUSES]
def test_fold_any_failure_yields_failure() -> None:
    """A single FAILURE shorts the fold — matches upstream §6.9.2 intent."""
    assert _fold_parallel_statuses(
        [OutcomeStatus.SUCCESS, OutcomeStatus.FAILURE, OutcomeStatus.SUCCESS]
    ) == OutcomeStatus.FAILURE


# [unit->REQ-EXEC-OUTCOME-STATUSES]
def test_fold_partial_success_propagates_when_no_failure() -> None:
    """No FAILURE, at least one PARTIAL_SUCCESS → join is PARTIAL_SUCCESS."""
    assert _fold_parallel_statuses(
        [OutcomeStatus.SUCCESS, OutcomeStatus.PARTIAL_SUCCESS, OutcomeStatus.SUCCESS]
    ) == OutcomeStatus.PARTIAL_SUCCESS


# [unit->REQ-EXEC-OUTCOME-STATUSES]
def test_fold_failure_beats_partial_success() -> None:
    """Failure wins over partial — the AND-fold short-circuits on FAILURE."""
    assert _fold_parallel_statuses(
        [OutcomeStatus.FAILURE, OutcomeStatus.PARTIAL_SUCCESS]
    ) == OutcomeStatus.FAILURE


# [unit->REQ-EXEC-OUTCOME-NO-OP]
def test_fold_no_op_propagates_when_no_failure_or_partial() -> None:
    """No FAILURE/PARTIAL_SUCCESS, at least one NO_OP → join is NO_OP."""
    assert _fold_parallel_statuses(
        [OutcomeStatus.SUCCESS, OutcomeStatus.NO_OP, OutcomeStatus.SUCCESS]
    ) == OutcomeStatus.NO_OP


# [unit->REQ-EXEC-OUTCOME-STATUSES]
def test_fold_single_branch_returns_that_status() -> None:
    """Single-branch fold is the identity."""
    assert _fold_parallel_statuses([OutcomeStatus.SUCCESS]) == OutcomeStatus.SUCCESS
    assert _fold_parallel_statuses([OutcomeStatus.FAILURE]) == OutcomeStatus.FAILURE
    assert (
        _fold_parallel_statuses([OutcomeStatus.PARTIAL_SUCCESS])
        == OutcomeStatus.PARTIAL_SUCCESS
    )
    assert _fold_parallel_statuses([OutcomeStatus.NO_OP]) == OutcomeStatus.NO_OP


# [unit->REQ-EXEC-OUTCOME-STATUSES]
def test_fold_empty_input_raises() -> None:
    """An empty branch list is a programming error — silently returning
    SUCCESS would mask a real bug at the call site.
    """
    with pytest.raises(ValueError, match="empty branch list"):
        _fold_parallel_statuses([])
