//! Python `uuid` runtime types.
//!
//! This module provides two heap-backed runtime objects used by the stdlib
//! `uuid` module:
//! - `UUID` values with CPython-compatible attribute access (`hex`, `int`,
//!   `bytes`, `variant`, etc.).
//! - `SafeUUID` enum-member values (`safe`, `unsafe`, `unknown`).

use std::{borrow::Cow, cmp::Ordering, fmt::Write};

use num_bigint::BigInt;
use num_traits::ToPrimitive;

use crate::{
    args::ArgValues,
    defer_drop,
    exception_private::{ExcType, RunResult, SimpleException},
    heap::{DropWithHeap, Heap, HeapData, HeapId},
    intern::{Interns, StringId},
    resource::ResourceTracker,
    types::{AttrCallResult, Bytes, LongInt, PyTrait, Type, allocate_tuple},
    value::{EitherStr, Value},
};

/// Bit mask for clearing UUID version and variant bits.
const UUID_CLEARFLAGS_MASK: u128 = !((0xf000u128 << 64) | (0xc000u128 << 48));
/// RFC 4122 variant bits encoded at the standard position.
const RFC_4122_VARIANT_BITS: u128 = 0x8000u128 << 48;

/// Name displayed for the NCS UUID variant.
pub(crate) const VARIANT_RESERVED_NCS: &str = "reserved for NCS compatibility";
/// Name displayed for the RFC 4122 UUID variant.
pub(crate) const VARIANT_RFC_4122: &str = "specified in RFC 4122";
/// Name displayed for the Microsoft UUID variant.
pub(crate) const VARIANT_RESERVED_MICROSOFT: &str = "reserved for Microsoft compatibility";
/// Name displayed for the future-reserved UUID variant.
pub(crate) const VARIANT_RESERVED_FUTURE: &str = "reserved for future definition";

/// Safety marker for UUID generation sources.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub(crate) enum SafeUuidKind {
    /// Generated by a source known to be safe.
    Safe,
    /// Generated by a source known to be unsafe.
    Unsafe,
    /// Safety is unknown.
    Unknown,
}

impl SafeUuidKind {
    /// Returns the enum member suffix name.
    #[must_use]
    pub const fn member_name(self) -> &'static str {
        match self {
            Self::Safe => "safe",
            Self::Unsafe => "unsafe",
            Self::Unknown => "unknown",
        }
    }

    /// Returns the display representation used by `str()`/`repr()`.
    #[must_use]
    pub const fn display(self) -> &'static str {
        match self {
            Self::Safe => "SafeUUID.safe",
            Self::Unsafe => "SafeUUID.unsafe",
            Self::Unknown => "SafeUUID.unknown",
        }
    }
}

/// A `SafeUUID` enum-member value.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub(crate) struct SafeUuid {
    kind: SafeUuidKind,
}

impl SafeUuid {
    /// Creates a new safe-uuid enum member.
    #[must_use]
    pub const fn new(kind: SafeUuidKind) -> Self {
        Self { kind }
    }

    /// Returns the enum member kind.
    #[must_use]
    pub const fn kind(self) -> SafeUuidKind {
        self.kind
    }

    /// Allocates this value on the heap.
    pub fn to_value(self, heap: &mut Heap<impl ResourceTracker>) -> Result<Value, crate::resource::ResourceError> {
        let id = heap.allocate(HeapData::SafeUuid(self))?;
        Ok(Value::Ref(id))
    }
}

/// A UUID value with associated safety metadata.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub(crate) struct Uuid {
    value: u128,
    is_safe: SafeUuidKind,
}

impl Uuid {
    /// Creates a UUID object from a 128-bit integer and safety metadata.
    #[must_use]
    pub const fn from_u128(value: u128, is_safe: SafeUuidKind) -> Self {
        Self { value, is_safe }
    }

    /// Returns the underlying 128-bit UUID integer.
    #[must_use]
    pub const fn as_u128(self) -> u128 {
        self.value
    }

