// Copyright (c) 2024-present, fjall-rs
// This source code is licensed under both the Apache 2.0 and MIT License
// (found in the LICENSE-* files in the repository)

use crate::{SeqNo, UserKey, ValueType};

#[derive(Clone, Eq)]
pub struct InternalKey {
    pub user_key: UserKey,
    pub seqno: SeqNo,
    pub value_type: ValueType,
}

impl PartialEq for InternalKey {
    fn eq(&self, other: &Self) -> bool {
        self.user_key == other.user_key && self.seqno == other.seqno
    }
}

#[cfg_attr(coverage_nightly, coverage(off))]
impl std::fmt::Debug for InternalKey {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{:?}:{}:{}",
            self.user_key,
            self.seqno,
            match self.value_type {
                ValueType::Value => "V",
                ValueType::Tombstone => "T",
                ValueType::WeakTombstone => "W",
            },
        )
    }
}

impl InternalKey {
    pub fn new<K: Into<UserKey>>(user_key: K, seqno: SeqNo, value_type: ValueType) -> Self {
        let user_key = user_key.into();

        assert!(
            u16::try_from(user_key.len()).is_ok(),
            "keys can be 65535 bytes in length",
        );

        Self {
            user_key,
            seqno,
            value_type,
        }
    }

    pub fn is_tombstone(&self) -> bool {
        self.value_type.is_tombstone()
    }
}

impl PartialOrd for InternalKey {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

// Order by user key ascending, THEN by sequence number descending
// This is one of the most important functions
// Otherwise queries will not match expected behaviour
impl Ord for InternalKey {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        (&self.user_key, other.seqno).cmp(&(&other.user_key, self.seqno))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use test_log::test;

    #[test]
    fn key_order_smoke_test() {
        use ValueType::Value as V;

        let mut keys = [
            InternalKey::new(b"d", 3, V),
            InternalKey::new(b"a", 0, V),
            InternalKey::new(b"b", 0, V),
            InternalKey::new(b"a", 2, V),
            InternalKey::new(b"b", 1, V),
            InternalKey::new(b"a", 1, V),
            InternalKey::new(b"c", 3, V),
        ];
        keys.sort();

        assert_eq!(
            [
                InternalKey::new(b"a", 2, V),
                InternalKey::new(b"a", 1, V),
                InternalKey::new(b"a", 0, V),
                InternalKey::new(b"b", 1, V),
                InternalKey::new(b"b", 0, V),
                InternalKey::new(b"c", 3, V),
                InternalKey::new(b"d", 3, V),
            ],
            keys,
        );
    }
}
