"""Deterministic and transactional contracts for local model checkpoints."""

from __future__ import annotations

import json
import logging
import os
import shutil
import stat
import tempfile
import uuid
from collections.abc import Mapping
from contextlib import contextmanager
from pathlib import Path
from typing import Any, Callable, Iterator, Protocol


logger = logging.getLogger(__name__)

if os.name == "nt":  # pragma: no cover - exercised by the Windows CI lane
    _DIRECTORY_SYNC_FLAGS: int | None = None
else:
    _DIRECTORY_SYNC_FLAGS = os.O_RDONLY | os.O_DIRECTORY


def _file_sync_flags(platform_name: str, binary_flag: int) -> int:
    """Return flags for a descriptor that the platform can durably flush."""
    if platform_name == "nt":
        return os.O_RDWR | binary_flag
    return os.O_RDONLY


_FILE_SYNC_FLAGS = _file_sync_flags(os.name, getattr(os, "O_BINARY", 0))


class SizedTensor(Protocol):
    """Structural subset used to estimate a serialized state dictionary."""

    def numel(self) -> int: ...

    def element_size(self) -> int: ...


def state_dict_size_bytes(state_dict: Mapping[str, SizedTensor]) -> int:
    """Return the exact unsharded tensor payload size for a state dictionary."""
    return sum(value.numel() * value.element_size() for value in state_dict.values())


def required_checkpoint_bytes(payload_bytes: int) -> int:
    """Return the checkpoint capacity requirement with ten percent headroom."""
    if payload_bytes < 0:
        raise ValueError("checkpoint payload size cannot be negative")
    return (payload_bytes * 11 + 9) // 10


def ensure_checkpoint_capacity(
    free_bytes: int,
    payload_bytes: int,
) -> None:
    """Reject a checkpoint write that cannot satisfy the headroom policy."""
    if free_bytes < required_checkpoint_bytes(payload_bytes):
        raise OSError(
            f"Insufficient disk space: {free_bytes / 1e9:.1f} GB free, "
            f"need ~{payload_bytes / 1e9:.1f} GB. "
            f"Try a different --output-dir on a larger filesystem.",
        )


def serialize_checkpoint_metadata(metadata: Mapping[str, Any]) -> str:
    """Serialize checkpoint metadata before any model artifact is written."""
    return json.dumps(metadata, indent=2, sort_keys=True, allow_nan=False)


def _read_json_object(path: Path, artifact_name: str) -> Mapping[str, Any]:
    """Load a required, non-empty JSON object from a checkpoint."""
    if path.is_symlink() or not path.is_file() or path.stat().st_size == 0:
        raise ValueError(f"Checkpoint {artifact_name} is missing or empty: {path}")
    try:
        value = json.loads(path.read_bytes().decode())
    except (OSError, UnicodeError, json.JSONDecodeError) as error:
        raise ValueError(f"Checkpoint {artifact_name} is corrupt: {path}") from error
    if not isinstance(value, Mapping):
        raise ValueError(f"Checkpoint {artifact_name} must contain a JSON object: {path}")
    return value


def _require_nonempty_regular_file(path: Path, artifact_name: str) -> None:
    """Reject absent, empty, or link-backed checkpoint payloads."""
    if (
        path.is_symlink()
        or not path.is_file()
        or not stat.S_ISREG(path.stat().st_mode)
        or path.stat().st_size == 0
    ):
        raise ValueError(f"Checkpoint {artifact_name} is missing or empty: {path}")


