#!/usr/bin/env python3
"""IDS 6 Phase 통합 watcher + .done.clear 월별 archive (task-2395).

Author: 쿠쿨칸 / dev7-team
Task: task-2395

기능:
1) archive_old_done_clear: memory/events/*.done.clear 중 90일 이상 파일을
   memory/events/archive/<YYYY-MM>/ 로 이동 (삭제 절대 금지).
2) check_phase_progress / run_completion_checks / watch_once:
   IDS 6 Phase (task-2389~2394) .done 마커를 polling 하여 진행률 보고.
   6/6 도착 시 보고서 존재 + git status 검증 + Telegram 통보.

원칙:
- 외부 dispatch 스크립트 임포트 절대 금지 (자동 위임 차단)
- 파일 삭제 금지 (history 보존 의무) — shutil.move 만 사용
- pytest 자동 실행 금지 (메시지 권고만)

CLI:
    python3 scripts/ids_phase_monitor.py archive [--dry-run] [--age-days 90]
    python3 scripts/ids_phase_monitor.py watch [--no-notify]
    python3 scripts/ids_phase_monitor.py check-progress
"""

from __future__ import annotations

import argparse
import json
import os
import shutil
import subprocess
import sys
import time
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Optional

try:
    import requests
except ImportError:
    requests = None  # type: ignore[assignment]


# ---------------------------------------------------------------------------
# 상수
# ---------------------------------------------------------------------------
TARGET_TASKS = ["task-2389", "task-2390", "task-2391", "task-2392", "task-2393", "task-2394"]
DEFAULT_AGE_DAYS = 90
DEFAULT_CHAT_ID = "6937032012"

WORKSPACE_ROOT = os.environ.get("WORKSPACE_ROOT", "/home/jay/workspace")
EVENTS_DIR = Path(f"{WORKSPACE_ROOT}/memory/events")
ARCHIVE_DIR = EVENTS_DIR / "archive"
REPORTS_DIR = Path(f"{WORKSPACE_ROOT}/memory/reports")
STATE_FILE = Path(f"{WORKSPACE_ROOT}/memory/events/ids_phase_monitor.state.json")
KST = timezone(timedelta(hours=9))


# ---------------------------------------------------------------------------
# archive_old_done_clear
# ---------------------------------------------------------------------------
def archive_old_done_clear(
    events_dir: Path,
    archive_root: Path,
    age_days: int = DEFAULT_AGE_DAYS,
    dry_run: bool = False,
    now: Optional[datetime] = None,
) -> dict:
    """*.done.clear 중 mtime 기준 age_days 이상 오래된 파일을
    archive_root/<YYYY-MM>/ 로 이동 (삭제 금지).
    """
    if now is None:
        now = datetime.now(KST)

    cutoff_ts = (now - timedelta(days=age_days)).timestamp()

    moved = 0
    skipped = 0
    by_month: dict[str, int] = {}
    errors: list[str] = []

    if not events_dir.exists():
        return {"moved": 0, "skipped": 0, "by_month": {}, "errors": [f"events_dir missing: {events_dir}"]}

    for entry in events_dir.iterdir():
        if not entry.is_file():
            continue
        if not entry.name.endswith(".done.clear"):
            continue

        try:
            mtime = entry.stat().st_mtime
        except OSError as e:
            errors.append(f"{entry.name}: stat failed: {e}")
            continue

        if mtime > cutoff_ts:
            skipped += 1
            continue

        month_key = datetime.fromtimestamp(mtime, tz=KST).strftime("%Y-%m")
        target_dir = archive_root / month_key
        target_path = target_dir / entry.name

        if dry_run:
            moved += 1
            by_month[month_key] = by_month.get(month_key, 0) + 1
            continue

        try:
            target_dir.mkdir(parents=True, exist_ok=True)
            shutil.move(str(entry), str(target_path))
            moved += 1
            by_month[month_key] = by_month.get(month_key, 0) + 1
        except OSError as e:
            errors.append(f"{entry.name}: move failed: {e}")

    return {
        "moved": moved,
        "skipped": skipped,
        "by_month": by_month,
        "errors": errors,
    }


