"""
test_dispatch_auto_inject.py

_auto_inject_affected_files 및 _auto_generate_goal_assertions 단위 테스트.
"""

import sys
from pathlib import Path
from unittest.mock import MagicMock, patch

# workspace sys.path 보정
_WORKSPACE = Path(__file__).parent.parent
sys.path.insert(0, str(_WORKSPACE))

from dispatch import (
    ALLOWED_COMMANDS,
    _auto_generate_goal_assertions,
    _auto_inject_affected_files,
)


# ===========================================================================
# _auto_inject_affected_files 테스트
# ===========================================================================


def test_auto_inject_extracts_backtick_tokens():
    """task_desc에 `FeatureGate` 포함 시 grep -rl로 파일 탐지 후 섹션 추가."""
    task_desc = "다음 클래스를 확인하세요: `FeatureGate` 관련 변경."

    mock_result = MagicMock()
    mock_result.stdout = "src/hooks/use-feature-access.ts\nsrc/components/gate.tsx\n"

    with patch("dispatch.subprocess.run", return_value=mock_result) as mock_run:
        result = _auto_inject_affected_files(task_desc, "/workspace")

    # subprocess.run이 호출됨 확인
    assert mock_run.called, "subprocess.run이 호출되어야 함"

    # affected_files 섹션이 추가됨 확인
    assert "## affected_files (auto-detected)" in result, (
        "## affected_files (auto-detected) 섹션이 추가되어야 함"
    )


def test_auto_inject_common_filter_skips():
    """`data`, `config`, `error` 같은 COMMON_FILTER 토큰만 있으면 subprocess.run 호출 없음."""
    task_desc = "다음을 처리하세요: `data` 와 `config` 그리고 `error` 를 확인합니다."

    with patch("dispatch.subprocess.run") as mock_run:
        result = _auto_inject_affected_files(task_desc, "/workspace")

    # subprocess.run 호출 없음 확인
    assert not mock_run.called, "COMMON_FILTER 토큰만 있을 때 subprocess.run이 호출되면 안 됨"

    # 원본과 동일 반환 확인
    assert result == task_desc, "COMMON_FILTER만 있을 때 원본 task_desc가 그대로 반환되어야 함"


def test_auto_inject_skips_existing_affected_files():
    """task_desc에 ## affected_files 섹션이 이미 있으면 원본 그대로 반환."""
    task_desc = (
        "## 작업 내용\n"
        "파일 수정\n\n"
        "## affected_files\n"
        "- dispatch.py\n"
        "- tests/test_dispatch.py\n"
    )

    with patch("dispatch.subprocess.run") as mock_run:
        result = _auto_inject_affected_files(task_desc, "/workspace")

    # subprocess.run 호출 없음 확인
    assert not mock_run.called, "이미 affected_files 섹션이 있으면 subprocess.run이 호출되면 안 됨"

    # 원본과 동일 반환 확인
    assert result == task_desc, "이미 섹션이 있을 때 원본 task_desc가 그대로 반환되어야 함"


def test_auto_inject_over_20_files_no_inject():
    """grep이 21개 이상 파일 반환 시 섹션 추가 안 함 + logger.warning 호출."""
    # 21개 파일 경로 생성
    many_files = "\n".join(f"/workspace/src/file_{i}.py" for i in range(21))
    task_desc = "클래스 `FeatureGate` 를 수정하세요."

    mock_result = MagicMock()
    mock_result.stdout = many_files + "\n"

    with patch("dispatch.subprocess.run", return_value=mock_result), \
         patch("dispatch.logger") as mock_logger:
        result = _auto_inject_affected_files(task_desc, "/workspace")

    # affected_files 섹션이 추가되지 않음 확인
    assert "## affected_files (auto-detected)" not in result, (
        "21개 이상 파일일 때 섹션이 추가되면 안 됨"
    )

    # logger.warning이 호출됨 확인
    assert mock_logger.warning.called, "21개 이상 파일 감지 시 logger.warning이 호출되어야 함"


# ===========================================================================
# _auto_generate_goal_assertions 테스트
# ===========================================================================


def test_auto_generate_goal_assertions_from_commands():
    """task_desc에 grep 명령이 백틱으로 감싸여 있으면 goal_assertions 섹션 추가."""
    task_desc = (
        "## 검증 시나리오\n"
        "다음 명령으로 확인하세요:\n"
        "`grep -c \"FeatureGate\" src/hooks/use-feature-access.ts`\n"
    )

    result = _auto_generate_goal_assertions(task_desc, "/workspace")

    # goal_assertions 섹션이 추가됨 확인
    assert "## goal_assertions (auto-generated)" in result, (
        "## goal_assertions (auto-generated) 섹션이 추가되어야 함"
    )

    # grep 명령이 포함됨 확인
    assert 'grep -c "FeatureGate" src/hooks/use-feature-access.ts' in result, (
        "grep 명령이 goal_assertions에 포함되어야 함"
    )


def test_auto_generate_skips_disallowed_commands():
    """`rm -rf /tmp/test` 같이 ALLOWED_COMMANDS에 없는 명령은 섹션에 추가 안 함."""
    task_desc = (
        "## 검증 시나리오\n"
        "다음 명령으로 정리하세요:\n"
        "`rm -rf /tmp/test`\n"
    )

    result = _auto_generate_goal_assertions(task_desc, "/workspace")

    # goal_assertions 섹션이 추가되지 않음 확인
    assert "## goal_assertions (auto-generated)" not in result, (
        "허용되지 않은 명령만 있을 때 goal_assertions 섹션이 추가되면 안 됨"
    )


def test_auto_generate_skips_existing_assertions():
    """task_desc에 ## goal_assertions 섹션이 이미 있으면 원본 그대로 반환."""
    task_desc = (
        "## 검증 시나리오\n"
        "`grep -c FeatureGate src/hooks.ts`\n\n"
        "## goal_assertions\n"
        "- 기존 검증 항목\n"
    )

    result = _auto_generate_goal_assertions(task_desc, "/workspace")

    # 원본과 동일 반환 확인
    assert result == task_desc, "이미 goal_assertions 섹션이 있을 때 원본 task_desc가 그대로 반환되어야 함"

    # 섹션이 두 번 추가되지 않음 확인
    assert result.count("## goal_assertions") == 1, (
        "goal_assertions 섹션이 중복 추가되면 안 됨"
    )


# ===========================================================================
# ALLOWED_COMMANDS 상수 검증
# ===========================================================================


def test_allowed_commands_contains_expected_values():
    """ALLOWED_COMMANDS에 최소한의 기대 커맨드가 포함되어 있어야 한다."""
    expected = {"grep", "curl", "pytest", "python3"}
    for cmd in expected:
        assert cmd in ALLOWED_COMMANDS, f"'{cmd}'이 ALLOWED_COMMANDS에 포함되어야 함"
