#!/usr/bin/env python3
"""
update-system-spec.py
시스템 현재 상태를 자동 수집하여 anu-system-spec.md를 업데이트하는 스크립트.
작성자: 카르티케야 (개발4팀 백엔드 개발자)
"""

import datetime
import json
import os
import re
import subprocess
import sys
from pathlib import Path

# ── 경로 상수 ──────────────────────────────────────────────────────────────────
_WORKSPACE_ROOT = os.environ.get("WORKSPACE_ROOT", str(Path(__file__).resolve().parent.parent))
SPEC_FILE = Path(_WORKSPACE_ROOT) / "memory" / "specs" / "anu-system-spec.md"
CACHE_FILE = Path(_WORKSPACE_ROOT) / "memory" / "specs" / ".spec-state-cache.json"
CHANGELOG_FILE = Path(_WORKSPACE_ROOT) / "memory" / "specs" / "anu-system-spec-changelog.md"

SKILLS_DIR = Path("/home/jay/.claude/skills/")
PROJECTS_DIR = Path("/home/jay/projects/")
SCRIPTS_DIR = Path(_WORKSPACE_ROOT) / "scripts"
TASK_TIMERS = Path(_WORKSPACE_ROOT) / "memory" / "task-timers.json"

# ── 환경변수 ───────────────────────────────────────────────────────────────────
ANU_KEY = os.environ.get("ANU_KEY", "")
ANU_CHAT = os.environ.get("ANU_CHAT", "")

# ── 섹션 마커 ──────────────────────────────────────────────────────────────────
SECTIONS = ["skills", "cron", "services", "projects", "scripts", "task_stats"]


# ══════════════════════════════════════════════════════════════════════════════
# 수집 함수
# ══════════════════════════════════════════════════════════════════════════════


def collect_skills() -> dict:
    """스킬 디렉토리를 스캔하여 목록 반환."""
    if not SKILLS_DIR.exists():
        raise FileNotFoundError(f"스킬 디렉토리 없음: {SKILLS_DIR}")
    skills = sorted([p.name for p in SKILLS_DIR.iterdir() if p.is_dir() or p.is_file()])
    return {"skills": skills, "count": len(skills)}


def collect_cron() -> dict:
    """cokacdir --cron-list 실행 후 JSON 파싱."""
    if not ANU_CHAT:
        raise ValueError("ANU_CHAT 환경변수가 설정되지 않았습니다.")
    cmd = [
        "/usr/local/bin/cokacdir",
        "--cron-list",
        "--chat",
        ANU_CHAT,
        "--key",
        ANU_KEY,
    ]
    result = subprocess.run(
        cmd,
        capture_output=True,
        text=True,
        timeout=30,
    )
    if result.returncode != 0:
        raise RuntimeError(f"cokacdir 실행 실패 (exit {result.returncode}): {result.stderr.strip()}")
    data = json.loads(result.stdout)
    return {"cron_jobs": data}


def collect_services() -> dict:
    """실행 중인 사용자 서비스 목록 파싱."""
    result = subprocess.run(
        [
            "systemctl",
            "--user",
            "list-units",
            "--type=service",
            "--state=running",
            "--no-pager",
            "--no-legend",
        ],
        capture_output=True,
        text=True,
        timeout=15,
    )
    services = []
    for line in result.stdout.splitlines():
        line = line.strip()
        if not line:
            continue
        # 첫 번째 토큰이 서비스 유닛 이름
        parts = line.split()
        if parts:
            services.append(parts[0])
    return {"running_services": sorted(services), "count": len(services)}


def collect_projects() -> dict:
    """프로젝트 디렉토리 목록 (디렉토리만, 히든/캐시 제외)."""
    if not PROJECTS_DIR.exists():
        raise FileNotFoundError(f"프로젝트 디렉토리 없음: {PROJECTS_DIR}")
    projects = sorted([p.name for p in PROJECTS_DIR.iterdir() if p.is_dir() and not p.name.startswith(".")])
    return {"projects": projects, "count": len(projects)}


def collect_scripts() -> dict:
    """scripts/ 파일 목록."""
    if not SCRIPTS_DIR.exists():
        raise FileNotFoundError(f"스크립트 디렉토리 없음: {SCRIPTS_DIR}")
    scripts = sorted([p.name for p in SCRIPTS_DIR.iterdir() if p.is_file()])
    return {"scripts": scripts, "count": len(scripts)}