    /// Returns the safety metadata for this UUID.
    #[must_use]
    pub const fn is_safe_kind(self) -> SafeUuidKind {
        self.is_safe
    }

    /// Allocates this UUID on the heap.
    pub fn to_value(self, heap: &mut Heap<impl ResourceTracker>) -> Result<Value, crate::resource::ResourceError> {
        let id = heap.allocate(HeapData::Uuid(self))?;
        Ok(Value::Ref(id))
    }

    /// Implements `uuid.UUID(...)` constructor parsing.
    pub fn init(heap: &mut Heap<impl ResourceTracker>, args: ArgValues, interns: &Interns) -> RunResult<Value> {
        let uuid = Self::parse_init_args(heap, args, interns)?;
        uuid.to_value(heap).map_err(Into::into)
    }

    /// Parses constructor arguments and produces a UUID value.
    fn parse_init_args(heap: &mut Heap<impl ResourceTracker>, args: ArgValues, interns: &Interns) -> RunResult<Self> {
        let (positional, kwargs) = args.into_parts();
        let positional: Vec<Value> = positional.collect();

        let positional_len = positional.len();
        if positional_len > 1 {
            positional.drop_with_heap(heap);
            kwargs.drop_with_heap(heap);
            return Err(ExcType::type_error_at_most("UUID", 1, positional_len));
        }

        let mut hex_arg = positional.into_iter().next();
        let mut bytes_arg: Option<Value> = None;
        let mut bytes_le_arg: Option<Value> = None;
        let mut fields_arg: Option<Value> = None;
        let mut int_arg: Option<Value> = None;
        let mut version_arg: Option<Value> = None;
        let mut is_safe_arg: Option<Value> = None;

        for (key, value) in kwargs {
            defer_drop!(key, heap);
            let Some(key_name) = key.as_either_str(heap) else {
                value.drop_with_heap(heap);
                hex_arg.drop_with_heap(heap);
                bytes_arg.drop_with_heap(heap);
                bytes_le_arg.drop_with_heap(heap);
                fields_arg.drop_with_heap(heap);
                int_arg.drop_with_heap(heap);
                version_arg.drop_with_heap(heap);
                is_safe_arg.drop_with_heap(heap);
                return Err(ExcType::type_error("keywords must be strings"));
            };
            let key_name = key_name.as_str(interns);
            let target = match key_name {
                "hex" => &mut hex_arg,
                "bytes" => &mut bytes_arg,
                "bytes_le" => &mut bytes_le_arg,
                "fields" => &mut fields_arg,
                "int" => &mut int_arg,
                "version" => &mut version_arg,
                "is_safe" => &mut is_safe_arg,
                _ => {
                    value.drop_with_heap(heap);
                    hex_arg.drop_with_heap(heap);
                    bytes_arg.drop_with_heap(heap);
                    bytes_le_arg.drop_with_heap(heap);
                    fields_arg.drop_with_heap(heap);
                    int_arg.drop_with_heap(heap);
                    version_arg.drop_with_heap(heap);
                    is_safe_arg.drop_with_heap(heap);
                    return Err(ExcType::type_error_unexpected_keyword("UUID", key_name));
                }
            };
            if target.is_some() {
                value.drop_with_heap(heap);
                hex_arg.drop_with_heap(heap);
                bytes_arg.drop_with_heap(heap);
                bytes_le_arg.drop_with_heap(heap);
                fields_arg.drop_with_heap(heap);
                int_arg.drop_with_heap(heap);
                version_arg.drop_with_heap(heap);
                is_safe_arg.drop_with_heap(heap);
                return Err(ExcType::type_error_multiple_values("UUID", key_name));
            }
            *target = Some(value);
        }

        let source_count = usize::from(hex_arg.is_some())
            + usize::from(bytes_arg.is_some())
            + usize::from(bytes_le_arg.is_some())
            + usize::from(fields_arg.is_some())
            + usize::from(int_arg.is_some());
        if source_count != 1 {
            hex_arg.drop_with_heap(heap);
            bytes_arg.drop_with_heap(heap);
            bytes_le_arg.drop_with_heap(heap);
            fields_arg.drop_with_heap(heap);
            int_arg.drop_with_heap(heap);
            version_arg.drop_with_heap(heap);
            is_safe_arg.drop_with_heap(heap);
            return Err(SimpleException::new_msg(
                ExcType::TypeError,
                "one of the hex, bytes, bytes_le, fields, or int arguments must be given",
            )
            .into());
        }

        let mut uuid_value = if let Some(hex_value) = hex_arg {
            let parsed = parse_uuid_text(&extract_string_like(heap, interns, &hex_value, "UUID", "hex")?)?;
            hex_value.drop_with_heap(heap);
            parsed
        } else if let Some(bytes_value) = bytes_arg {
            let bytes = extract_bytes_16(heap, interns, &bytes_value, "UUID", "bytes")?;
            bytes_value.drop_with_heap(heap);
            u128::from_be_bytes(bytes)
        } else if let Some(bytes_le_value) = bytes_le_arg {
            let bytes_le = extract_bytes_16(heap, interns, &bytes_le_value, "UUID", "bytes_le")?;
            bytes_le_value.drop_with_heap(heap);
            u128::from_be_bytes(bytes_le_to_be(bytes_le))
        } else if let Some(fields_value) = fields_arg {
            let parsed = parse_fields_value(heap, &fields_value)?;
            fields_value.drop_with_heap(heap);
            parsed
        } else {
            let Some(int_value) = int_arg else {
                return Err(SimpleException::new_msg(
                    ExcType::RuntimeError,
                    "UUID() constructor source argument resolution failed",
                )
                .into());
            };
            let parsed = value_to_u128(heap, &int_value, "UUID", "int")?;
            int_value.drop_with_heap(heap);
            parsed
        };

        if let Some(version_value) = version_arg {
            let version = value_to_i64(heap, &version_value, "UUID", "version")?;
            version_value.drop_with_heap(heap);
            if !(1..=8).contains(&version) {
                is_safe_arg.drop_with_heap(heap);
                return Err(SimpleException::new_msg(ExcType::ValueError, "illegal version number").into());
            }
            let version_bits = (version as u128) << 76;
            uuid_value &= UUID_CLEARFLAGS_MASK;
            uuid_value |= version_bits | RFC_4122_VARIANT_BITS;
        }

        let is_safe = if let Some(safe_value) = is_safe_arg {
            let parsed = parse_safe_uuid_value(heap, &safe_value)?;
            safe_value.drop_with_heap(heap);
            parsed
        } else {
            SafeUuidKind::Unknown
        };

        Ok(Self::from_u128(uuid_value, is_safe))
    }