# ---------------------------------------------------------------------------
# check_phase_progress
# ---------------------------------------------------------------------------
def check_phase_progress(
    events_dir: Path,
    tasks: Optional[list[str]] = None,
) -> dict:
    """events_dir 안의 <task_id>.done (또는 <task_id>.dev*.done / <task_id>.<team>.done)
    마커 존재 여부로 진행률 산출. .done.clear 는 제외."""
    if tasks is None:
        tasks = list(TARGET_TASKS)

    try:
        all_files = {f.name for f in events_dir.iterdir() if f.is_file()}
    except OSError:
        all_files = set()

    done_tasks: list[str] = []
    pending_tasks: list[str] = []

    for task_id in tasks:
        matched = False
        for fname in all_files:
            if not fname.startswith(task_id):
                continue
            if not fname.endswith(".done"):
                continue
            if fname.endswith(".done.clear"):
                continue
            # 패턴: {task_id}.done | {task_id}.<suffix>.done
            tail = fname[len(task_id):]
            if tail == ".done" or (tail.startswith(".") and tail.endswith(".done")):
                matched = True
                break
        if matched:
            done_tasks.append(task_id)
        else:
            pending_tasks.append(task_id)

    n_done = len(done_tasks)
    n_total = len(tasks)
    return {
        "tasks": list(tasks),
        "done": done_tasks,
        "pending": pending_tasks,
        "ratio": f"{n_done}/{n_total}",
        "n_done": n_done,
        "n_total": n_total,
        "complete": n_done == n_total,
    }


# ---------------------------------------------------------------------------
# run_completion_checks
# ---------------------------------------------------------------------------
def run_completion_checks(
    reports_dir: Path,
    project_root: Path,
    tasks: Optional[list[str]] = None,
) -> dict:
    """6/6 도착 시 통합 검증.

    1) reports_dir/<task_id>.md 존재 확인
    2) git status (project_root)
    3) pytest 권고 메시지만 반환 (자동 실행 금지)
    """
    if tasks is None:
        tasks = list(TARGET_TASKS)

    reports_ok: list[str] = []
    reports_missing: list[str] = []
    for task_id in tasks:
        report_path = reports_dir / f"{task_id}.md"
        if report_path.exists() and report_path.is_file():
            reports_ok.append(task_id)
        else:
            reports_missing.append(task_id)

    git_status = "no-git"
    git_output = ""
    git_dir = project_root / ".git"
    if git_dir.exists():
        try:
            res = subprocess.run(
                ["git", "-C", str(project_root), "status", "--porcelain"],
                capture_output=True,
                text=True,
                timeout=20,
                check=False,
            )
            if res.returncode == 0:
                git_output = res.stdout.strip()
                git_status = "dirty" if git_output else "clean"
            else:
                git_status = f"error: rc={res.returncode}"
                git_output = (res.stderr or res.stdout or "").strip()
        except (OSError, subprocess.SubprocessError) as e:
            git_status = f"error: {type(e).__name__}"
            git_output = str(e)

    pytest_recommendation = (
        "pytest tests/dev1 tests/dev6 tests/design-team tests/dev3"
    )
    batch_report_target = str(reports_dir / "batch-ids-master.md")

    return {
        "reports_ok": reports_ok,
        "reports_missing": reports_missing,
        "git_status": git_status,
        "git_output": git_output,
        "pytest_recommendation": pytest_recommendation,
        "batch_report_target": batch_report_target,
    }


# ---------------------------------------------------------------------------
# Telegram (done-watcher.py 패턴 차용)
# ---------------------------------------------------------------------------
def send_telegram_notification(
    message: str,
    chat_id: str = DEFAULT_CHAT_ID,
    bot_token: Optional[str] = None,
) -> bool:
    """Telegram 메시지 전송. 토큰 부재 시 False (조용히 실패)."""
    if bot_token is None:
        bot_token = os.environ.get("ANU_BOT_TOKEN", "")
    if not bot_token:
        return False
    if requests is None:
        return False

    url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
    for attempt in range(1, 4):
        payload = {"chat_id": chat_id, "text": message, "parse_mode": "Markdown"}
        try:
            resp = requests.post(url, json=payload, timeout=10)
            if resp.status_code == 200:
                return True
            if resp.status_code == 429:
                retry_after = 5
                try:
                    retry_after = int(resp.json().get("parameters", {}).get("retry_after", 5))
                except Exception:
                    pass
                time.sleep(retry_after)
                continue
            if resp.status_code == 400:
                resp2 = requests.post(
                    url, json={"chat_id": chat_id, "text": message}, timeout=10
                )
                return resp2.status_code == 200
            if attempt < 3:
                time.sleep(2)
        except requests.RequestException:
            if attempt < 3:
                time.sleep(2)
    return False


