"""
tests for canary-test.py

[task-794.1] P6-1 canary 테스트 스크립트 검증
"""

import importlib.util
import json
import sys
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock, patch

import pytest

# 대시 포함 파일명 import
_spec = importlib.util.spec_from_file_location(
    "canary_test",
    Path(__file__).parent.parent / "canary-test.py",
)
assert _spec is not None and _spec.loader is not None
ct: Any = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(ct)  # type: ignore[union-attr]


class TestSaveCanaryStatus:
    """save_canary_status 함수 테스트"""

    def test_saves_json_with_correct_fields(self, tmp_path: Path) -> None:
        """결과가 올바른 필드로 JSON에 저장된다."""
        results = [
            {"test": "test1", "passed": True, "detail": "ok"},
            {"test": "test2", "passed": False, "detail": "fail reason"},
        ]
        with patch.object(ct, "CANARY_STATUS_JSON", tmp_path / "canary-status.json"):
            with patch.object(ct, "MEMORY_DIR", tmp_path):
                ct.save_canary_status(results, all_passed=False)

        saved = json.loads((tmp_path / "canary-status.json").read_text())
        assert saved["all_passed"] is False
        assert saved["pass_count"] == 1
        assert saved["fail_count"] == 1
        assert len(saved["results"]) == 2
        assert "last_run" in saved

    def test_all_passed_true_when_no_failures(self, tmp_path: Path) -> None:
        """모든 테스트 통과 시 all_passed=True"""
        results = [
            {"test": "t1", "passed": True, "detail": "ok"},
        ]
        with patch.object(ct, "CANARY_STATUS_JSON", tmp_path / "canary-status.json"):
            with patch.object(ct, "MEMORY_DIR", tmp_path):
                ct.save_canary_status(results, all_passed=True)

        saved = json.loads((tmp_path / "canary-status.json").read_text())
        assert saved["all_passed"] is True
        assert saved["fail_count"] == 0


class TestWriteLog:
    """write_log 함수 테스트"""

    def test_creates_log_file(self, tmp_path: Path) -> None:
        """로그 파일이 없으면 생성한다."""
        log_path = tmp_path / "canary-test.log"
        with patch.object(ct, "CANARY_LOG", log_path):
            with patch.object(ct, "LOGS_DIR", tmp_path):
                ct.write_log("[TEST] test message")

        assert log_path.exists()
        assert "[TEST] test message" in log_path.read_text()

    def test_trims_to_max_lines(self, tmp_path: Path) -> None:
        """LOG_KEEP_LINES 초과 시 마지막 N줄만 유지한다."""
        log_path = tmp_path / "canary-test.log"
        with patch.object(ct, "CANARY_LOG", log_path):
            with patch.object(ct, "LOGS_DIR", tmp_path):
                with patch.object(ct, "LOG_KEEP_LINES", 5):
                    for i in range(10):
                        ct.write_log(f"line {i}")

        lines = log_path.read_text().splitlines()
        assert len(lines) <= 5


class TestTaskTimersJsonRw:
    """test_task_timers_json_rw 함수 테스트"""

    def test_pass_when_file_exists_and_writable(self, tmp_path: Path) -> None:
        """task-timers.json이 존재하고 쓰기 가능하면 PASS"""
        timers_path = tmp_path / "task-timers.json"
        timers_path.write_text(json.dumps({"tasks": {}}))

        with patch.object(ct, "TASK_TIMERS_JSON", timers_path):
            passed, detail = ct.test_task_timers_json_rw()

        assert passed is True
        assert "정상" in detail

    def test_fail_when_file_missing(self, tmp_path: Path) -> None:
        """파일이 없으면 FAIL"""
        missing = tmp_path / "missing.json"
        with patch.object(ct, "TASK_TIMERS_JSON", missing):
            passed, detail = ct.test_task_timers_json_rw()

        assert passed is False


class TestEventsDirWritable:
    """test_events_dir_writable 함수 테스트"""

    def test_pass_when_dir_writable(self, tmp_path: Path) -> None:
        """쓰기 가능한 디렉토리면 PASS"""
        with patch.object(ct, "EVENTS_DIR", tmp_path / "events"):
            passed, detail = ct.test_events_dir_writable()

        assert passed is True

    def test_pass_creates_dir_if_missing(self, tmp_path: Path) -> None:
        """디렉토리가 없어도 자동 생성 후 PASS"""
        events_dir = tmp_path / "nonexistent" / "events"
        with patch.object(ct, "EVENTS_DIR", events_dir):
            passed, detail = ct.test_events_dir_writable()

        assert passed is True
        assert events_dir.exists()


class TestStaleRunningTasks:
    """test_stale_running_tasks 함수 테스트"""

    def test_returns_pass_on_no_tasks(self) -> None:
        """실행 중인 task가 없으면 PASS"""
        mock_result = MagicMock()
        mock_result.returncode = 0
        mock_result.stdout = json.dumps({"total": 0, "tasks": []})
        mock_result.stderr = ""

        with patch("subprocess.run", return_value=mock_result):
            passed, detail = ct.test_stale_running_tasks()

        assert passed is True

    def test_detects_stale_task(self) -> None:
        """24시간 이상 running인 작업이 있으면 stale로 감지 (PASS, 감지 성공)"""
        old_time = (datetime.now() - timedelta(hours=25)).isoformat()
        mock_result = MagicMock()
        mock_result.returncode = 0
        mock_result.stdout = json.dumps({"total": 1, "tasks": [{"task_id": "task-9999.1", "start_time": old_time}]})
        mock_result.stderr = ""

        with patch("subprocess.run", return_value=mock_result):
            passed, detail = ct.test_stale_running_tasks()

        assert passed is True
        assert "stale" in detail

    def test_fail_on_timer_error(self) -> None:
        """task-timer.py 오류 시 FAIL"""
        mock_result = MagicMock()
        mock_result.returncode = 1
        mock_result.stdout = ""
        mock_result.stderr = "error message"

        with patch("subprocess.run", return_value=mock_result):
            passed, detail = ct.test_stale_running_tasks()

        assert passed is False