    /// Returns UUID bytes in network order.
    #[must_use]
    pub const fn bytes(self) -> [u8; 16] {
        self.value.to_be_bytes()
    }

    /// Returns UUID bytes in little-endian field order.
    #[must_use]
    pub fn bytes_le(self) -> [u8; 16] {
        let bytes = self.bytes();
        let mut out = [0u8; 16];
        out[0] = bytes[3];
        out[1] = bytes[2];
        out[2] = bytes[1];
        out[3] = bytes[0];
        out[4] = bytes[5];
        out[5] = bytes[4];
        out[6] = bytes[7];
        out[7] = bytes[6];
        out[8..].copy_from_slice(&bytes[8..]);
        out
    }

    /// Returns UUID as a lowercase 32-char hex string.
    #[must_use]
    pub fn hex(self) -> String {
        format!("{:032x}", self.value)
    }

    /// Returns UUID as a hyphenated lowercase string.
    #[must_use]
    pub fn hyphenated(self) -> String {
        let time_low = self.time_low();
        let time_mid = self.time_mid();
        let time_hi_version = self.time_hi_version();
        let clock_seq_hi_variant = self.clock_seq_hi_variant();
        let clock_seq_low = self.clock_seq_low();
        let node = self.node();
        format!(
            "{time_low:08x}-{time_mid:04x}-{time_hi_version:04x}-{clock_seq_hi_variant:02x}{clock_seq_low:02x}-{node:012x}"
        )
    }

