"""
stale 재발 방지 테스트
대상:
  - memory/task-timer.py  :: TaskTimer.cleanup_stale()
  - dispatch.py           :: _cleanup_reserved()
  - dashboard/server.py   :: DataLoader.get_team_stats() stale 카운팅
                             DataLoader.get_tasks_info() is_stale 플래그
"""

import importlib.util
import json
import sys
from datetime import datetime, timedelta
from pathlib import Path

import pytest

# ─── 경로 설정 ───────────────────────────────────────────────────────────────
WORKSPACE = Path("/home/jay/workspace")
sys.path.insert(0, str(WORKSPACE))


# ─── task-timer.py 동적 로드 (파일명에 하이픈이 포함되어 일반 import 불가) ────
def _load_task_timer_module():
    spec = importlib.util.spec_from_file_location(
        "task_timer", str(WORKSPACE / "memory" / "task-timer.py")
    )
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod


_task_timer_mod = _load_task_timer_module()
TaskTimer = _task_timer_mod.TaskTimer


# ════════════════════════════════════════════════════════════════════════════
# 1. TaskTimer.cleanup_stale() 테스트
# ════════════════════════════════════════════════════════════════════════════

def _make_timer(tmp_path: Path, initial_tasks: dict) -> "TaskTimer":
    """tmp_path 기반 격리된 TaskTimer 인스턴스 생성"""
    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": initial_tasks}, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )
    return TaskTimer(workspace_path=str(tmp_path))


def _past(hours: float = 0.0, minutes: float = 0.0) -> str:
    """현재 기준 hours시간 + minutes분 전의 ISO 타임스탬프 반환"""
    delta = timedelta(hours=hours, minutes=minutes)
    return (datetime.now() - delta).isoformat()


# ── 1-1. running 상태가 threshold 초과 시 stale 전환 ─────────────────────────

def test_cleanup_stale_running(tmp_path):
    """running 상태이고 start_time이 running_hours(기본 2h) 초과 → stale 전환"""
    tasks = {
        "task-1.1": {
            "task_id": "task-1.1",
            "status": "running",
            "start_time": _past(hours=3),  # 3시간 전 → 기본 2h 초과
            "team_id": "dev1-team",
        }
    }
    timer = _make_timer(tmp_path, tasks)
    cleaned = timer.cleanup_stale(running_hours=2.0, reserved_minutes=30.0)

    assert len(cleaned) == 1, "1개 task가 stale 전환되어야 함"
    assert cleaned[0]["task_id"] == "task-1.1"
    assert cleaned[0]["prev_status"] == "running"
    assert cleaned[0]["stale_reason"] == "timeout_running"

    # 파일에도 반영되었는지 확인
    saved = json.loads((tmp_path / "memory" / "task-timers.json").read_text())
    assert saved["tasks"]["task-1.1"]["status"] == "stale"
    assert saved["tasks"]["task-1.1"]["stale_reason"] == "timeout_running"
    assert "stale_at" in saved["tasks"]["task-1.1"]


# ── 1-2. reserved 상태가 threshold 초과 시 stale 전환 ────────────────────────

def test_cleanup_stale_reserved(tmp_path):
    """reserved 상태이고 reserved_at이 reserved_minutes(기본 30m) 초과 → stale 전환"""
    tasks = {
        "task-2.1": {
            "task_id": "task-2.1",
            "status": "reserved",
            "reserved_at": _past(minutes=45),  # 45분 전 → 기본 30m 초과
        }
    }
    timer = _make_timer(tmp_path, tasks)
    cleaned = timer.cleanup_stale(running_hours=2.0, reserved_minutes=30.0)

    assert len(cleaned) == 1
    assert cleaned[0]["task_id"] == "task-2.1"
    assert cleaned[0]["prev_status"] == "reserved"
    assert cleaned[0]["stale_reason"] == "timeout_reserved"

    saved = json.loads((tmp_path / "memory" / "task-timers.json").read_text())
    assert saved["tasks"]["task-2.1"]["status"] == "stale"
    assert saved["tasks"]["task-2.1"]["stale_reason"] == "timeout_reserved"


# ── 1-3. threshold 이내 running/reserved는 stale 전환 금지 ──────────────────

