"""test_affected_files_overlap.py — affected_files 겹침 감지 테스트 (task-1905)"""
import json
import sys
from pathlib import Path
from unittest.mock import patch, MagicMock
import pytest

# dispatch 모듈 import
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import dispatch as mod  # type: ignore[import-not-found]


# ---------------------------------------------------------------------------
# 헬퍼: task-timers.json 생성
# ---------------------------------------------------------------------------

def _make_timer_json(tmp_path: Path, tasks: dict) -> Path:
    """tmp_path/memory/task-timers.json 을 생성하고 경로를 반환한다."""
    memory_dir = tmp_path / "memory"
    memory_dir.mkdir(parents=True, exist_ok=True)
    timer_file = memory_dir / "task-timers.json"
    timer_file.write_text(json.dumps({"tasks": tasks}), encoding="utf-8")
    return timer_file


# ---------------------------------------------------------------------------
# _check_affected_files_overlap 테스트
# ---------------------------------------------------------------------------

class TestCheckAffectedFilesOverlap:
    """_check_affected_files_overlap() 동작 검증"""

    # 1. 겹침 감지 케이스
    def test_overlap_detected(self, tmp_path, monkeypatch):
        """running 상태 task와 affected_files가 겹칠 때 경고 메시지를 반환한다."""
        _make_timer_json(tmp_path, {
            "task-9001": {
                "status": "running",
                "affected_files": ["src/foo.py", "src/bar.py"],
            }
        })
        monkeypatch.setattr(mod, "WORKSPACE", tmp_path)

        result = mod._check_affected_files_overlap(["src/foo.py", "src/baz.py"], "task-9999")

        assert len(result) == 1
        assert "task-9001" in result[0]
        assert "src/foo.py" in result[0]
        assert "[파일 충돌 경고]" in result[0]

    # 2. 겹침 없음 케이스
    def test_no_overlap(self, tmp_path, monkeypatch):
        """affected_files가 다른 running task와 겹치지 않으면 빈 리스트를 반환한다."""
        _make_timer_json(tmp_path, {
            "task-9001": {
                "status": "running",
                "affected_files": ["src/other.py"],
            }
        })
        monkeypatch.setattr(mod, "WORKSPACE", tmp_path)

        result = mod._check_affected_files_overlap(["src/foo.py", "src/baz.py"], "task-9999")

        assert result == []

    # 3. 빈 affected_files 케이스
    def test_empty_affected_files(self, tmp_path, monkeypatch):
        """affected_files=[] 이면 빈 리스트를 반환한다."""
        _make_timer_json(tmp_path, {
            "task-9001": {
                "status": "running",
                "affected_files": ["src/foo.py"],
            }
        })
        monkeypatch.setattr(mod, "WORKSPACE", tmp_path)

        result = mod._check_affected_files_overlap([], "task-9999")

        assert result == []

    # 4. 자기 자신 제외 케이스
    def test_self_excluded(self, tmp_path, monkeypatch):
        """current_task_id와 동일한 task는 겹침 검사에서 제외된다."""
        _make_timer_json(tmp_path, {
            "task-9999": {
                "status": "running",
                "affected_files": ["src/foo.py"],
            }
        })
        monkeypatch.setattr(mod, "WORKSPACE", tmp_path)

        result = mod._check_affected_files_overlap(["src/foo.py"], "task-9999")

        assert result == []

    # 5. running이 아닌 task 제외
    def test_non_running_tasks_excluded(self, tmp_path, monkeypatch):
        """status가 'done' 또는 'reserved'인 task는 겹침 검사에서 무시된다."""
        _make_timer_json(tmp_path, {
            "task-done": {
                "status": "done",
                "affected_files": ["src/foo.py"],
            },
            "task-reserved": {
                "status": "reserved",
                "affected_files": ["src/foo.py"],
            },
        })
        monkeypatch.setattr(mod, "WORKSPACE", tmp_path)

        result = mod._check_affected_files_overlap(["src/foo.py"], "task-9999")

        assert result == []

    # 6. timer 파일 없음
    def test_timer_file_not_exists(self, tmp_path, monkeypatch):
        """task-timers.json 파일이 없을 때 빈 리스트를 반환한다."""
        monkeypatch.setattr(mod, "WORKSPACE", tmp_path)
        # memory 디렉토리 자체도 없음 → 파일 없음

        result = mod._check_affected_files_overlap(["src/foo.py"], "task-9999")

        assert result == []

    # 추가: 여러 task와 겹침 — 경고 메시지가 여러 개 반환되어야 함
    def test_multiple_overlapping_tasks(self, tmp_path, monkeypatch):
        """여러 running task와 겹칠 때 각각 경고 메시지가 반환된다."""
        _make_timer_json(tmp_path, {
            "task-A": {
                "status": "running",
                "affected_files": ["src/foo.py"],
            },
            "task-B": {
                "status": "running",
                "affected_files": ["src/bar.py"],
            },
        })
        monkeypatch.setattr(mod, "WORKSPACE", tmp_path)

        result = mod._check_affected_files_overlap(["src/foo.py", "src/bar.py"], "task-9999")

        assert len(result) == 2
        task_ids_in_warnings = [w for w in result]
        assert any("task-A" in w for w in task_ids_in_warnings)
        assert any("task-B" in w for w in task_ids_in_warnings)

    # 추가: 손상된 JSON 파일 → 빈 리스트 반환 (예외 미발생)
    def test_corrupted_timer_file(self, tmp_path, monkeypatch):
        """손상된 task-timers.json 파일이 있을 때 예외 없이 빈 리스트를 반환한다."""
        memory_dir = tmp_path / "memory"
        memory_dir.mkdir(parents=True, exist_ok=True)
        (memory_dir / "task-timers.json").write_text("NOT VALID JSON", encoding="utf-8")
        monkeypatch.setattr(mod, "WORKSPACE", tmp_path)

        result = mod._check_affected_files_overlap(["src/foo.py"], "task-9999")

        assert result == []