    /// Returns UUID `fields` tuple data.
    #[must_use]
    pub fn fields(self) -> (u32, u16, u16, u8, u8, u64) {
        (
            self.time_low(),
            self.time_mid(),
            self.time_hi_version(),
            self.clock_seq_hi_variant(),
            self.clock_seq_low(),
            self.node(),
        )
    }

    /// Returns UUID `time_low` field.
    #[must_use]
    pub fn time_low(self) -> u32 {
        (self.value >> 96) as u32
    }

    /// Returns UUID `time_mid` field.
    #[must_use]
    pub fn time_mid(self) -> u16 {
        ((self.value >> 80) & 0xffff) as u16
    }

    /// Returns UUID `time_hi_version` field.
    #[must_use]
    pub fn time_hi_version(self) -> u16 {
        ((self.value >> 64) & 0xffff) as u16
    }

    /// Returns UUID `clock_seq_hi_variant` field.
    #[must_use]
    pub fn clock_seq_hi_variant(self) -> u8 {
        ((self.value >> 56) & 0xff) as u8
    }

    /// Returns UUID `clock_seq_low` field.
    #[must_use]
    pub fn clock_seq_low(self) -> u8 {
        ((self.value >> 48) & 0xff) as u8
    }

    /// Returns UUID node field.
    #[must_use]
    pub fn node(self) -> u64 {
        (self.value & 0xffff_ffff_ffff) as u64
    }

    /// Returns UUID clock sequence.
    #[must_use]
    pub fn clock_seq(self) -> u16 {
        (u16::from(self.clock_seq_hi_variant() & 0x3f) << 8) | u16::from(self.clock_seq_low())
    }

    /// Returns UUID variant description.
    #[must_use]
    pub fn variant(self) -> &'static str {
        let hi = self.clock_seq_hi_variant();
        if (hi & 0x80) == 0 {
            VARIANT_RESERVED_NCS
        } else if (hi & 0x40) == 0 {
            VARIANT_RFC_4122
        } else if (hi & 0x20) == 0 {
            VARIANT_RESERVED_MICROSOFT
        } else {
            VARIANT_RESERVED_FUTURE
        }
    }

    /// Returns UUID version when RFC 4122 variant bits are set.
    #[must_use]
    pub fn version(self) -> Option<u8> {
        if self.variant() == VARIANT_RFC_4122 {
            Some(((self.time_hi_version() >> 12) & 0x0f) as u8)
        } else {
            None
        }
    }

    /// Returns UUID `time` attribute value.
    #[must_use]
    pub fn time(self) -> u64 {
        if self.version() == Some(7) {
            ((self.value >> 80) & 0xffff_ffff_ffff) as u64
        } else {
            ((u64::from(self.time_hi_version() & 0x0fff)) << 48)
                | (u64::from(self.time_mid()) << 32)
                | u64::from(self.time_low())
        }
    }

    /// Returns the Python integer hash value for this UUID.
    ///
    /// CPython defines `UUID.__hash__` as `hash(self.int)`.
    #[must_use]
    pub fn python_hash(self) -> u64 {
        python_hash_u128(self.value)
    }
}

impl PyTrait for SafeUuid {
    fn py_type(&self, _heap: &Heap<impl ResourceTracker>) -> Type {
        Type::SafeUuid
    }

    fn py_estimate_size(&self) -> usize {
        std::mem::size_of::<Self>()
    }

    fn py_len(&self, _heap: &Heap<impl ResourceTracker>, _interns: &Interns) -> Option<usize> {
        None
    }

    fn py_eq(&self, other: &Self, _heap: &mut Heap<impl ResourceTracker>, _interns: &Interns) -> bool {
        self.kind == other.kind
    }