def test_cleanup_no_false_positive(tmp_path):
    """running 1h (< 2h) / reserved 10m (< 30m) → stale 전환 없음"""
    tasks = {
        "task-3.1": {
            "task_id": "task-3.1",
            "status": "running",
            "start_time": _past(hours=1),   # 1시간 전 → 2h 이내
            "team_id": "dev1-team",
        },
        "task-3.2": {
            "task_id": "task-3.2",
            "status": "reserved",
            "reserved_at": _past(minutes=10),  # 10분 전 → 30m 이내
        },
    }
    timer = _make_timer(tmp_path, tasks)
    cleaned = timer.cleanup_stale(running_hours=2.0, reserved_minutes=30.0)

    assert cleaned == [], "threshold 이내 task는 stale 전환되지 않아야 함"

    saved = json.loads((tmp_path / "memory" / "task-timers.json").read_text())
    assert saved["tasks"]["task-3.1"]["status"] == "running"
    assert saved["tasks"]["task-3.2"]["status"] == "reserved"


# ── 1-4. completed 상태는 변경 없음 ─────────────────────────────────────────

def test_cleanup_completed_untouched(tmp_path):
    """completed 작업은 cleanup_stale 후에도 그대로 completed"""
    start_old = _past(hours=5)
    tasks = {
        "task-4.1": {
            "task_id": "task-4.1",
            "status": "completed",
            "start_time": start_old,
            "end_time": _past(hours=4),
            "duration_seconds": 3600.0,
            "team_id": "dev1-team",
        }
    }
    timer = _make_timer(tmp_path, tasks)
    cleaned = timer.cleanup_stale(running_hours=2.0, reserved_minutes=30.0)

    assert cleaned == [], "completed task는 stale 전환 대상이 아님"

    saved = json.loads((tmp_path / "memory" / "task-timers.json").read_text())
    assert saved["tasks"]["task-4.1"]["status"] == "completed"


# ── 1-5. 커스텀 threshold 동작 확인 ─────────────────────────────────────────

def test_cleanup_custom_thresholds(tmp_path):
    """running_hours=0.1(6분), reserved_minutes=5 커스텀 threshold 동작 확인"""
    tasks = {
        # 10분 전 running → 0.1h(6분) 초과 → stale 대상
        "task-5.1": {
            "task_id": "task-5.1",
            "status": "running",
            "start_time": _past(minutes=10),
            "team_id": "dev1-team",
        },
        # 3분 전 running → 0.1h(6분) 이내 → 정상
        "task-5.2": {
            "task_id": "task-5.2",
            "status": "running",
            "start_time": _past(minutes=3),
            "team_id": "dev2-team",
        },
        # 7분 전 reserved → 5분 초과 → stale 대상
        "task-5.3": {
            "task_id": "task-5.3",
            "status": "reserved",
            "reserved_at": _past(minutes=7),
        },
        # 2분 전 reserved → 5분 이내 → 정상
        "task-5.4": {
            "task_id": "task-5.4",
            "status": "reserved",
            "reserved_at": _past(minutes=2),
        },
    }
    timer = _make_timer(tmp_path, tasks)
    cleaned = timer.cleanup_stale(running_hours=0.1, reserved_minutes=5.0)

    cleaned_ids = {c["task_id"] for c in cleaned}
    assert "task-5.1" in cleaned_ids, "10분 running은 stale 대상"
    assert "task-5.3" in cleaned_ids, "7분 reserved는 stale 대상"
    assert "task-5.2" not in cleaned_ids, "3분 running은 stale 대상 아님"
    assert "task-5.4" not in cleaned_ids, "2분 reserved는 stale 대상 아님"

    saved = json.loads((tmp_path / "memory" / "task-timers.json").read_text())
    assert saved["tasks"]["task-5.1"]["status"] == "stale"
    assert saved["tasks"]["task-5.3"]["status"] == "stale"
    assert saved["tasks"]["task-5.2"]["status"] == "running"
    assert saved["tasks"]["task-5.4"]["status"] == "reserved"


# ════════════════════════════════════════════════════════════════════════════
# 2. dispatch._cleanup_reserved() 테스트
# ════════════════════════════════════════════════════════════════════════════

