#!/usr/bin/env python3
"""
canary-test.py — 일 1회 실행되는 시스템 건강도 canary 테스트.

시스템 핵심 기능이 정상 작동하는지 검증하고 실패 시 텔레그램으로 알림 전송.
"""

import json
import os
import subprocess
import sys
import tempfile
from datetime import datetime, timezone
from pathlib import Path
from typing import List, Optional, Tuple

# ── 경로 상수 ──────────────────────────────────────────────────────────────────
WORKSPACE_ROOT = Path(os.environ.get("WORKSPACE_ROOT", str(Path(__file__).resolve().parent.parent)))
MEMORY_DIR = WORKSPACE_ROOT / "memory"
EVENTS_DIR = MEMORY_DIR / "events"
LOGS_DIR = WORKSPACE_ROOT / "logs"
TASK_TIMER_PY = MEMORY_DIR / "task-timer.py"
TASK_TIMERS_JSON = MEMORY_DIR / "task-timers.json"
NOTIFY_COMPLETION_PY = WORKSPACE_ROOT / "scripts" / "notify-completion.py"
CANARY_STATUS_JSON = MEMORY_DIR / "canary-status.json"
CANARY_LOG = LOGS_DIR / "canary-test.log"

# 텔레그램 알림 설정
CHAT_ID = os.environ.get("COKACDIR_CHAT_ID", "6937032012")
ANU_KEY = os.environ.get("COKACDIR_KEY_ANU", "c119085addb0f8b7")

# 로그 보관 줄 수
LOG_KEEP_LINES = 100

# stale 작업 기준: 24시간(초)
STALE_THRESHOLD_SECONDS = 24 * 3600


# ── 로깅 ───────────────────────────────────────────────────────────────────────


def _now_str() -> str:
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")


def _log_line(level: str, test_name: str, description: str) -> str:
    return f"[{_now_str()}] [{level}] {test_name}: {description}"


def write_log(line: str) -> None:
    """로그 파일에 한 줄 추가 후 마지막 LOG_KEEP_LINES 줄만 유지."""
    LOGS_DIR.mkdir(parents=True, exist_ok=True)
    # 기존 줄 읽기
    existing: List[str] = []
    if CANARY_LOG.exists():
        existing = CANARY_LOG.read_text(encoding="utf-8").splitlines()
    existing.append(line)
    # 마지막 100줄만 보관
    trimmed = existing[-LOG_KEEP_LINES:]
    CANARY_LOG.write_text("\n".join(trimmed) + "\n", encoding="utf-8")
    # 콘솔에도 출력
    print(line)


def log_pass(test_name: str, description: str) -> None:
    write_log(_log_line("PASS", test_name, description))


def log_fail(test_name: str, description: str) -> None:
    write_log(_log_line("FAIL", test_name, description))


# ── 텔레그램 알림 ──────────────────────────────────────────────────────────────


def send_telegram_log(log_file: Path) -> None:
    """로그 파일을 텔레그램으로 전송. 실패해도 조용히 무시."""
    try:
        cmd = [
            "cokacdir",
            "--sendfile",
            str(log_file),
            "--chat",
            CHAT_ID,
            "--key",
            ANU_KEY,
        ]
        subprocess.run(cmd, timeout=30, capture_output=True)
    except Exception:
        pass  # 알림 실패는 canary 결과에 영향 없음


# ── 개별 테스트 ────────────────────────────────────────────────────────────────


def test_task_timers_json_rw() -> Tuple[bool, str]:
    """1. task-timers.json 읽기/쓰기 가능 여부."""
    test_name = "task-timers.json 읽기/쓰기"
    try:
        if not TASK_TIMERS_JSON.exists():
            return False, f"{TASK_TIMERS_JSON} 파일이 존재하지 않음"
        # 읽기
        content = TASK_TIMERS_JSON.read_text(encoding="utf-8")
        json.loads(content)  # JSON 파싱 검증
        # 쓰기 가능 여부: 같은 디렉토리에 임시 파일 생성
        test_file = TASK_TIMERS_JSON.parent / ".canary-write-test"
        test_file.write_text("ok", encoding="utf-8")
        test_file.unlink()
        return True, "읽기/쓰기 정상"
    except Exception as e:
        return False, str(e)