    fn py_cmp(&self, other: &Self, _heap: &mut Heap<impl ResourceTracker>, _interns: &Interns) -> Option<Ordering> {
        self.kind.member_name().partial_cmp(other.kind.member_name())
    }

    fn py_dec_ref_ids(&mut self, _stack: &mut Vec<HeapId>) {}

    fn py_bool(&self, _heap: &Heap<impl ResourceTracker>, _interns: &Interns) -> bool {
        true
    }

    fn py_repr_fmt(
        &self,
        f: &mut impl Write,
        _heap: &Heap<impl ResourceTracker>,
        _heap_ids: &mut ahash::AHashSet<HeapId>,
        _interns: &Interns,
    ) -> std::fmt::Result {
        f.write_str(self.kind.display())
    }

    fn py_str(&self, _heap: &Heap<impl ResourceTracker>, _interns: &Interns) -> Cow<'static, str> {
        Cow::Borrowed(self.kind.display())
    }
}

impl PyTrait for Uuid {
    fn py_type(&self, _heap: &Heap<impl ResourceTracker>) -> Type {
        Type::Uuid
    }

    fn py_estimate_size(&self) -> usize {
        std::mem::size_of::<Self>()
    }

    fn py_len(&self, _heap: &Heap<impl ResourceTracker>, _interns: &Interns) -> Option<usize> {
        None
    }

    fn py_eq(&self, other: &Self, _heap: &mut Heap<impl ResourceTracker>, _interns: &Interns) -> bool {
        self.value == other.value
    }

    fn py_cmp(&self, other: &Self, _heap: &mut Heap<impl ResourceTracker>, _interns: &Interns) -> Option<Ordering> {
        self.value.partial_cmp(&other.value)
    }

    fn py_dec_ref_ids(&mut self, _stack: &mut Vec<HeapId>) {}

    fn py_bool(&self, _heap: &Heap<impl ResourceTracker>, _interns: &Interns) -> bool {
        true
    }

    fn py_repr_fmt(
        &self,
        f: &mut impl Write,
        _heap: &Heap<impl ResourceTracker>,
        _heap_ids: &mut ahash::AHashSet<HeapId>,
        _interns: &Interns,
    ) -> std::fmt::Result {
        write!(f, "UUID('{}')", self.hyphenated())
    }

    fn py_str(&self, _heap: &Heap<impl ResourceTracker>, _interns: &Interns) -> Cow<'static, str> {
        Cow::Owned(self.hyphenated())
    }

    fn py_getattr(
        &self,
        attr_id: StringId,
        heap: &mut Heap<impl ResourceTracker>,
        interns: &Interns,
    ) -> RunResult<Option<AttrCallResult>> {
        let attr_name = interns.get_str(attr_id);
        let value = match attr_name {
            "bytes" => {
                let id = heap.allocate(HeapData::Bytes(Bytes::from(self.bytes().to_vec())))?;
                Value::Ref(id)
            }
            "bytes_le" => {
                let id = heap.allocate(HeapData::Bytes(Bytes::from(self.bytes_le().to_vec())))?;
                Value::Ref(id)
            }
            "hex" => {
                let id = heap.allocate(HeapData::Str(self.hex().into()))?;
                Value::Ref(id)
            }
            "int" => LongInt::new(BigInt::from(self.value)).into_value(heap)?,
            "urn" => {
                let urn = format!("urn:uuid:{}", self.hyphenated());
                let id = heap.allocate(HeapData::Str(urn.into()))?;
                Value::Ref(id)
            }
            "variant" => {
                let id = heap.allocate(HeapData::Str(self.variant().into()))?;
                Value::Ref(id)
            }
            "version" => self.version().map_or(Value::None, |v| Value::Int(i64::from(v))),
            "is_safe" => SafeUuid::new(self.is_safe).to_value(heap)?,
            "fields" => {
                let (time_low, time_mid, time_hi_version, clock_seq_hi_variant, clock_seq_low, node) = self.fields();
                allocate_tuple(
                    smallvec::smallvec![
                        Value::Int(i64::from(time_low)),
                        Value::Int(i64::from(time_mid)),
                        Value::Int(i64::from(time_hi_version)),
                        Value::Int(i64::from(clock_seq_hi_variant)),
                        Value::Int(i64::from(clock_seq_low)),
                        Value::Int(node as i64),
                    ],
                    heap,
                )?
            }
            "time_low" => Value::Int(i64::from(self.time_low())),
            "time_mid" => Value::Int(i64::from(self.time_mid())),
            "time_hi_version" => Value::Int(i64::from(self.time_hi_version())),
            "clock_seq_hi_variant" => Value::Int(i64::from(self.clock_seq_hi_variant())),
            "clock_seq_low" => Value::Int(i64::from(self.clock_seq_low())),
            "node" => Value::Int(self.node() as i64),
            "time" => Value::Int(self.time() as i64),
            "clock_seq" => Value::Int(i64::from(self.clock_seq())),
            _ => return Ok(None),
        };
        Ok(Some(AttrCallResult::Value(value)))
    }

    fn py_call_attr(
        &mut self,
        heap: &mut Heap<impl ResourceTracker>,
        attr: &EitherStr,
        args: ArgValues,
        interns: &Interns,
        _self_id: Option<HeapId>,
    ) -> RunResult<Value> {
        args.drop_with_heap(heap);
        Err(ExcType::attribute_error("UUID", attr.as_str(interns)))
    }
}

