"""체크포인트(백업) 관리 유틸리티 (M-08).

타임스탬프 기반 백업 디렉토리에 파일 스냅샷을 저장하고 복원한다.
기본 저장 경로: ~/.hermes/checkpoints/ (없으면 /tmp/hermes_checkpoints/)
파일 이름 형식: {YYYYMMDD_HHMMSS_ffffff}__{label}__{original_filename}

Usage:
    from utils.checkpoint import snapshot, restore, list_checkpoints, cleanup_old
    path = snapshot("myfile.py", label="before_refactor")
    restore(path, "myfile.py")
"""

from __future__ import annotations

from datetime import datetime
from pathlib import Path
from typing import Union

from utils.atomic_write import atomic_text_write

_DEFAULT_CHECKPOINT_DIR: Path = Path.home() / ".hermes" / "checkpoints"
_FALLBACK_CHECKPOINT_DIR: Path = Path("/tmp/hermes_checkpoints")
_SEP: str = "__"


def _get_default_checkpoint_dir() -> Path:
    """~/.hermes/checkpoints/ 반환. 실패 시 /tmp/hermes_checkpoints/ 사용."""
    try:
        _DEFAULT_CHECKPOINT_DIR.mkdir(parents=True, exist_ok=True)
        return _DEFAULT_CHECKPOINT_DIR
    except OSError:
        _FALLBACK_CHECKPOINT_DIR.mkdir(parents=True, exist_ok=True)
        return _FALLBACK_CHECKPOINT_DIR


def _resolve_ckpt_dir(checkpoint_dir: Union[str, Path, None]) -> Path:
    """checkpoint_dir 인수를 Path로 변환하고, None이면 기본값 반환."""
    return Path(checkpoint_dir) if checkpoint_dir is not None else _get_default_checkpoint_dir()


def _make_backup_filename(original_path: Path, label: str) -> str:
    """형식: {YYYYMMDD_HHMMSS_ffffff}__{label}__{original_filename}."""
    ts = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
    safe_label = label.strip() if label.strip() else "nolabel"
    return f"{ts}{_SEP}{safe_label}{_SEP}{original_path.name}"


def _parse_backup_filename(filename: str) -> tuple[str, str] | None:
    """파일명에서 (timestamp_str, label) 파싱. 실패 시 None."""
    parts = filename.split(_SEP, 2)
    if len(parts) < 3:
        return None
    return parts[0], parts[1]


def _get_file_subdir(file_path: Path, checkpoint_dir: Path) -> Path:
    """파일별 서브디렉토리 경로 반환."""
    return checkpoint_dir / file_path.name


def snapshot(
    file_path: Union[str, Path],
    label: str = "",
    checkpoint_dir: Union[str, Path, None] = None,
) -> Path:
    """파일을 체크포인트에 백업한다.

    Args:
        file_path:      백업할 원본 파일 경로.
        label:          체크포인트 레이블 (선택).
        checkpoint_dir: 저장 디렉토리. None이면 기본 경로 사용.

    Returns:
        생성된 백업 파일 경로.

    Raises:
        FileNotFoundError: file_path가 존재하지 않을 때.
    """
    source = Path(file_path)
    if not source.exists():
        raise FileNotFoundError(f"스냅샷 대상 파일이 없습니다: {source}")

    ckpt_dir = _resolve_ckpt_dir(checkpoint_dir)
    subdir = _get_file_subdir(source, ckpt_dir)
    subdir.mkdir(parents=True, exist_ok=True)

    backup_path = subdir / _make_backup_filename(source, label)
    atomic_text_write(backup_path, source.read_text(encoding="utf-8", errors="replace"))
    return backup_path


def restore(
    checkpoint_path: Union[str, Path],
    target_path: Union[str, Path],
) -> None:
    """체크포인트 파일을 대상 경로로 복원한다.

    Args:
        checkpoint_path: 복원할 체크포인트 파일 경로.
        target_path:     복원 대상 경로.

    Raises:
        FileNotFoundError: checkpoint_path가 존재하지 않을 때.
    """
    src = Path(checkpoint_path)
    if not src.exists():
        raise FileNotFoundError(f"체크포인트 파일이 없습니다: {src}")
    atomic_text_write(Path(target_path), src.read_text(encoding="utf-8", errors="replace"))


def list_checkpoints(
    file_path: Union[str, Path],
    checkpoint_dir: Union[str, Path, None] = None,
) -> list[dict[str, object]]:
    """특정 파일의 체크포인트 목록을 최신순으로 반환한다.

    Args:
        file_path:      원본 파일 경로 (파일명 기준으로 검색).
        checkpoint_dir: 저장 디렉토리. None이면 기본 경로 사용.

    Returns:
        dict 리스트 (최신순). 각 dict 키: path, label, timestamp, original_path
    """
    source = Path(file_path)
    ckpt_dir = _resolve_ckpt_dir(checkpoint_dir)
    subdir = _get_file_subdir(source, ckpt_dir)

    if not subdir.exists():
        return []

    entries: list[dict[str, object]] = []
    for backup_file in subdir.iterdir():
        if not backup_file.is_file():
            continue
        parsed = _parse_backup_filename(backup_file.name)
        if parsed is None:
            continue
        ts_str, label = parsed
        entries.append(
            {
                "path": backup_file,
                "label": label if label != "nolabel" else "",
                "timestamp": ts_str,
                "original_path": source,
            }
        )

    entries.sort(key=lambda e: str(e["timestamp"]), reverse=True)
    return entries


def cleanup_old(
    file_path: Union[str, Path],
    keep: int = 10,
    checkpoint_dir: Union[str, Path, None] = None,
) -> int:
    """오래된 체크포인트를 정리한다.

    Args:
        file_path:      원본 파일 경로.
        keep:           유지할 최신 체크포인트 수 (기본 10).
        checkpoint_dir: 저장 디렉토리. None이면 기본 경로 사용.

    Returns:
        삭제된 체크포인트 수.
    """
    entries = list_checkpoints(file_path, checkpoint_dir=checkpoint_dir)
    deleted = 0
    for entry in entries[keep:]:
        try:
            Path(str(entry["path"])).unlink()
            deleted += 1
        except OSError:
            pass
    return deleted