def test_events_dir_writable() -> Tuple[bool, str]:
    """2. memory/events/ 디렉토리 쓰기 가능 여부."""
    test_name = "memory/events/ 쓰기"
    try:
        EVENTS_DIR.mkdir(parents=True, exist_ok=True)
        test_file = EVENTS_DIR / ".canary-write-test"
        test_file.write_text("ok", encoding="utf-8")
        test_file.unlink()
        return True, "쓰기 정상"
    except Exception as e:
        return False, str(e)


def test_task_timer_status() -> Tuple[bool, str]:
    """3. task-timer.py `status` 명령 동작 확인 (list 명령으로 동작 검증)."""
    try:
        result = subprocess.run(
            [sys.executable, str(TASK_TIMER_PY), "list", "running"],
            capture_output=True,
            text=True,
            timeout=30,
        )
        # exit code 0이고 JSON 파싱 가능이면 성공
        if result.returncode != 0:
            return False, f"exit code {result.returncode}: {result.stderr.strip()}"
        output = result.stdout.strip()
        if not output:
            return False, "출력 없음"
        json.loads(output)
        return True, "task-timer.py list running 정상 응답"
    except subprocess.TimeoutExpired:
        return False, "타임아웃 (30초)"
    except Exception as e:
        return False, str(e)


def test_task_timer_cleanup_dry_run() -> Tuple[bool, str]:
    """4. task-timer.py `cleanup --dry-run` 동작 확인."""
    try:
        result = subprocess.run(
            [sys.executable, str(TASK_TIMER_PY), "cleanup", "--dry-run"],
            capture_output=True,
            text=True,
            timeout=30,
        )
        if result.returncode != 0:
            return False, f"exit code {result.returncode}: {result.stderr.strip()}"
        output = result.stdout.strip()
        if not output:
            return False, "출력 없음"
        parsed = json.loads(output)
        if parsed.get("status") != "dry_run":
            return False, f"예상치 않은 status: {parsed.get('status')}"
        return True, f"dry_run 정상, would_clean_count={parsed.get('would_clean_count', 0)}"
    except subprocess.TimeoutExpired:
        return False, "타임아웃 (30초)"
    except Exception as e:
        return False, str(e)


def test_daily_log_writable() -> Tuple[bool, str]:
    """5. 일일 로그 파일 쓰기 가능 여부."""
    try:
        today_str = datetime.now().strftime("%Y-%m-%d")
        daily_log = LOGS_DIR / f"canary-daily-{today_str}.log"
        LOGS_DIR.mkdir(parents=True, exist_ok=True)
        daily_log.write_text(
            f"[{_now_str()}] canary write test\n",
            encoding="utf-8",
        )
        size = daily_log.stat().st_size
        return True, f"일일 로그 쓰기 정상 ({size}바이트)"
    except Exception as e:
        return False, str(e)


def test_notify_completion_exists() -> Tuple[bool, str]:
    """6. notify-completion.py 파일 존재 확인."""
    try:
        if NOTIFY_COMPLETION_PY.exists() and NOTIFY_COMPLETION_PY.is_file():
            size = NOTIFY_COMPLETION_PY.stat().st_size
            return True, f"파일 존재 ({size}바이트)"
        return False, f"{NOTIFY_COMPLETION_PY} 파일 없음"
    except Exception as e:
        return False, str(e)


