"""Atomic I/O utilities for safe file operations.

Provides atomic write and safe load functions to prevent data corruption
during file operations. Uses temp file + os.replace() for atomicity.
"""

import contextlib
import json
import os
import tempfile
from typing import Any


def atomic_write_json(filepath: str, data: Any, indent: int = 2) -> None:
    """Atomically write JSON to file using temp + os.replace().

    Args:
        filepath: Target file path
        data: Data to serialize as JSON
        indent: JSON indentation level (default: 2)

    Raises:
        OSError: If file operations fail
        TypeError: If data is not JSON-serializable

    """
    dir_path = os.path.dirname(filepath) or "."
    os.makedirs(dir_path, exist_ok=True)

    fd, temp_path = tempfile.mkstemp(dir=dir_path, prefix=".tmp_", suffix=".json")
    os.chmod(temp_path, 0o600)

    try:
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            json.dump(data, f, indent=indent, ensure_ascii=False)
            f.flush()
            os.fsync(f.fileno())

        os.replace(temp_path, filepath)
    except (OSError, PermissionError, TypeError):
        with contextlib.suppress(OSError, PermissionError):
            os.unlink(temp_path)
        raise


def atomic_write_text(filepath: str, content: str) -> None:
    """Atomically write text to file using temp + os.replace().

    Args:
        filepath: Target file path
        content: Text content to write
    """
    dir_path = os.path.dirname(filepath) or "."
    os.makedirs(dir_path, exist_ok=True)

    fd, temp_path = tempfile.mkstemp(dir=dir_path, prefix=".tmp_", suffix=".txt")
    os.chmod(temp_path, 0o600)

    try:
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            f.write(content)
            f.flush()
            os.fsync(f.fileno())

        os.replace(temp_path, filepath)
    except (OSError, PermissionError):
        with contextlib.suppress(OSError, PermissionError):
            os.unlink(temp_path)
        raise


def safe_load_json(filepath: str, default: Any = None) -> Any:
    """Load JSON with validation. Returns default if corrupted.

    Args:
        filepath: Path to JSON file
        default: Default value if file missing/corrupted (default: {})

    Returns:
        Loaded JSON data or default value

    Note:
        Corrupted files are backed up to {filepath}.corrupted

    """
    if default is None:
        default = {}

    if not os.path.exists(filepath):
        return default

    try:
        with open(filepath, encoding="utf-8") as f:
            data = json.load(f)
            if not isinstance(data, (dict, list)):
                raise ValueError("Invalid JSON structure")
            return data
    except (json.JSONDecodeError, ValueError, OSError):
        backup_path = f"{filepath}.corrupted"
        with contextlib.suppress(OSError, PermissionError):
            os.rename(filepath, backup_path)
        return default