def _validate_checkpoint_artifacts(
    checkpoint_dir: Path,
    expected_metadata_json: str | None,
) -> Path:
    """Validate local checkpoint identity and reload-critical artifacts."""
    checkpoint_dir = Path(checkpoint_dir)
    if checkpoint_dir.is_symlink() or not checkpoint_dir.is_dir():
        raise FileNotFoundError(f"Checkpoint path is not a directory: {checkpoint_dir}")

    actual_metadata = _read_json_object(
        checkpoint_dir / "abliteration_metadata.json",
        "metadata",
    )
    if expected_metadata_json is not None:
        expected_metadata = json.loads(expected_metadata_json)
        if actual_metadata != expected_metadata:
            raise ValueError("Checkpoint metadata does not match the prepared transaction")

    _read_json_object(checkpoint_dir / "config.json", "model config")
    _read_json_object(checkpoint_dir / "tokenizer_config.json", "tokenizer config")

    direct_weights = [
        checkpoint_dir / "model.safetensors",
        checkpoint_dir / "pytorch_model.bin",
    ]
    for weights_path in direct_weights:
        if _path_exists(weights_path):
            _require_nonempty_regular_file(weights_path, "weights")
            return checkpoint_dir.resolve()

    index_paths = [
        checkpoint_dir / "model.safetensors.index.json",
        checkpoint_dir / "pytorch_model.bin.index.json",
    ]
    for index_path in index_paths:
        if not _path_exists(index_path):
            continue
        index = _read_json_object(index_path, "weight index")
        weight_map = index.get("weight_map")
        if not isinstance(weight_map, Mapping) or not weight_map:
            raise ValueError(f"Checkpoint weight index has no weight map: {index_path}")
        shard_values = list(weight_map.values())
        if not all(isinstance(name, str) and Path(name).name == name for name in shard_values):
            raise ValueError(f"Checkpoint weight index contains an unsafe shard path: {index_path}")
        shard_names = set(shard_values)
        for shard_name in shard_names:
            _require_nonempty_regular_file(
                checkpoint_dir / shard_name,
                "weight shard",
            )
        return checkpoint_dir.resolve()

    raise ValueError(f"Checkpoint has no model weights: {checkpoint_dir}")


def validate_local_checkpoint(
    checkpoint_dir: Path,
    expected_metadata_json: str,
) -> None:
    """Validate a staging checkpoint against its prepared transaction."""
    try:
        _validate_checkpoint_artifacts(checkpoint_dir, expected_metadata_json)
    except FileNotFoundError as error:
        raise ValueError(
            f"Checkpoint staging path is not a directory: {Path(checkpoint_dir)}",
        ) from error


def validate_reloadable_checkpoint(checkpoint_dir: Path | str) -> Path:
    """Return an absolute local checkpoint path only when it is reloadable."""
    return _validate_checkpoint_artifacts(Path(checkpoint_dir), None)


def _path_exists(path: Path) -> bool:
    """Return whether a path or dangling symlink occupies ``path``."""
    return path.exists() or path.is_symlink()


def _remove_checkpoint_path(path: Path) -> None:
    """Remove a staging/backup path without following directory symlinks."""
    if path.is_symlink() or path.is_file():
        path.unlink(missing_ok=True)
    elif path.exists():
        try:
            shutil.rmtree(path)
        except FileNotFoundError:
            pass


def _sync_file(path: Path) -> None:
    """Flush one regular checkpoint artifact to stable storage."""
    mode = path.lstat().st_mode
    if not stat.S_ISREG(mode):
        raise OSError(f"Checkpoint artifact is not a regular file: {path}")
    descriptor = os.open(path, _FILE_SYNC_FLAGS)
    try:
        os.fsync(descriptor)
    finally:
        os.close(descriptor)


def _sync_directory(path: Path) -> None:
    """Persist directory entries where the platform exposes directory fsync."""
    if _DIRECTORY_SYNC_FLAGS is None:
        return
    descriptor = os.open(path, _DIRECTORY_SYNC_FLAGS)
    try:
        os.fsync(descriptor)
    finally:
        os.close(descriptor)


def _sync_checkpoint_tree(checkpoint_dir: Path) -> None:
    """Flush checkpoint files and directories from leaves to root."""
    for root, directory_names, file_names in os.walk(
        checkpoint_dir,
        topdown=False,
        followlinks=False,
    ):
        root_path = Path(root)
        for file_name in sorted(file_names):
            _sync_file(root_path / file_name)
        for directory_name in sorted(directory_names):
            directory = root_path / directory_name
            if directory.is_symlink():
                raise OSError(
                    f"Checkpoint directory contains a symlink: {directory}",
                )
            _sync_directory(directory)
        _sync_directory(root_path)