def test_stale_running_tasks() -> Tuple[bool, str]:
    """7. 현재 running 중인 stale 작업 감지 (24시간 이상)."""
    try:
        result = subprocess.run(
            [sys.executable, str(TASK_TIMER_PY), "list", "running"],
            capture_output=True,
            text=True,
            timeout=30,
        )
        if result.returncode != 0:
            return False, f"list running 실패: {result.stderr.strip()}"
        output = result.stdout.strip()
        if not output:
            return True, "실행 중인 작업 없음"
        parsed = json.loads(output)
        tasks: List[dict] = parsed.get("tasks", [])
        now = datetime.now()
        stale_tasks: List[str] = []
        for task in tasks:
            start_str: Optional[str] = task.get("start_time")
            if not start_str:
                continue
            # ISO 형식 파싱 (Python 3.8 호환: fromisoformat은 timezone-naive만)
            start_str_clean = start_str.replace("Z", "")
            try:
                start_dt = datetime.fromisoformat(start_str_clean)
            except ValueError:
                continue
            elapsed = (now - start_dt).total_seconds()
            if elapsed >= STALE_THRESHOLD_SECONDS:
                hours = elapsed / 3600
                stale_tasks.append(f"{task.get('task_id', '?')} ({hours:.1f}h)")
        if stale_tasks:
            # stale 작업 존재는 경고이지만 테스트 자체는 "감지 성공"이므로 PASS로 처리
            # (감지 기능이 동작했음을 의미)
            return True, f"stale 작업 {len(stale_tasks)}개 감지: {', '.join(stale_tasks)}"
        return True, f"running 작업 {len(tasks)}개, stale 없음"
    except subprocess.TimeoutExpired:
        return False, "타임아웃 (30초)"
    except Exception as e:
        return False, str(e)


# ── canary 상태 저장 ───────────────────────────────────────────────────────────


def save_canary_status(results: List[dict], all_passed: bool) -> None:
    """canary-status.json에 마지막 실행 결과 기록."""
    status = {
        "last_run": _now_str(),
        "all_passed": all_passed,
        "pass_count": sum(1 for r in results if r["passed"]),
        "fail_count": sum(1 for r in results if not r["passed"]),
        "results": results,
    }
    MEMORY_DIR.mkdir(parents=True, exist_ok=True)
    CANARY_STATUS_JSON.write_text(
        json.dumps(status, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )


# ── 메인 ──────────────────────────────────────────────────────────────────────


def main() -> int:
    write_log(f"[{_now_str()}] [INFO] canary-test 시작")

    tests = [
        ("task-timers.json 읽기/쓰기", test_task_timers_json_rw),
        ("memory/events/ 쓰기", test_events_dir_writable),
        ("task-timer.py status 동작", test_task_timer_status),
        ("task-timer.py cleanup --dry-run 동작", test_task_timer_cleanup_dry_run),
        ("일일 로그 쓰기", test_daily_log_writable),
        ("notify-completion.py 존재", test_notify_completion_exists),
        ("stale 작업 감지 (24h 이상)", test_stale_running_tasks),
    ]

    results: List[dict] = []
    all_passed = True

    for test_name, test_fn in tests:
        try:
            passed, description = test_fn()
        except Exception as e:
            passed = False
            description = f"예외 발생: {e}"

        if passed:
            log_pass(test_name, description)
        else:
            log_fail(test_name, description)
            all_passed = False

        results.append({"test": test_name, "passed": passed, "detail": description})

    # 요약
    pass_count = sum(1 for r in results if r["passed"])
    fail_count = len(results) - pass_count
    summary = f"총 {len(results)}개 테스트 — PASS: {pass_count}, FAIL: {fail_count}"
    write_log(f"[{_now_str()}] [{'PASS' if all_passed else 'FAIL'}] 최종 결과: {summary}")

    # canary 상태 저장
    try:
        save_canary_status(results, all_passed)
    except Exception as e:
        write_log(f"[{_now_str()}] [FAIL] canary-status.json 저장 실패: {e}")

    # 실패 시 텔레그램 알림
    if not all_passed:
        write_log(f"[{_now_str()}] [INFO] 텔레그램 알림 전송 시도")
        send_telegram_log(CANARY_LOG)

    return 0 if all_passed else 1


if __name__ == "__main__":
    sys.exit(main())
