"""Unit tests for the edge `condition=` boolean expression DSL.

REQ-EXEC-EDGE-CONDITION-DSL — parser + evaluator. Pure logic;
exercise without the engine class.
"""

from __future__ import annotations

import pytest

from attractor.agent import OutcomeStatus
from attractor.engine.conditions import (
    ConditionSyntaxError,
    evaluate_condition,
    parse_condition,
)

# ─── parser ─────────────────────────────────────────────────────────────


# [unit->REQ-EXEC-EDGE-CONDITION-DSL]
def test_empty_expression_parses_to_no_clauses() -> None:
    assert parse_condition("") == []
    assert parse_condition("   ") == []


# [unit->REQ-EXEC-EDGE-CONDITION-DSL]
def test_single_clause_equality() -> None:
    [clause] = parse_condition("outcome=SUCCESS")
    assert clause.key == "outcome"
    assert clause.op == "="
    assert clause.literal == "SUCCESS"


# [unit->REQ-EXEC-EDGE-CONDITION-DSL]
def test_single_clause_inequality() -> None:
    [clause] = parse_condition("outcome!=FAILURE")
    assert clause.op == "!="
    assert clause.literal == "FAILURE"


# [unit->REQ-EXEC-EDGE-CONDITION-DSL]
def test_multi_clause_and_combined() -> None:
    clauses = parse_condition(
        "outcome=SUCCESS && preferred_label!=draft && context.tests_passed=true"
    )
    assert len(clauses) == 3
    assert clauses[0].key == "outcome"
    assert clauses[1].key == "preferred_label"
    assert clauses[2].key == "context.tests_passed"


# [unit->REQ-EXEC-EDGE-CONDITION-DSL]
def test_quoted_literal_strips_quotes() -> None:
    [clause] = parse_condition('preferred_label="needs review"')
    assert clause.literal == "needs review"


# [unit->REQ-EXEC-EDGE-CONDITION-DSL]
def test_whitespace_around_operators_is_tolerated() -> None:
    [clause] = parse_condition("  outcome  =  SUCCESS  ")
    assert clause.key == "outcome"
    assert clause.literal == "SUCCESS"


# [unit->REQ-EXEC-EDGE-CONDITION-DSL]
def test_context_dot_path_parses() -> None:
    [clause] = parse_condition("context.run.tokens_in!=0")
    assert clause.key == "context.run.tokens_in"


# [unit->REQ-EXEC-EDGE-CONDITION-DSL]
def test_unknown_key_rejected() -> None:
    with pytest.raises(ConditionSyntaxError, match="invalid key"):
        parse_condition("outcomes=SUCCESS")  # typo
    with pytest.raises(ConditionSyntaxError, match="invalid key"):
        parse_condition("status=SUCCESS")
    with pytest.raises(ConditionSyntaxError, match="invalid key"):
        parse_condition("foo.bar=baz")  # context.* prefix required


# [unit->REQ-EXEC-EDGE-CONDITION-DSL]
def test_missing_operator_rejected() -> None:
    with pytest.raises(ConditionSyntaxError, match="no operator"):
        parse_condition("outcome SUCCESS")


# [unit->REQ-EXEC-EDGE-CONDITION-DSL]
def test_invalid_literal_rejected() -> None:
    with pytest.raises(ConditionSyntaxError, match="invalid literal"):
        parse_condition("outcome=hello world")  # bare literal with space


# ─── evaluator ──────────────────────────────────────────────────────────


# [unit->REQ-EXEC-EDGE-CONDITION-DSL]
def test_empty_condition_always_true() -> None:
    """Per upstream §10.5: empty condition = always-eligible edge."""
    assert evaluate_condition("", OutcomeStatus.SUCCESS, None) is True


# [unit->REQ-EXEC-EDGE-CONDITION-DSL]
def test_outcome_equality_matches_status_value() -> None:
    assert evaluate_condition("outcome=SUCCESS", OutcomeStatus.SUCCESS, None) is True
    assert evaluate_condition("outcome=FAILURE", OutcomeStatus.SUCCESS, None) is False
    assert (
        evaluate_condition("outcome=PARTIAL_SUCCESS", OutcomeStatus.PARTIAL_SUCCESS, None)
        is True
    )


# [unit->REQ-EXEC-OUTCOME-NO-OP]
def test_outcome_equality_matches_no_op_status_value() -> None:
    assert evaluate_condition("outcome=NO_OP", OutcomeStatus.NO_OP, None) is True


# [unit->REQ-EXEC-EDGE-CONDITION-DSL]
def test_outcome_uses_uppercase_not_lowercase() -> None:
    """Our `outcome` literal uses the StrEnum value (uppercase) — diverges
    from upstream's lowercase deliberately for consistency with `label=`.
    """
    assert evaluate_condition("outcome=success", OutcomeStatus.SUCCESS, None) is False
    assert evaluate_condition("outcome=SUCCESS", OutcomeStatus.SUCCESS, None) is True


# [unit->REQ-EXEC-EDGE-CONDITION-DSL]
def test_outcome_inequality() -> None:
    assert (
        evaluate_condition("outcome!=FAILURE", OutcomeStatus.SUCCESS, None) is True
    )
    assert (
        evaluate_condition("outcome!=SUCCESS", OutcomeStatus.SUCCESS, None) is False
    )


# [unit->REQ-EXEC-EDGE-CONDITION-DSL]
def test_preferred_label_match() -> None:
    assert (
        evaluate_condition(
            "preferred_label=env-error", OutcomeStatus.FAILURE, "env-error"
        )
        is True
    )
    assert (
        evaluate_condition(
            "preferred_label=env-error", OutcomeStatus.FAILURE, None
        )
        is False
    )


# [unit->REQ-EXEC-EDGE-CONDITION-DSL]
def test_preferred_label_empty_quoted_matches_none() -> None:
    """None preferred_label resolves to empty string; quoted-empty literal
    matches it. (Bare empty literal isn't valid syntax — use ``""``.)
    """
    assert (
        evaluate_condition('preferred_label=""', OutcomeStatus.SUCCESS, None)
        is True
    )


# [unit->REQ-EXEC-EDGE-CONDITION-DSL]
def test_context_keys_always_empty_in_v02() -> None:
    """context.* is reserved; v0.2 always resolves to empty string."""
    # `context.foo=bar` is False because resolved value is "" not "bar"
    assert (
        evaluate_condition("context.tests_passed=true", OutcomeStatus.SUCCESS, None)
        is False
    )
    # `context.foo!=bar` is True because "" != "bar"
    assert (
        evaluate_condition("context.tests_passed!=true", OutcomeStatus.SUCCESS, None)
        is True
    )


# [unit->REQ-EXEC-EDGE-CONDITION-DSL]
def test_and_combined_all_must_be_true() -> None:
    expr = "outcome=SUCCESS && preferred_label=ok"
    assert evaluate_condition(expr, OutcomeStatus.SUCCESS, "ok") is True
    assert evaluate_condition(expr, OutcomeStatus.SUCCESS, "not-ok") is False
    assert evaluate_condition(expr, OutcomeStatus.FAILURE, "ok") is False


# [unit->REQ-EXEC-EDGE-CONDITION-DSL]
def test_bad_syntax_returns_false_at_runtime() -> None:
    """Defensive: runtime sees a bad condition as 'never matches'.
    The validator should have flagged it at parse time.
    """
    assert evaluate_condition("not a clause", OutcomeStatus.SUCCESS, None) is False
