"""UUID helpers backed by uuid-utils."""

from __future__ import annotations

import uuid
import warnings
from typing import Final

import xxhash
from uuid_utils.compat import uuid7 as _uuid_utils_uuid7

_NANOS_PER_SECOND: Final = 1_000_000_000


def _to_timestamp_and_nanos(nanoseconds: int) -> tuple[int, int]:
    """Split a nanosecond timestamp into seconds and remaining nanoseconds."""
    seconds, nanos = divmod(nanoseconds, _NANOS_PER_SECOND)
    return seconds, nanos


def uuid7(nanoseconds: int | None = None) -> uuid.UUID:
    """Generate a UUID from a Unix timestamp in nanoseconds and random bits.

    UUIDv7 objects feature monotonicity within a millisecond.

    Args:
        nanoseconds: Optional ns timestamp. If not provided, uses current time.
    """
    # --- 48 ---   -- 4 --   --- 12 ---   -- 2 --   --- 30 ---   - 32 -
    # unix_ts_ms | version | counter_hi | variant | counter_lo | random
    #
    # 'counter = counter_hi | counter_lo' is a 42-bit counter constructed
    # with Method 1 of RFC 9562, §6.2, and its MSB is set to 0.
    #
    # 'random' is a 32-bit random value regenerated for every new UUID.
    #
    # If multiple UUIDs are generated within the same millisecond, the LSB
    # of 'counter' is incremented by 1. When overflowing, the timestamp is
    # advanced and the counter is reset to a random 42-bit integer with MSB
    # set to 0.

    # For now, just delegate to the uuid_utils implementation
    if nanoseconds is None:
        return _uuid_utils_uuid7()
    seconds, nanos = _to_timestamp_and_nanos(nanoseconds)
    return _uuid_utils_uuid7(timestamp=seconds, nanos=nanos)


def is_uuid_v7(uuid_obj: uuid.UUID) -> bool:
    """Check if a UUID is version 7.

    Args:
        uuid_obj: The UUID to check.

    Returns:
        True if the UUID is version 7, False otherwise.
    """
    return uuid_obj.version == 7


_UUID_V7_WARNING_EMITTED = False


def warn_if_not_uuid_v7(uuid_obj: uuid.UUID, id_type: str) -> None:
    """Warn if a UUID is not version 7.

    Args:
        uuid_obj: The UUID to check.
        id_type: The type of ID (e.g., "run_id", "trace_id") for the warning message.
    """
    global _UUID_V7_WARNING_EMITTED
    if not is_uuid_v7(uuid_obj) and not _UUID_V7_WARNING_EMITTED:
        _UUID_V7_WARNING_EMITTED = True
        warnings.warn(
            (
                "LangSmith now uses UUID v7 for run and trace identifiers. "
                "This warning appears when passing custom IDs. "
                "Please use: from langsmith import uuid7\n"
                "            id = uuid7()\n"
                "Future versions will require UUID v7."
            ),
            UserWarning,
            stacklevel=3,
        )


def uuid7_deterministic(original_id: uuid.UUID, key: str) -> uuid.UUID:
    """Generate a deterministic UUID7 derived from an original UUID and a key.

    A pure function of ``original_id`` and ``key``: it reads no ambient state, so
    the same pair always yields the same UUID, in any process, at any time. This
    is used for creating replica ids that are reproducible across distributed
    systems.

    A UUID v7 original keeps its timestamp, so derived ids stay ordered by run
    start time. Any other original has none to keep, and the field is
    hash-derived: stable, but not chronological.

    Args:
        original_id: The source UUID (ideally UUID v7 to preserve timestamp).
        key: A string key used for deterministic derivation (e.g., project name).

    Returns:
        A new UUID v7 with preserved timestamp (if original is v7) and
        deterministic random bits.

    Example:
        >>> original = uuid7()
        >>> replica_id = uuid7_deterministic(original, "replica-project")
        >>> # Same inputs always produce same output
        >>> assert uuid7_deterministic(original, "replica-project") == replica_id
    """
    # Generate deterministic bytes from XXH3-128 hash of original + key
    hash_input = f"{original_id}:{key}".encode()
    h = xxhash.xxh3_128(hash_input).digest()

    # Build new UUID7:
    # UUID7 structure (RFC 9562):
    # [0-5]  48 bits: unix_ts_ms (timestamp in milliseconds)
    # [6]    4 bits: version (0111 = 7) + 4 bits rand_a
    # [7]    8 bits: rand_a (continued)
    # [8]    2 bits: variant (10) + 6 bits rand_b
    # [9-15] 56 bits: rand_b (continued)

    b = bytearray(16)

    if is_uuid_v7(original_id):
        # Preserve timestamp from original UUID7 (bytes 0-5)
        b[0:6] = original_id.bytes[0:6]
    else:
        # No timestamp to preserve. Use the six hash bytes left unused below: a
        # clock read here would make this impure, and the same id is derived
        # from several runs, all of which must agree.
        b[0:6] = h[10:16]

    # Set version 7 (0111) in high nibble + 4 bits from hash
    b[6] = 0x70 | (h[0] & 0x0F)

    # rand_a continued (8 bits from hash)
    b[7] = h[1]

    # Set variant (10) in high 2 bits + 6 bits from hash
    b[8] = 0x80 | (h[2] & 0x3F)

    # rand_b (56 bits = 7 bytes from hash)
    b[9:16] = h[3:10]

    return uuid.UUID(bytes=bytes(b))
