"""task-2709 회귀 테스트: allowed_resources cross-key paths normalization.

배경
----
task-2709는 ``allowed_resources``의 schema mismatch(task-2706 ``paths`` key ↔
task-2707/2708 ``expected_files`` key)를 양 layer cross-key normalization으로
고친다.

- ``dispatch._normalize_allowed_resources(ar)``: ``ar["paths"]``가 비어있거나
  없으면 ``expected_files`` ∪ ``allowed_existing_file_edits``의 순서보존 union으로
  ``paths``를 채운다. 기존 ``paths``가 채워져 있으면 그대로 유지.
- ``dispatch._parse_allowed_resources(task_desc)``: task md의 ```yaml``` 블록에서
  allowed_resources를 파싱하며, 위 정규화를 적용해 반환.

RS-1 ~ RS-5 회귀 케이스를 검증한다.

import 실패 시 skip하지 않고 그대로 에러나게 둔다(구현이 끝나면 통과해야 함).
"""

import sys

sys.path.insert(0, "/home/jay/workspace")

from dispatch import _normalize_allowed_resources, _parse_allowed_resources


# ---------------------------------------------------------------------------
# RS-1 backward compatibility: paths가 직접 지정되면 그대로 유지
# ---------------------------------------------------------------------------
def test_rs1_backward_compat_paths_preserved():
    """RS-1: paths가 채워져 있으면 expected_files로 덮어쓰지 않고 유지한다."""
    result = _normalize_allowed_resources(
        {"paths": ["a.py", "b.py"], "expected_files": ["c.py"]}
    )
    assert result["paths"] == ["a.py", "b.py"]
    # expected_files가 paths를 덮어쓰지 않았음을 명시적으로 확인
    assert "c.py" not in result["paths"]


def test_rs1_input_dict_not_mutated():
    """RS-1: 입력 dict는 원본 불변(side-effect 없음)이어야 한다."""
    original = {"expected_files": ["x.py", "y.py"]}
    snapshot = dict(original)
    _normalize_allowed_resources(original)
    # 원본 dict에 paths 키가 추가되거나 내용이 바뀌지 않아야 함
    assert original == snapshot
    assert "paths" not in original


# ---------------------------------------------------------------------------
# RS-2 forward compatibility: paths 없음 → expected_files ∪ allowed_existing_file_edits
# ---------------------------------------------------------------------------
def test_rs2_forward_compat_union():
    """RS-2: paths가 없으면 expected_files ∪ allowed_existing_file_edits union으로 채운다.

    순서보존 + 중복제거를 확인한다(x.py가 양쪽에 있으나 한 번만).
    """
    result = _normalize_allowed_resources(
        {
            "expected_files": ["x.py", "y.py"],
            "allowed_existing_file_edits": ["x.py", "z.py"],
        }
    )
    assert result["paths"] == ["x.py", "y.py", "z.py"]


def test_rs2_parse_allowed_resources_union():
    """RS-2: _parse_allowed_resources도 union normalization을 적용한다.

    expected_files/allowed_existing_file_edits만 있는 yaml 블록을 파싱하면
    반환 dict의 paths가 union으로 채워져야 한다.
    """
    task_desc = (
        "# task body\n"
        "\n"
        "```yaml\n"
        "allowed_resources:\n"
        "  expected_files:\n"
        "    - x.py\n"
        "    - y.py\n"
        "  allowed_existing_file_edits:\n"
        "    - x.py\n"
        "    - z.py\n"
        "```\n"
    )
    parsed = _parse_allowed_resources(task_desc)
    assert parsed is not None
    assert parsed["paths"] == ["x.py", "y.py", "z.py"]


# ---------------------------------------------------------------------------
# RS-3 task-2708 replay: paths 키 자체가 없을 때
# ---------------------------------------------------------------------------
def test_rs3_task2708_replay_no_paths_key():
    """RS-3: paths 키가 아예 없는 dict도 union으로 채워진다(task-2708 재현)."""
    result = _normalize_allowed_resources(
        {
            "expected_files": ["dispatch/__init__.py", "scripts/task-scope-guard.sh"],
            "allowed_existing_file_edits": ["dispatch/__init__.py"],
        }
    )
    assert result["paths"] == [
        "dispatch/__init__.py",
        "scripts/task-scope-guard.sh",
    ]


# ---------------------------------------------------------------------------
# RS-4 빈 union: expected_files / allowed_existing_file_edits 양쪽 다 없음
# ---------------------------------------------------------------------------
def test_rs4_empty_union_no_paths_added():
    """RS-4: union 대상이 없으면 paths가 추가되지 않거나 falsy로 유지된다.

    forbidden_paths는 그대로 보존되어야 한다.
    """
    result = _normalize_allowed_resources({"forbidden_paths": ["secret.py"]})
    # paths 키가 없거나 falsy 여야 함
    assert not result.get("paths")
    # forbidden_paths 보존
    assert result["forbidden_paths"] == ["secret.py"]


# ---------------------------------------------------------------------------
# RS-5 mixed schema: paths 우선
# ---------------------------------------------------------------------------
def test_rs5_mixed_schema_paths_priority():
    """RS-5: paths가 비어있지 않으면(union 미적용) paths를 우선한다."""
    result = _normalize_allowed_resources(
        {"paths": ["main.py"], "expected_files": ["extra.py"]}
    )
    assert result["paths"] == ["main.py"]
    assert "extra.py" not in result["paths"]


def test_rs5_empty_paths_list_triggers_union():
    """RS-5: 빈 리스트 paths는 falsy이므로 union이 적용된다."""
    result = _normalize_allowed_resources(
        {"paths": [], "expected_files": ["e.py"]}
    )
    assert result["paths"] == ["e.py"]
