#!/usr/bin/env python3
"""
Cross-platform atomic JSON file write.

Usage::

    from src.atomic_write import atomic_write_json

    atomic_write_json(path, data)           # JSON dict
    atomic_write_json(path, data, indent=2) # custom indent

Guarantees:
  - Temp file with PID + timestamp suffix (avoids collision across processes)
  - os.fsync before replace (durable on crash)
  - os.replace for atomic rename (no partial reads)
  - Retry on PermissionError (3 attempts, 0.2s backoff)
  - Fallback to direct write if tempfile fails
"""

from __future__ import annotations

import json
import os
import tempfile
import time
from pathlib import Path
from typing import Any


def atomic_write_json(
    path: str | Path,
    data: dict[str, Any] | list[Any],
    indent: int = 2,
) -> bool:
    """Atomically write JSON-serializable data to *path*.

    Returns True on success, False if all attempts (including fallback) failed.
    """
    target = Path(path)
    target.parent.mkdir(parents=True, exist_ok=True)

    content = json.dumps(data, ensure_ascii=False, indent=indent)

    # `active_instances.json` is a legacy compatibility file that the GUI can touch
    # during cold start. Prefer a simple overwrite there to avoid temp-file churn
    # blocking startup when the environment is hostile to rapid tmp creation.
    if target.name == "active_instances.json":
        try:
            target.write_text(content, encoding="utf-8")
            return True
        except Exception:
            pass

    for attempt in range(3):
        try:
            pid = os.getpid()
            ts = int(time.time() * 1_000_000)
            suffix = f".{pid}.{ts}.tmp"
            fd, tmp_path = tempfile.mkstemp(
                suffix=suffix, prefix=target.name + ".", dir=str(target.parent)
            )
            try:
                with os.fdopen(fd, "w", encoding="utf-8") as f:
                    f.write(content)
                    f.flush()
                    os.fsync(f.fileno())
                os.replace(tmp_path, str(target))
                return True
            except Exception:
                # Clean up temp file on write failure
                try:
                    os.unlink(tmp_path)
                except OSError:
                    pass
                raise
        except PermissionError:
            if attempt < 2:
                time.sleep(0.2)
            continue
        except Exception:
            if attempt < 2:
                time.sleep(0.1)
            continue

    # Fallback: direct write (best-effort, not atomic)
    try:
        target.write_text(content, encoding="utf-8")
        return True
    except Exception:
        return False