/// Converts little-endian UUID bytes to canonical big-endian byte order.
#[must_use]
fn bytes_le_to_be(bytes_le: [u8; 16]) -> [u8; 16] {
    let mut out = [0u8; 16];
    out[0] = bytes_le[3];
    out[1] = bytes_le[2];
    out[2] = bytes_le[1];
    out[3] = bytes_le[0];
    out[4] = bytes_le[5];
    out[5] = bytes_le[4];
    out[6] = bytes_le[7];
    out[7] = bytes_le[6];
    out[8..].copy_from_slice(&bytes_le[8..]);
    out
}

/// Parses textual UUID forms (`hex`, `{hex}`, `urn:uuid:...`) into a `u128`.
fn parse_uuid_text(raw: &str) -> RunResult<u128> {
    let mut text = raw.trim();
    if text.len() >= 9 && text[..9].eq_ignore_ascii_case("urn:uuid:") {
        text = &text[9..];
    }
    if text.starts_with('{') && text.ends_with('}') && text.len() >= 2 {
        text = &text[1..text.len() - 1];
    }
    let normalized: String = text.chars().filter(|ch| *ch != '-').collect();
    if normalized.len() != 32 || !normalized.chars().all(|ch| ch.is_ascii_hexdigit()) {
        return Err(SimpleException::new_msg(ExcType::ValueError, "badly formed hexadecimal UUID string").into());
    }
    u128::from_str_radix(&normalized, 16)
        .map_err(|_| SimpleException::new_msg(ExcType::ValueError, "badly formed hexadecimal UUID string").into())
}

/// Extracts a string-like argument from a runtime value.
fn extract_string_like(
    heap: &Heap<impl ResourceTracker>,
    interns: &Interns,
    value: &Value,
    func_name: &str,
    arg_name: &str,
) -> RunResult<String> {
    match value {
        Value::InternString(id) => Ok(interns.get_str(*id).to_string()),
        Value::Ref(id) => {
            if let HeapData::Str(s) = heap.get(*id) {
                Ok(s.as_str().to_string())
            } else {
                Err(ExcType::type_error(format!(
                    "{func_name}() argument '{arg_name}' must be str, not {}",
                    value.py_type(heap)
                )))
            }
        }
        _ => Err(ExcType::type_error(format!(
            "{func_name}() argument '{arg_name}' must be str, not {}",
            value.py_type(heap)
        ))),
    }
}

