"""Atomic file-write utilities for netanel-core state files.

All state files (eval_state.json, learning_state.json, tool_scores.json,
tool_history.jsonl) use the same write-then-rename pattern to prevent
data corruption on process crash (BUG-033).

Pattern: write to temp file in the same directory → fsync → rename.
Path.replace() maps to rename(2) on POSIX, which is atomic: the file
is either the old version or the new version — never a partial write.
"""

from __future__ import annotations

import contextlib
import json
import logging
import os
import tempfile
from pathlib import Path
from typing import Any

logger = logging.getLogger(__name__)


def atomic_write_json(path: Path, data: dict[str, Any]) -> None:
    """Write *data* as JSON to *path* atomically.

    Uses a temp file in the same directory followed by an atomic rename so
    that a crash mid-write leaves the original file intact.

    Args:
        path: Destination file path (must be a regular file, not a directory).
        data: JSON-serialisable dict to write.

    Raises:
        OSError: If the write or rename fails.
    """
    path.parent.mkdir(parents=True, exist_ok=True)
    content = json.dumps(data, indent=2)
    fd, tmp = tempfile.mkstemp(dir=path.parent, suffix=".tmp", text=True)
    try:
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            f.write(content)
        Path(tmp).replace(path)
    except BaseException:
        # Clean up the temp file on any error (including KeyboardInterrupt).
        with contextlib.suppress(OSError):
            os.unlink(tmp)
        raise


def atomic_write_text(path: Path, content: str) -> None:
    """Write *content* as plain text to *path* atomically.

    Same temp-file-then-rename pattern as :func:`atomic_write_json` but
    accepts a pre-serialised string (e.g. for JSONL rewrites).

    Args:
        path: Destination file path.
        content: Text content to write (UTF-8 encoded).

    Raises:
        OSError: If the write or rename fails.
    """
    path.parent.mkdir(parents=True, exist_ok=True)
    fd, tmp = tempfile.mkstemp(dir=path.parent, suffix=".tmp", text=True)
    try:
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            f.write(content)
        Path(tmp).replace(path)
    except BaseException:
        with contextlib.suppress(OSError):
            os.unlink(tmp)
        raise
