"""Tests for lint-workspace.py - TDD approach.

tmp_path를 사용하여 격리된 테스트. 각 규칙 위반 감지, 심각도 분류,
종료 코드를 검증합니다.
"""

import os
import sys
import time
from pathlib import Path

import pytest

sys.path.insert(0, str(Path(__file__).parent.parent))

import importlib.util

_spec = importlib.util.spec_from_file_location(
    "lint_workspace", Path(__file__).parent.parent / "lint-workspace.py"
)
assert _spec and _spec.loader
_mod = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_mod)  # type: ignore[union-attr]
lw = _mod


def _ws(tmp_path: Path) -> Path:
    (tmp_path / "teams").mkdir()
    (tmp_path / "tmp").mkdir()
    (tmp_path / "memory" / "events").mkdir(parents=True)
    (tmp_path / "scripts").mkdir()
    (tmp_path / "workers").mkdir()
    return tmp_path


def _old(path: Path, days: int) -> Path:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text("data")
    t = time.time() - days * 86400
    os.utime(path, (t, t))
    return path


# ---------------------------------------------------------------------------
# Rule 1 – ERROR: 루트에 불필요한 파일
# ---------------------------------------------------------------------------

def test_root_allowed_files_no_error(tmp_path: Path) -> None:
    ws = _ws(tmp_path)
    (ws / "CLAUDE.md").write_text("ok")
    (ws / ".env").write_text("ok")
    (ws / "dispatch.py").write_text("ok")
    assert lw.check_root_unwanted_files(ws) == []


def test_root_unknown_file_error(tmp_path: Path) -> None:
    ws = _ws(tmp_path)
    (ws / "random_notes.txt").write_text("oops")
    v = lw.check_root_unwanted_files(ws)
    assert len(v) == 1 and v[0][0] == "ERROR" and "random_notes.txt" in v[0][1]


def test_root_multiple_unwanted(tmp_path: Path) -> None:
    ws = _ws(tmp_path)
    (ws / "foo.txt").write_text("x")
    (ws / "bar.md").write_text("x")
    v = lw.check_root_unwanted_files(ws)
    assert len(v) == 2 and all(s == "ERROR" for s, _ in v)


# ---------------------------------------------------------------------------
# Rule 2 – WARNING: teams/ 파일 산재
# ---------------------------------------------------------------------------

def test_teams_allowed_no_warning(tmp_path: Path) -> None:
    ws = _ws(tmp_path)
    t = ws / "teams" / "dev1"
    t.mkdir(parents=True)
    (t / "CLAUDE.md").write_text("ok")
    (t / "qc").mkdir()
    assert lw.check_teams_scatter(ws) == []


def test_teams_loose_file_warning(tmp_path: Path) -> None:
    ws = _ws(tmp_path)
    t = ws / "teams" / "dev2"
    t.mkdir(parents=True)
    (t / "plan-task-999.1.md").write_text("oops")
    v = lw.check_teams_scatter(ws)
    assert len(v) == 1 and v[0][0] == "WARNING" and "plan-task-999.1.md" in v[0][1]


def test_teams_task_dir_ignored(tmp_path: Path) -> None:
    ws = _ws(tmp_path)
    d = ws / "teams" / "dev3" / "task-101.1"
    d.mkdir(parents=True)
    (d / "plan.md").write_text("ok")
    assert lw.check_teams_scatter(ws) == []


# ---------------------------------------------------------------------------
# Rule 3 – WARNING: tmp/ 7일 초과
# ---------------------------------------------------------------------------

def test_tmp_recent_no_warning(tmp_path: Path) -> None:
    ws = _ws(tmp_path)
    _old(ws / "tmp" / "recent.txt", days=3)
    assert lw.check_tmp_old_files(ws) == []


def test_tmp_old_file_warning(tmp_path: Path) -> None:
    ws = _ws(tmp_path)
    _old(ws / "tmp" / "stale.log", days=10)
    v = lw.check_tmp_old_files(ws)
    assert len(v) == 1 and v[0][0] == "WARNING" and "stale.log" in v[0][1]


def test_tmp_exactly_7_days_no_warning(tmp_path: Path) -> None:
    """6.9일짜리는 7일 미초과이므로 경고 없음."""
    ws = _ws(tmp_path)
    _old(ws / "tmp" / "edge.txt", days=6)
    assert lw.check_tmp_old_files(ws) == []


# ---------------------------------------------------------------------------
# Rule 4 – INFO: 200줄 초과 .py
# ---------------------------------------------------------------------------

def test_short_py_no_info(tmp_path: Path) -> None:
    ws = _ws(tmp_path)
    (ws / "scripts" / "short.py").write_text("x\n" * 50)
    assert lw.check_long_py_files(ws) == []


def test_long_py_info(tmp_path: Path) -> None:
    ws = _ws(tmp_path)
    (ws / "scripts" / "long.py").write_text("x\n" * 201)
    v = lw.check_long_py_files(ws)
    assert len(v) == 1 and v[0][0] == "INFO" and "long.py" in v[0][1] and "줄" in v[0][1]


def test_workers_py_checked(tmp_path: Path) -> None:
    ws = _ws(tmp_path)
    (ws / "workers" / "big.py").write_text("x\n" * 250)
    v = lw.check_long_py_files(ws)
    assert any("big.py" in m for _, m in v)


# ---------------------------------------------------------------------------
# Rule 5 – ERROR: memory/events/ 90일 초과 .done
# ---------------------------------------------------------------------------

def test_memory_recent_done_no_error(tmp_path: Path) -> None:
    ws = _ws(tmp_path)
    _old(ws / "memory" / "events" / "task-1.done", days=30)
    assert lw.check_memory_events_done(ws) == []


def test_memory_old_done_error(tmp_path: Path) -> None:
    ws = _ws(tmp_path)
    _old(ws / "memory" / "events" / "task-old.done", days=100)
    v = lw.check_memory_events_done(ws)
    assert len(v) == 1 and v[0][0] == "ERROR" and "task-old.done" in v[0][1]


def test_memory_non_done_ignored(tmp_path: Path) -> None:
    ws = _ws(tmp_path)
    _old(ws / "memory" / "events" / "task-1.event", days=200)
    assert lw.check_memory_events_done(ws) == []


# ---------------------------------------------------------------------------
# Rule 6 – INFO: 디스크 사용량
# ---------------------------------------------------------------------------

def test_disk_usage_info(tmp_path: Path) -> None:
    ws = _ws(tmp_path)
    v = lw.check_disk_usage(ws)
    assert len(v) == 1 and v[0][0] == "INFO"
    assert any(k in v[0][1] for k in ("디스크", "disk", "GB", "MB"))


# ---------------------------------------------------------------------------
# 정렬 및 종료 코드
# ---------------------------------------------------------------------------

def test_sort_order(tmp_path: Path) -> None:
    v = [("INFO", "i"), ("ERROR", "e"), ("WARNING", "w")]
    assert [s for s, _ in lw.sort_violations(v)] == ["ERROR", "WARNING", "INFO"]


def test_exit_code_error() -> None:
    assert lw.compute_exit_code([("ERROR", "x"), ("WARNING", "y")]) == 1


def test_exit_code_warning_only() -> None:
    assert lw.compute_exit_code([("WARNING", "x")]) == 0


def test_exit_code_no_violations() -> None:
    assert lw.compute_exit_code([]) == 0