def collect_task_stats() -> dict:
    """task-timers.json 통계 계산."""
    if not TASK_TIMERS.exists():
        raise FileNotFoundError(f"task-timers.json 없음: {TASK_TIMERS}")
    with TASK_TIMERS.open(encoding="utf-8") as f:
        data = json.load(f)

    tasks_raw = data.get("tasks", {})
    if isinstance(tasks_raw, dict):
        task_list = list(tasks_raw.values())
    elif isinstance(tasks_raw, list):
        task_list = tasks_raw
    else:
        task_list = []

    total = len(task_list)
    completed = sum(1 for t in task_list if t.get("status") == "completed")
    durations = [t["duration_seconds"] for t in task_list if isinstance(t.get("duration_seconds"), (int, float))]
    avg_duration = (sum(durations) / len(durations)) if durations else 0.0
    max_duration = max(durations) if durations else 0.0
    min_duration = min(durations) if durations else 0.0

    return {
        "total_tasks": total,
        "completed_tasks": completed,
        "avg_duration_sec": round(avg_duration, 2),
        "max_duration_sec": round(max_duration, 2),
        "min_duration_sec": round(min_duration, 2),
    }


# ══════════════════════════════════════════════════════════════════════════════
# 콘텐츠 렌더 함수 (섹션별 마크다운)
# ══════════════════════════════════════════════════════════════════════════════


def render_skills(data: dict) -> str:
    lines = [f"**총 {data['count']}개 스킬**\n"]
    for s in data["skills"]:
        lines.append(f"- `{s}`")
    return "\n".join(lines)


def render_cron(data: dict) -> str:
    jobs = data.get("cron_jobs", [])
    if not jobs:
        return "_등록된 cron job 없음_"
    if isinstance(jobs, list):
        lines = [f"**총 {len(jobs)}개 cron job**\n"]
        for job in jobs:
            if isinstance(job, dict):
                lines.append(f"- `{json.dumps(job, ensure_ascii=False)}`")
            else:
                lines.append(f"- `{job}`")
        return "\n".join(lines)
    return f"```json\n{json.dumps(jobs, indent=2, ensure_ascii=False)}\n```"


def render_services(data: dict) -> str:
    svcs = data.get("running_services", [])
    if not svcs:
        return "_실행 중인 사용자 서비스 없음_"
    lines = [f"**실행 중: {data['count']}개**\n"]
    for s in svcs:
        lines.append(f"- `{s}`")
    return "\n".join(lines)


def render_projects(data: dict) -> str:
    projs = data.get("projects", [])
    if not projs:
        return "_프로젝트 없음_"
    lines = [f"**총 {data['count']}개 프로젝트**\n"]
    for p in projs:
        lines.append(f"- `{p}`")
    return "\n".join(lines)


def render_scripts(data: dict) -> str:
    scripts = data.get("scripts", [])
    if not scripts:
        return "_스크립트 없음_"
    lines = [f"**총 {data['count']}개 파일**\n"]
    for s in scripts:
        lines.append(f"- `{s}`")
    return "\n".join(lines)


def render_task_stats(data: dict) -> str:
    def fmt_sec(s: float) -> str:
        m, sec = divmod(int(s), 60)
        h, m = divmod(m, 60)
        if h:
            return f"{h}시간 {m}분 {sec}초"
        if m:
            return f"{m}분 {sec}초"
        return f"{sec}초"

    lines = [
        f"| 항목 | 값 |",
        f"|------|-----|",
        f"| 총 작업 수 | {data['total_tasks']} |",
        f"| 완료된 작업 | {data['completed_tasks']} |",
        (
            f"| 완료율 | {data['completed_tasks']/data['total_tasks']*100:.1f}% |"
            if data["total_tasks"]
            else "| 완료율 | N/A |"
        ),
        f"| 평균 소요시간 | {fmt_sec(data['avg_duration_sec'])} |",
        f"| 최대 소요시간 | {fmt_sec(data['max_duration_sec'])} |",
        f"| 최소 소요시간 | {fmt_sec(data['min_duration_sec'])} |",
    ]
    return "\n".join(lines)


RENDERERS = {
    "skills": render_skills,
    "cron": render_cron,
    "services": render_services,
    "projects": render_projects,
    "scripts": render_scripts,
    "task_stats": render_task_stats,
}


# ══════════════════════════════════════════════════════════════════════════════
# 섹션 업데이트 함수
# ══════════════════════════════════════════════════════════════════════════════


def update_section_in_spec(content: str, section: str, new_body: str) -> str:
    """
    섹션 마커 사이의 내용을 new_body로 교체.
    마커가 없으면 파일 끝에 섹션을 추가.
    """
    start_marker = f"<!-- AUTO:{section}:START -->"
    end_marker = f"<!-- AUTO:{section}:END -->"

    start_idx = content.find(start_marker)
    end_idx = content.find(end_marker)

    if start_idx == -1 or end_idx == -1:
        # 마커 없음 → 파일 끝에 추가
        block = f"\n\n{start_marker}\n" f"{new_body}\n" f"{end_marker}\n"
        return content + block

    # 마커 사이 내용 교체 (마커 자체는 유지)
    before = content[: start_idx + len(start_marker)]
    after = content[end_idx:]
    return before + "\n" + new_body + "\n" + after


# ══════════════════════════════════════════════════════════════════════════════
# 캐시 / 변경 감지
# ══════════════════════════════════════════════════════════════════════════════