def _write_timer_file(timer_file: Path, tasks: dict) -> None:
    timer_file.parent.mkdir(parents=True, exist_ok=True)
    timer_file.write_text(
        json.dumps({"tasks": tasks}, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )


def _read_timer_file(timer_file: Path) -> dict:
    return json.loads(timer_file.read_text(encoding="utf-8"))


# ── 2-1. reserved entry 정상 삭제 ────────────────────────────────────────────

def test_cleanup_reserved_removes_entry(tmp_path, monkeypatch):
    """reserved 상태인 entry가 _cleanup_reserved() 호출 후 삭제되는지"""
    import dispatch  # type: ignore

    # dispatch 모듈 내 WORKSPACE를 tmp_path로 교체
    monkeypatch.setattr(dispatch, "WORKSPACE", tmp_path)

    timer_file = tmp_path / "memory" / "task-timers.json"
    _write_timer_file(
        timer_file,
        {
            "task-10.1": {
                "status": "reserved",
                "reserved_at": datetime.now().isoformat(),
            }
        },
    )

    dispatch._cleanup_reserved("task-10.1")

    saved = _read_timer_file(timer_file)
    assert "task-10.1" not in saved["tasks"], "reserved entry는 삭제되어야 함"


# ── 2-2. running/completed 상태는 건너뜀 ─────────────────────────────────────

def test_cleanup_reserved_skips_non_reserved(tmp_path, monkeypatch):
    """running 또는 completed 상태인 task는 _cleanup_reserved()가 건드리지 않음"""
    import dispatch  # type: ignore

    monkeypatch.setattr(dispatch, "WORKSPACE", tmp_path)

    timer_file = tmp_path / "memory" / "task-timers.json"
    _write_timer_file(
        timer_file,
        {
            "task-11.1": {
                "status": "running",
                "start_time": datetime.now().isoformat(),
                "team_id": "dev1-team",
            },
            "task-11.2": {
                "status": "completed",
                "start_time": _past(hours=1),
                "end_time": datetime.now().isoformat(),
                "duration_seconds": 3600.0,
            },
        },
    )

    # running task에 대해 호출
    dispatch._cleanup_reserved("task-11.1")
    saved = _read_timer_file(timer_file)
    assert saved["tasks"]["task-11.1"]["status"] == "running", "running은 삭제되지 않아야 함"

    # completed task에 대해 호출
    dispatch._cleanup_reserved("task-11.2")
    saved = _read_timer_file(timer_file)
    assert saved["tasks"]["task-11.2"]["status"] == "completed", "completed는 삭제되지 않아야 함"


# ════════════════════════════════════════════════════════════════════════════
# 3. Dashboard DataLoader stale 카운팅 테스트
# ════════════════════════════════════════════════════════════════════════════

def _make_data_loader(tmp_path: Path, tasks: dict, org_data: dict = None) -> "DataLoader":
    """tmp_path 기반 격리된 DataLoader 인스턴스 생성"""
    from dashboard.server import DataLoader

    memory_dir = tmp_path / "memory"
    memory_dir.mkdir(parents=True, exist_ok=True)

    # task-timers.json 작성
    task_file = memory_dir / "task-timers.json"
    task_file.write_text(
        json.dumps({"tasks": tasks}, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )

    # organization-structure.json 작성 (org_data가 있는 경우)
    if org_data:
        org_file = memory_dir / "organization-structure.json"
        org_file.write_text(
            json.dumps(org_data, ensure_ascii=False, indent=2),
            encoding="utf-8",
        )

    loader = DataLoader(workspace_dir=tmp_path)
    loader.load_tasks()
    if org_data:
        loader.load_organization()
    return loader


# ── 3-1. running 2시간 초과가 stale_tasks로 카운트 ───────────────────────────

def test_get_team_stats_stale_counting(tmp_path):
    """running 상태이고 2시간 초과된 task는 stale_tasks로 카운트되어야 함"""
    tasks = {
        # 3시간 전 running → stale_tasks 카운트 대상
        "task-20.1": {
            "task_id": "task-20.1",
            "status": "running",
            "start_time": _past(hours=3),
            "team_id": "dev1-team",
        },
        # 1시간 전 running → running_tasks 카운트 대상
        "task-20.2": {
            "task_id": "task-20.2",
            "status": "running",
            "start_time": _past(hours=1),
            "team_id": "dev2-team",
        },
        # 이미 stale 상태 → stale_tasks 카운트 대상
        "task-20.3": {
            "task_id": "task-20.3",
            "status": "stale",
            "start_time": _past(hours=5),
            "stale_reason": "timeout_running",
            "stale_at": _past(hours=3),
            "team_id": "dev1-team",
        },
        # completed → completed_tasks 카운트 대상
        "task-20.4": {
            "task_id": "task-20.4",
            "status": "completed",
            "start_time": _past(hours=2),
            "end_time": _past(hours=1),
            "duration_seconds": 3600.0,
            "team_id": "dev1-team",
        },
    }

    # get_team_stats()는 org_data가 있어야 작업 통계를 계산함
    # 최소 구조의 org_data 주입 (팀원 없는 빈 조직)
    minimal_org = {"structure": {"columns": {"teams": []}, "rows": {"centers": []}}}
    loader = _make_data_loader(tmp_path, tasks, org_data=minimal_org)
    stats = loader.get_team_stats()

    assert stats["stale_tasks"] == 2, (
        f"stale_tasks는 2여야 함 (3h running + 기존 stale), 실제: {stats['stale_tasks']}"
    )
    assert stats["running_tasks"] == 1, (
        f"running_tasks는 1이어야 함 (1h running), 실제: {stats['running_tasks']}"
    )
    assert stats["completed_tasks"] == 1, (
        f"completed_tasks는 1이어야 함, 실제: {stats['completed_tasks']}"
    )
    assert stats["total_tasks"] == 4, (
        f"total_tasks는 4여야 함, 실제: {stats['total_tasks']}"
    )


def test_get_team_stats_reserved_stale_counting(tmp_path):
    """reserved 상태이고 30분 초과된 task도 stale_tasks로 카운트되어야 함"""
    tasks = {
        # 45분 전 reserved → stale_tasks 카운트 대상
        "task-21.1": {
            "task_id": "task-21.1",
            "status": "reserved",
            "reserved_at": _past(minutes=45),
        },
        # 10분 전 reserved → stale 대상 아님 (카운트 안 됨)
        "task-21.2": {
            "task_id": "task-21.2",
            "status": "reserved",
            "reserved_at": _past(minutes=10),
        },
    }

    minimal_org = {"structure": {"columns": {"teams": []}, "rows": {"centers": []}}}
    loader = _make_data_loader(tmp_path, tasks, org_data=minimal_org)
    stats = loader.get_team_stats()

    assert stats["stale_tasks"] == 1, (
        f"stale_tasks는 1이어야 함 (45분 reserved), 실제: {stats['stale_tasks']}"
    )


# ── 3-2. is_stale 플래그 확인 ────────────────────────────────────────────────

def test_get_tasks_info_is_stale_flag(tmp_path):
    """get_tasks_info()가 is_stale 플래그를 올바르게 설정하는지 확인"""
    tasks = {
        # 3시간 전 running → is_stale=True
        "task-30.1": {
            "task_id": "task-30.1",
            "status": "running",
            "start_time": _past(hours=3),
            "team_id": "dev1-team",
        },
        # 1시간 전 running → is_stale=False
        "task-30.2": {
            "task_id": "task-30.2",
            "status": "running",
            "start_time": _past(hours=1),
            "team_id": "dev2-team",
        },
        # status=stale → is_stale=True
        "task-30.3": {
            "task_id": "task-30.3",
            "status": "stale",
            "start_time": _past(hours=5),
            "stale_reason": "timeout_running",
            "stale_at": _past(hours=3),
            "team_id": "dev1-team",
        },
        # 45분 전 reserved → is_stale=True (STALE_MEMBER_WORKING_SECONDS=1800 초과)
        "task-30.4": {
            "task_id": "task-30.4",
            "status": "reserved",
            "reserved_at": _past(minutes=45),
        },
        # 10분 전 reserved → is_stale=False
        "task-30.5": {
            "task_id": "task-30.5",
            "status": "reserved",
            "reserved_at": _past(minutes=10),
        },
        # completed → is_stale=False
        "task-30.6": {
            "task_id": "task-30.6",
            "status": "completed",
            "start_time": _past(hours=3),
            "end_time": _past(hours=2),
            "duration_seconds": 3600.0,
            "team_id": "dev1-team",
        },
    }

    loader = _make_data_loader(tmp_path, tasks)
    tasks_info = loader.get_tasks_info()

    # task_id → is_stale 매핑 추출
    stale_map = {t["task_id"]: t["is_stale"] for t in tasks_info}

    assert stale_map["task-30.1"] is True, "3h running은 is_stale=True"
    assert stale_map["task-30.2"] is False, "1h running은 is_stale=False"
    assert stale_map["task-30.3"] is True, "status=stale은 is_stale=True"
    assert stale_map["task-30.4"] is True, "45min reserved는 is_stale=True"
    assert stale_map["task-30.5"] is False, "10min reserved는 is_stale=False"
    assert stale_map["task-30.6"] is False, "completed는 is_stale=False"