/// Extracts an exact-length 16-byte argument from bytes-like input.
fn extract_bytes_16(
    heap: &Heap<impl ResourceTracker>,
    interns: &Interns,
    value: &Value,
    func_name: &str,
    arg_name: &str,
) -> RunResult<[u8; 16]> {
    let bytes = match value {
        Value::InternBytes(id) => interns.get_bytes(*id).to_vec(),
        Value::Ref(id) => match heap.get(*id) {
            HeapData::Bytes(b) => b.as_slice().to_vec(),
            _ => {
                return Err(ExcType::type_error(format!(
                    "{func_name}() argument '{arg_name}' must be bytes, not {}",
                    value.py_type(heap)
                )));
            }
        },
        _ => {
            return Err(ExcType::type_error(format!(
                "{func_name}() argument '{arg_name}' must be bytes, not {}",
                value.py_type(heap)
            )));
        }
    };
    if bytes.len() != 16 {
        return Err(SimpleException::new_msg(ExcType::ValueError, "bytes is not a 16-char string").into());
    }
    let mut out = [0u8; 16];
    out.copy_from_slice(&bytes);
    Ok(out)
}

/// Parses UUID `fields=(...)` tuple into a UUID integer.
fn parse_fields_value(heap: &Heap<impl ResourceTracker>, value: &Value) -> RunResult<u128> {
    let elements: Vec<Value> = match value {
        Value::Ref(id) => match heap.get(*id) {
            HeapData::Tuple(tuple) => tuple.as_vec().iter().map(Value::copy_for_extend).collect(),
            _ => {
                return Err(ExcType::type_error(format!(
                    "UUID() argument 'fields' must be a 6-tuple, not {}",
                    value.py_type(heap)
                )));
            }
        },
        _ => {
            return Err(ExcType::type_error(format!(
                "UUID() argument 'fields' must be a 6-tuple, not {}",
                value.py_type(heap)
            )));
        }
    };
    if elements.len() != 6 {
        return Err(SimpleException::new_msg(ExcType::ValueError, "fields is not a 6-tuple").into());
    }
    let time_low = value_to_u64(heap, &elements[0], "UUID", "time_low")? & 0xffff_ffff;
    let time_mid = value_to_u64(heap, &elements[1], "UUID", "time_mid")? & 0xffff;
    let time_hi_version = value_to_u64(heap, &elements[2], "UUID", "time_hi_version")? & 0xffff;
    let clock_seq_hi_variant = value_to_u64(heap, &elements[3], "UUID", "clock_seq_hi_variant")? & 0xff;
    let clock_seq_low = value_to_u64(heap, &elements[4], "UUID", "clock_seq_low")? & 0xff;
    let node = value_to_u64(heap, &elements[5], "UUID", "node")? & 0xffff_ffff_ffff;
    Ok((u128::from(time_low) << 96)
        | (u128::from(time_mid) << 80)
        | (u128::from(time_hi_version) << 64)
        | (u128::from(clock_seq_hi_variant) << 56)
        | (u128::from(clock_seq_low) << 48)
        | u128::from(node))
}

/// Parses `is_safe` constructor argument into `SafeUuidKind`.
fn parse_safe_uuid_value(heap: &Heap<impl ResourceTracker>, value: &Value) -> RunResult<SafeUuidKind> {
    match value {
        Value::Ref(id) => match heap.get(*id) {
            HeapData::SafeUuid(safe) => Ok(safe.kind()),
            _ => Err(ExcType::type_error(format!(
                "UUID() argument 'is_safe' must be SafeUUID, not {}",
                value.py_type(heap)
            ))),
        },
        _ => Err(ExcType::type_error(format!(
            "UUID() argument 'is_safe' must be SafeUUID, not {}",
            value.py_type(heap)
        ))),
    }
}