@contextmanager
def _checkpoint_lock(destination: Path) -> Iterator[None]:
    """Serialize commits to one destination across threads and processes."""
    lock_path = destination.with_name(f".{destination.name}.lock")
    with lock_path.open("a+b") as lock_file:
        if os.name == "nt":  # pragma: no cover - exercised by the Windows CI lane
            import msvcrt

            lock_file.seek(0, os.SEEK_END)
            if lock_file.tell() == 0:
                lock_file.write(b"\0")
                lock_file.flush()
            lock_file.seek(0)
            msvcrt.locking(lock_file.fileno(), msvcrt.LK_LOCK, 1)
            try:
                yield
            finally:
                lock_file.seek(0)
                msvcrt.locking(lock_file.fileno(), msvcrt.LK_UNLCK, 1)
        else:
            import fcntl

            fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX)
            try:
                yield
            finally:
                fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)


def _rollback_checkpoint_commit(
    destination: Path,
    staging: Path,
    backup: Path | None,
    *,
    had_destination: bool,
) -> None:
    """Restore the pre-commit state after a failed or cancelled commit."""
    if backup is not None and _path_exists(backup):
        if _path_exists(destination):
            if _path_exists(staging):
                raise RuntimeError(
                    "Checkpoint rollback found both destination and staging; "
                    f"recover the previous checkpoint from {backup}",
                )
            os.replace(destination, staging)
        os.replace(backup, destination)
    elif not had_destination and _path_exists(destination) and not _path_exists(staging):
        os.replace(destination, staging)
    _sync_directory(destination.parent)


def _cleanup_failed_staging(staging: Path) -> None:
    """Remove failed staging without masking the transaction's primary error."""
    try:
        _remove_checkpoint_path(staging)
    except OSError as cleanup_error:
        logger.warning(
            "Checkpoint write failed, and staging directory %s could not be "
            "removed: %s",
            staging,
            cleanup_error,
        )


@contextmanager
def atomic_checkpoint_directory(
    destination: Path,
    *,
    validate: Callable[[Path], None] | None = None,
) -> Iterator[Path]:
    """Yield a sibling staging directory and atomically promote it on success.

    An existing checkpoint is moved to a uniquely named backup immediately
    before promotion. Concurrent commits to the same destination are serialized.
    Validation and durable file flushes happen before the commit lock is taken.
    If commit or parent-directory sync fails, the previous destination is
    restored. Cancellation and other ``BaseException`` failures follow the same
    rollback path as ordinary exceptions.
    """
    destination = Path(destination)
    destination.parent.mkdir(parents=True, exist_ok=True)
    staging = Path(
        tempfile.mkdtemp(
            prefix=f".{destination.name or 'checkpoint'}.staging-",
            dir=destination.parent,
        ),
    )
    backup: Path | None = None
    try:
        yield staging
        if validate is not None:
            validate(staging)
        _sync_checkpoint_tree(staging)
        with _checkpoint_lock(destination):
            had_destination = _path_exists(destination)
            try:
                if had_destination:
                    backup = destination.with_name(
                        f".{destination.name}.backup-{uuid.uuid4().hex}",
                    )
                    os.replace(destination, backup)
                    _sync_directory(destination.parent)
                os.replace(staging, destination)
                _sync_directory(destination.parent)
            except BaseException:
                try:
                    _rollback_checkpoint_commit(
                        destination,
                        staging,
                        backup,
                        had_destination=had_destination,
                    )
                except BaseException as restore_error:
                    recovery_path = backup if backup is not None else staging
                    raise RuntimeError(
                        "Checkpoint promotion and rollback both failed; "
                        f"recover the previous checkpoint from {recovery_path}",
                    ) from restore_error
                raise
            if backup is not None:
                try:
                    _remove_checkpoint_path(backup)
                    _sync_directory(destination.parent)
                except OSError as cleanup_error:
                    logger.warning(
                        "Checkpoint promoted, but previous-checkpoint backup %s "
                        "could not be removed durably: %s",
                        backup,
                        cleanup_error,
                    )
    except BaseException:
        _cleanup_failed_staging(staging)
        raise