# ---------------------------------------------------------------------------
# state I/O
# ---------------------------------------------------------------------------
def load_state(state_file: Path = STATE_FILE) -> dict:
    if not state_file.exists():
        return {"last_ratio": None, "last_notified_at": None, "completion_notified": False}
    try:
        return json.loads(state_file.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError):
        return {"last_ratio": None, "last_notified_at": None, "completion_notified": False}


def save_state(state: dict, state_file: Path = STATE_FILE) -> None:
    state_file.parent.mkdir(parents=True, exist_ok=True)
    tmp = state_file.with_suffix(state_file.suffix + ".tmp")
    tmp.write_text(json.dumps(state, indent=2, ensure_ascii=False), encoding="utf-8")
    shutil.move(str(tmp), str(state_file))


# ---------------------------------------------------------------------------
# watch_once
# ---------------------------------------------------------------------------
def watch_once(
    events_dir: Path = EVENTS_DIR,
    reports_dir: Path = REPORTS_DIR,
    project_root: Path = Path(WORKSPACE_ROOT),
    state_file: Path = STATE_FILE,
    tasks: Optional[list[str]] = None,
    notify: bool = True,
) -> dict:
    """1회 polling. 진행률 변화 시 Telegram 1회 발송, 6/6 시 통합 검증.
    자동 dispatch 호출 금지 (외부 스크립트 임포트 일체 없음)."""
    if tasks is None:
        tasks = list(TARGET_TASKS)

    progress = check_phase_progress(events_dir=events_dir, tasks=tasks)
    state = load_state(state_file=state_file)

    output: dict = {"progress": progress, "completion": None, "notified": False}

    last_ratio = state.get("last_ratio")
    if progress["ratio"] != last_ratio:
        if notify:
            msg = (
                f"*IDS 6 Phase 진행률* `{progress['ratio']}`\n"
                f"done: {', '.join(progress['done']) or '-'}\n"
                f"pending: {', '.join(progress['pending']) or '-'}"
            )
            send_telegram_notification(msg)
            output["notified"] = True
        state["last_ratio"] = progress["ratio"]
        state["last_notified_at"] = datetime.now(KST).isoformat()

    if progress["complete"] and not state.get("completion_notified"):
        completion = run_completion_checks(
            reports_dir=reports_dir, project_root=project_root, tasks=tasks
        )
        output["completion"] = completion
        if notify:
            msg = (
                "*IDS 6/6 완료 — 통합 검증*\n"
                f"reports_ok: {len(completion['reports_ok'])}/{len(tasks)}\n"
                f"reports_missing: {', '.join(completion['reports_missing']) or '-'}\n"
                f"git_status: {completion['git_status']}\n"
                f"권고: `{completion['pytest_recommendation']}`\n"
                f"batch report: {completion['batch_report_target']}"
            )
            send_telegram_notification(msg)
            output["notified"] = True
        state["completion_notified"] = True

    try:
        save_state(state, state_file=state_file)
    except OSError:
        pass

    return output


# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------
def _cmd_archive(args: argparse.Namespace) -> int:
    result = archive_old_done_clear(
        events_dir=EVENTS_DIR,
        archive_root=ARCHIVE_DIR,
        age_days=args.age_days,
        dry_run=args.dry_run,
    )
    print(json.dumps(result, indent=2, ensure_ascii=False))
    return 0 if not result["errors"] else 1


def _cmd_watch(args: argparse.Namespace) -> int:
    result = watch_once(notify=not args.no_notify)
    print(json.dumps(result, indent=2, ensure_ascii=False, default=str))
    return 0


def _cmd_check_progress(_args: argparse.Namespace) -> int:
    progress = check_phase_progress(events_dir=EVENTS_DIR)
    print(json.dumps(progress, indent=2, ensure_ascii=False))
    return 0


def main(argv: Optional[list[str]] = None) -> int:
    parser = argparse.ArgumentParser(description="IDS phase monitor + done.clear archiver")
    sub = parser.add_subparsers(dest="cmd", required=True)

    p_arch = sub.add_parser("archive", help="archive *.done.clear older than --age-days")
    p_arch.add_argument("--age-days", type=int, default=DEFAULT_AGE_DAYS)
    p_arch.add_argument("--dry-run", action="store_true")
    p_arch.set_defaults(func=_cmd_archive)

    p_watch = sub.add_parser("watch", help="run one polling cycle")
    p_watch.add_argument("--no-notify", action="store_true")
    p_watch.set_defaults(func=_cmd_watch)

    p_chk = sub.add_parser("check-progress", help="print current N/6 progress")
    p_chk.set_defaults(func=_cmd_check_progress)

    args = parser.parse_args(argv)
    return args.func(args)


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