"""원자적 파일 쓰기 유틸리티.

temp 파일 생성 → fsync → os.replace() 순서로 POSIX 원자적 쓰기를 구현합니다.
프로세스가 쓰기 도중 충돌하더라도 대상 파일은 항상 완전한 이전 버전 또는
완전한 새 버전을 유지합니다 (부분 쓰기 상태 없음).

BaseException 캐치로 KeyboardInterrupt / SystemExit 발생 시에도 임시 파일을
반드시 정리합니다.

Usage:
    from utils.atomic_write import atomic_json_write, atomic_yaml_write, atomic_text_write

    atomic_json_write("config.json", {"key": "value"})
    atomic_yaml_write("config.yaml", {"key": "value"})
    atomic_text_write("output.txt", "hello world")
"""

import json
import os
import tempfile
from pathlib import Path
from typing import Any, Union


def atomic_json_write(
    path: Union[str, Path],
    data: Any,
    *,
    indent: int = 2,
    **dump_kwargs: Any,
) -> None:
    """JSON 데이터를 대상 경로에 원자적으로 씁니다.

    Args:
        path: 대상 파일 경로. 없으면 생성, 있으면 원자적으로 교체.
        data: JSON 직렬화 가능한 데이터.
        indent: JSON 들여쓰기 (기본값 2).
        **dump_kwargs: json.dump()에 전달할 추가 키워드 인수 (예: default=str).

    Raises:
        TypeError: data가 JSON 직렬화 불가능한 경우.
        OSError: 파일시스템 오류 발생 시.
    """
    target = Path(path)
    target.parent.mkdir(parents=True, exist_ok=True)

    fd, tmp_path = tempfile.mkstemp(
        dir=str(target.parent),
        prefix=f".{target.stem}_",
        suffix=".tmp",
    )
    try:
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            json.dump(data, f, indent=indent, ensure_ascii=False, **dump_kwargs)
            f.flush()
            os.fsync(f.fileno())
        os.replace(tmp_path, target)
    except BaseException:
        # KeyboardInterrupt / SystemExit 포함 모든 예외 시 임시 파일 정리 후 재발생
        try:
            os.unlink(tmp_path)
        except OSError:
            pass
        raise


def atomic_yaml_write(
    path: Union[str, Path],
    data: Any,
    *,
    default_flow_style: bool = False,
    sort_keys: bool = False,
) -> None:
    """YAML 데이터를 대상 경로에 원자적으로 씁니다.

    Args:
        path: 대상 파일 경로. 없으면 생성, 있으면 원자적으로 교체.
        data: YAML 직렬화 가능한 데이터.
        default_flow_style: YAML flow 스타일 사용 여부 (기본값 False).
        sort_keys: 딕셔너리 키 정렬 여부 (기본값 False).

    Raises:
        ImportError: PyYAML(yaml 패키지)이 설치되지 않은 경우.
        OSError: 파일시스템 오류 발생 시.
    """
    import yaml  # 선택적 의존성: yaml 미설치 시 ImportError 발생

    target = Path(path)
    target.parent.mkdir(parents=True, exist_ok=True)

    fd, tmp_path = tempfile.mkstemp(
        dir=str(target.parent),
        prefix=f".{target.stem}_",
        suffix=".tmp",
    )
    try:
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            yaml.dump(data, f, default_flow_style=default_flow_style, sort_keys=sort_keys)
            f.flush()
            os.fsync(f.fileno())
        os.replace(tmp_path, target)
    except BaseException:
        try:
            os.unlink(tmp_path)
        except OSError:
            pass
        raise


def atomic_text_write(
    path: Union[str, Path],
    text: str,
    *,
    encoding: str = "utf-8",
) -> None:
    """텍스트를 대상 경로에 원자적으로 씁니다.

    Args:
        path: 대상 파일 경로. 없으면 생성, 있으면 원자적으로 교체.
        text: 쓸 문자열 내용.
        encoding: 파일 인코딩 (기본값 'utf-8').

    Raises:
        OSError: 파일시스템 오류 발생 시.
    """
    target = Path(path)
    target.parent.mkdir(parents=True, exist_ok=True)

    fd, tmp_path = tempfile.mkstemp(
        dir=str(target.parent),
        prefix=f".{target.stem}_",
        suffix=".tmp",
    )
    try:
        with os.fdopen(fd, "w", encoding=encoding) as f:
            f.write(text)
            f.flush()
            os.fsync(f.fileno())
        os.replace(tmp_path, target)
    except BaseException:
        try:
            os.unlink(tmp_path)
        except OSError:
            pass
        raise