# ---------------------------------------------------------------------------
# _send_overlap_telegram_warning 테스트
# ---------------------------------------------------------------------------

_send_fn = getattr(mod, "_send_overlap_telegram_warning", None)
_skip_if_absent = pytest.mark.skipif(
    _send_fn is None,
    reason="_send_overlap_telegram_warning 함수가 dispatch.py에 아직 없음",
)


def _call_send_fn(warnings: list) -> None:  # type: ignore[type-arg]
    """_send_fn을 안전하게 호출하는 래퍼 (pyright optional-call 방지)."""
    fn = getattr(mod, "_send_overlap_telegram_warning", None)
    assert fn is not None, "_send_overlap_telegram_warning이 dispatch.py에 존재해야 합니다"
    fn(warnings)


class TestSendOverlapTelegramWarning:
    """_send_overlap_telegram_warning() 동작 검증 (함수가 없으면 skip)"""

    # 7. Telegram 경고 발송 (mock)
    @_skip_if_absent
    def test_sends_telegram_when_warnings_and_token(self, monkeypatch):
        """warnings가 있고 ANU_BOT_TOKEN이 설정되어 있으면 urllib.request.urlopen을 호출한다."""
        monkeypatch.setenv("ANU_BOT_TOKEN", "fake-token-12345")

        mock_urlopen = MagicMock()
        with patch("urllib.request.urlopen", mock_urlopen):
            _call_send_fn(["[파일 충돌 경고] task-A(running)와 파일 겹침: src/foo.py"])

        mock_urlopen.assert_called_once()

    # 8. Telegram 토큰 없음 — 발송 스킵
    @_skip_if_absent
    def test_skips_when_no_token(self, monkeypatch):
        """ANU_BOT_TOKEN 미설정 시 urlopen을 호출하지 않는다."""
        monkeypatch.delenv("ANU_BOT_TOKEN", raising=False)

        mock_urlopen = MagicMock()
        with patch("urllib.request.urlopen", mock_urlopen):
            _call_send_fn(["[파일 충돌 경고] task-A(running)와 파일 겹침: src/foo.py"])

        mock_urlopen.assert_not_called()

    # 추가: warnings가 비어있으면 urlopen을 호출하지 않는다
    @_skip_if_absent
    def test_skips_when_empty_warnings(self, monkeypatch):
        """warnings가 비어있으면 urlopen을 호출하지 않는다."""
        monkeypatch.setenv("ANU_BOT_TOKEN", "fake-token-12345")

        mock_urlopen = MagicMock()
        with patch("urllib.request.urlopen", mock_urlopen):
            _call_send_fn([])

        mock_urlopen.assert_not_called()

    # 추가: urlopen 예외 발생해도 함수가 예외를 전파하지 않는다
    @_skip_if_absent
    def test_no_exception_on_urlopen_failure(self, monkeypatch):
        """urllib.request.urlopen이 예외를 던져도 _send_overlap_telegram_warning은 예외를 전파하지 않는다."""
        monkeypatch.setenv("ANU_BOT_TOKEN", "fake-token-12345")

        with patch("urllib.request.urlopen", side_effect=Exception("network error")):
            # 예외가 발생하지 않아야 함
            _call_send_fn(["[파일 충돌 경고] task-A(running)와 파일 겹침: src/foo.py"])