def load_cache() -> dict:
    if CACHE_FILE.exists():
        try:
            with CACHE_FILE.open(encoding="utf-8") as f:
                return json.load(f)
        except Exception:
            pass
    return {}


def save_cache(state: dict) -> None:
    CACHE_FILE.parent.mkdir(parents=True, exist_ok=True)
    with CACHE_FILE.open("w", encoding="utf-8") as f:
        json.dump(state, f, ensure_ascii=False, indent=2)


def detect_changes(old_state: dict, new_state: dict) -> dict[str, list[str]]:
    """
    섹션별로 변경 항목 설명 목록을 반환.
    변경 없으면 빈 dict.
    """
    changes: dict[str, list[str]] = {}

    for section in SECTIONS:
        old = old_state.get(section)
        new = new_state.get(section)
        if old is None and new is not None:
            changes[section] = [f"`{section}` 섹션 신규 생성"]
            continue
        if old == new:
            continue

        msgs: list[str] = []
        # 리스트 비교 (skills, projects, scripts, services)
        for list_key in ("skills", "projects", "scripts", "running_services"):
            old_list = (old or {}).get(list_key, []) if isinstance(old, dict) else []
            new_list = (new or {}).get(list_key, []) if isinstance(new, dict) else []
            added = sorted(set(new_list) - set(old_list))
            removed = sorted(set(old_list) - set(new_list))
            for item in added:
                msgs.append(f"`{list_key}` 추가: `{item}`")
            for item in removed:
                msgs.append(f"`{list_key}` 삭제: `{item}`")

        # 그 외 단순 변경
        if not msgs and old != new:
            msgs.append(f"`{section}` 데이터 변경")

        if msgs:
            changes[section] = msgs

    return changes


# ══════════════════════════════════════════════════════════════════════════════
# 변경 로그 기록
# ══════════════════════════════════════════════════════════════════════════════


def append_changelog(changes: dict[str, list[str]]) -> None:
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
    lines = [f"## [{now}] 변경 내역"]
    for section, msgs in changes.items():
        for msg in msgs:
            lines.append(f"- [{section}] {msg}")
    lines.append("")  # 빈 줄 구분

    CHANGELOG_FILE.parent.mkdir(parents=True, exist_ok=True)

    existing = ""
    if CHANGELOG_FILE.exists():
        with CHANGELOG_FILE.open(encoding="utf-8") as f:
            existing = f.read()

    with CHANGELOG_FILE.open("w", encoding="utf-8") as f:
        f.write("\n".join(lines) + "\n" + existing)


# ══════════════════════════════════════════════════════════════════════════════
# 메인
# ══════════════════════════════════════════════════════════════════════════════

COLLECTORS = {
    "skills": collect_skills,
    "cron": collect_cron,
    "services": collect_services,
    "projects": collect_projects,
    "scripts": collect_scripts,
    "task_stats": collect_task_stats,
}


def main() -> int:
    errors: list[str] = []
    new_state: dict = {}

    # 1. 각 섹션 데이터 수집
    print("[update-system-spec] 데이터 수집 시작...")
    for section in SECTIONS:
        try:
            result = COLLECTORS[section]()
            new_state[section] = result
            print(f"  [OK] {section}")
        except Exception as e:
            errors.append(f"{section}: {e}")
            print(f"  [FAIL] {section}: {e}", file=sys.stderr)

    if not new_state:
        print("[update-system-spec] 모든 수집 단계 실패. 종료.", file=sys.stderr)
        return 2

    # 2. 변경 감지
    old_state = load_cache()
    changes = detect_changes(old_state, new_state)

    if not changes:
        print("[update-system-spec] 변경 없음 - 파일 업데이트 생략.")
        return 0 if not errors else 1

    print(f"[update-system-spec] 변경 감지: {list(changes.keys())}")

    # 3. spec 파일 읽기 (없으면 빈 파일로 시작)
    SPEC_FILE.parent.mkdir(parents=True, exist_ok=True)
    if SPEC_FILE.exists():
        with SPEC_FILE.open(encoding="utf-8") as f:
            content = f.read()
    else:
        content = "# Anu System Spec\n\n_자동 생성된 시스템 명세서_\n"

    # 4. 변경된 섹션만 업데이트
    for section in changes:
        if section not in new_state:
            continue
        body = RENDERERS[section](new_state[section])
        content = update_section_in_spec(content, section, body)

    with SPEC_FILE.open("w", encoding="utf-8") as f:
        f.write(content)
    print(f"[update-system-spec] {SPEC_FILE} 업데이트 완료.")

    # 5. 캐시 저장 (성공한 섹션만 병합)
    merged_state = {**old_state, **new_state}
    save_cache(merged_state)

    # 6. 변경 로그 기록
    append_changelog(changes)
    print(f"[update-system-spec] 변경 로그 기록 완료: {CHANGELOG_FILE}")

    return 0 if not errors else 1


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