/// Extracts a signed integer argument from a Python value.
fn value_to_i64(heap: &Heap<impl ResourceTracker>, value: &Value, func_name: &str, arg_name: &str) -> RunResult<i64> {
    match value {
        Value::Int(i) => Ok(*i),
        Value::Bool(b) => Ok(i64::from(*b)),
        Value::Ref(id) => {
            if let HeapData::LongInt(li) = heap.get(*id) {
                li.to_i64().ok_or_else(|| {
                    SimpleException::new_msg(
                        ExcType::OverflowError,
                        format!("{func_name}() argument '{arg_name}' is out of range"),
                    )
                    .into()
                })
            } else {
                Err(ExcType::type_error(format!(
                    "{func_name}() argument '{arg_name}' must be int, not {}",
                    value.py_type(heap)
                )))
            }
        }
        _ => Err(ExcType::type_error(format!(
            "{func_name}() argument '{arg_name}' must be int, not {}",
            value.py_type(heap)
        ))),
    }
}

/// Extracts a non-negative `u64` argument from a Python value.
fn value_to_u64(heap: &Heap<impl ResourceTracker>, value: &Value, func_name: &str, arg_name: &str) -> RunResult<u64> {
    let parsed = value_to_i64(heap, value, func_name, arg_name)?;
    if parsed < 0 {
        return Err(SimpleException::new_msg(
            ExcType::ValueError,
            format!("{func_name}() argument '{arg_name}' must be non-negative"),
        )
        .into());
    }
    Ok(parsed as u64)
}

/// Extracts a non-negative `u128` argument from a Python value.
fn value_to_u128(heap: &Heap<impl ResourceTracker>, value: &Value, func_name: &str, arg_name: &str) -> RunResult<u128> {
    match value {
        Value::Int(i) => {
            if *i < 0 {
                Err(SimpleException::new_msg(
                    ExcType::ValueError,
                    format!("{func_name}() argument '{arg_name}' must be non-negative"),
                )
                .into())
            } else {
                Ok(*i as u128)
            }
        }
        Value::Bool(b) => Ok(u128::from(*b)),
        Value::Ref(id) => {
            if let HeapData::LongInt(li) = heap.get(*id) {
                let bi = li.inner();
                if bi.sign() == num_bigint::Sign::Minus {
                    return Err(SimpleException::new_msg(
                        ExcType::ValueError,
                        format!("{func_name}() argument '{arg_name}' must be non-negative"),
                    )
                    .into());
                }
                bi.to_u128().ok_or_else(|| {
                    SimpleException::new_msg(
                        ExcType::OverflowError,
                        format!("{func_name}() argument '{arg_name}' is out of range"),
                    )
                    .into()
                })
            } else {
                Err(ExcType::type_error(format!(
                    "{func_name}() argument '{arg_name}' must be int, not {}",
                    value.py_type(heap)
                )))
            }
        }
        _ => Err(ExcType::type_error(format!(
            "{func_name}() argument '{arg_name}' must be int, not {}",
            value.py_type(heap)
        ))),
    }
}

/// Computes CPython-compatible `hash(int_value)` for non-negative 128-bit integers.
#[must_use]
fn python_hash_u128(value: u128) -> u64 {
    const PY_HASH_MODULUS: u128 = (1u128 << 61) - 1;
    const PYLONG_SHIFT: u32 = 30;
    const PYLONG_MASK: u128 = (1u128 << PYLONG_SHIFT) - 1;

    if value == 0 {
        return 0;
    }

    let mut digits = smallvec::SmallVec::<[u32; 5]>::new();
    let mut remaining = value;
    while remaining != 0 {
        digits.push((remaining & PYLONG_MASK) as u32);
        remaining >>= PYLONG_SHIFT;
    }

    let mut hash = 0u128;
    for digit in digits.iter().rev() {
        hash = ((hash << PYLONG_SHIFT) + u128::from(*digit)) % PY_HASH_MODULUS;
    }

    let mut hash_i64 = hash as i64;
    if hash_i64 == -1 {
        hash_i64 = -2;
    }
    u64::from_ne_bytes(hash_i64.to_ne_bytes())
}
