// Copyright 2026 TiKV Project Authors. Licensed under Apache-2.0.
// Modifications Copyright 2026 PingCAP, Inc.

// These public names mirror the removed RocksDB binding so existing config
// code does not need a broad, conflict-prone rename.
#![allow(clippy::upper_case_acronyms)]

//! Serialization-compatible representations of legacy RocksDB configuration.
//!
//! Cloud Storage Engine no longer constructs a RocksDB instance, but it keeps
//! accepting and emitting the historical `[rocksdb]` and `[raftdb]` settings.
//! These types deliberately mirror the names and serde behavior of the RocksDB
//! bindings without linking those bindings into the server.
//!
//! This compatibility-only module is intentionally not assigned a separate
//! configuration CODEOWNER: its values no longer tune a production engine.
//! Changes here should preserve the historical serialization contract instead.

use std::{convert::TryFrom, str::FromStr};

use online_config::ConfigValue;
use serde::{Deserialize, Serialize};
use tikv_util::numeric_enum_serializing_mod;

#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum LogLevel {
    Error,
    Fatal,
    Info,
    Warn,
    Debug,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum CompressionType {
    No,
    Snappy,
    Zlib,
    Bz2,
    Lz4,
    Lz4hc,
    Zstd,
    ZstdNotFinal,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum DBCompressionType {
    No,
    Snappy,
    Zlib,
    Bz2,
    Lz4,
    Lz4hc,
    Zstd,
    ZstdNotFinal,
    Disable,
}

impl DBCompressionType {
    /// Converts a legacy RocksDB compression setting into an SST writer
    /// compression supported by Cloud Storage Engine.
    pub fn to_sst_compression_type(self) -> Option<engine_traits::SstCompressionType> {
        use engine_traits::SstCompressionType;

        match self {
            Self::Lz4 => Some(SstCompressionType::Lz4),
            Self::Snappy => Some(SstCompressionType::Snappy),
            Self::Zstd => Some(SstCompressionType::Zstd),
            _ => None,
        }
    }
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum DBCompactionStyle {
    Level,
    Universal,
    Fifo,
    None,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum DBRecoveryMode {
    TolerateCorruptedTailRecords,
    AbsoluteConsistency,
    PointInTime,
    SkipAnyCorruptedRecords,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum CompactionPriority {
    ByCompensatedSize,
    OldestLargestSeqFirst,
    OldestSmallestSeqFirst,
    MinOverlappingRatio,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum DBRateLimiterMode {
    ReadOnly,
    WriteOnly,
    AllIo,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum PrepopulateBlockCache {
    Disabled,
    FlushOnly,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ChecksumType {
    NoChecksum,
    CRC32c,
    XxHash,
    XxHash64,
    XXH3,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum BlobRunMode {
    Normal,
    ReadOnly,
    Fallback,
}

impl From<BlobRunMode> for ConfigValue {
    fn from(mode: BlobRunMode) -> Self {
        let value = match mode {
            BlobRunMode::Normal => "normal",
            BlobRunMode::ReadOnly => "read-only",
            BlobRunMode::Fallback => "fallback",
        };
        ConfigValue::String(value.into())
    }
}

impl TryFrom<ConfigValue> for BlobRunMode {
    type Error = String;

    fn try_from(value: ConfigValue) -> Result<Self, Self::Error> {
        if let ConfigValue::String(value) = value {
            Self::from_str(&value)
        } else {
            panic!("expect ConfigValue::String, got {:?}", value);
        }
    }
}

impl FromStr for BlobRunMode {
    type Err = String;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        match value {
            "normal" => Ok(Self::Normal),
            "read-only" => Ok(Self::ReadOnly),
            "fallback" => Ok(Self::Fallback),
            value => Err(format!(
                "expect normal, read-only or fallback, got {:?}",
                value
            )),
        }
    }
}

pub mod compression_type_level_serde {
    use std::fmt;

    use serde::{
        Deserializer, Serializer,
        de::{Error, SeqAccess, Unexpected, Visitor},
        ser::SerializeSeq,
    };

    use super::DBCompressionType;

    pub fn serialize<S>(types: &[DBCompressionType; 7], serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut sequence = serializer.serialize_seq(Some(types.len()))?;
        for compression_type in types {
            sequence.serialize_element(super::compression_type_name(*compression_type))?;
        }
        sequence.end()
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<[DBCompressionType; 7], D::Error>
    where
        D: Deserializer<'de>,
    {
        struct SeqVisitor;

        impl<'de> Visitor<'de> for SeqVisitor {
            type Value = [DBCompressionType; 7];

            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
                write!(formatter, "a compression type vector")
            }

            fn visit_seq<S>(self, mut sequence: S) -> Result<Self::Value, S::Error>
            where
                S: SeqAccess<'de>,
            {
                let mut types = [DBCompressionType::No; 7];
                let mut index = 0;
                while let Some(value) = sequence.next_element::<String>()? {
                    if index == types.len() {
                        return Err(S::Error::invalid_value(
                            Unexpected::Str(&value),
                            &"only 7 compression types",
                        ));
                    }
                    types[index] = super::parse_compression_type(&value).ok_or_else(|| {
                        S::Error::invalid_value(
                            Unexpected::Str(&value),
                            &"invalid compression type",
                        )
                    })?;
                    index += 1;
                }
                if index != types.len() {
                    return Err(S::Error::invalid_length(index, &"7 compression types"));
                }
                Ok(types)
            }
        }

        deserializer.deserialize_seq(SeqVisitor)
    }
}

pub mod compression_type_serde {
    use std::fmt;

    use serde::{
        Deserializer, Serializer,
        de::{Error, Unexpected, Visitor},
    };

    use super::DBCompressionType;

    pub fn serialize<S>(value: &DBCompressionType, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(super::compression_type_name(*value))
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<DBCompressionType, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct StringVisitor;

        impl Visitor<'_> for StringVisitor {
            type Value = DBCompressionType;

            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
                write!(formatter, "a compression type")
            }

            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
            where
                E: Error,
            {
                super::parse_compression_type(value).ok_or_else(|| {
                    E::invalid_value(Unexpected::Other("invalid compression type"), &self)
                })
            }
        }

        deserializer.deserialize_str(StringVisitor)
    }
}

fn compression_type_name(value: DBCompressionType) -> &'static str {
    match value {
        DBCompressionType::No => "no",
        DBCompressionType::Snappy => "snappy",
        DBCompressionType::Zlib => "zlib",
        DBCompressionType::Bz2 => "bzip2",
        DBCompressionType::Lz4 => "lz4",
        DBCompressionType::Lz4hc => "lz4hc",
        DBCompressionType::Zstd => "zstd",
        DBCompressionType::ZstdNotFinal => "zstd-not-final",
        DBCompressionType::Disable => "disable",
    }
}

fn parse_compression_type(value: &str) -> Option<DBCompressionType> {
    match value.trim().to_lowercase().as_str() {
        "no" => Some(DBCompressionType::No),
        "snappy" => Some(DBCompressionType::Snappy),
        "zlib" => Some(DBCompressionType::Zlib),
        "bzip2" => Some(DBCompressionType::Bz2),
        "lz4" => Some(DBCompressionType::Lz4),
        "lz4hc" => Some(DBCompressionType::Lz4hc),
        "zstd" => Some(DBCompressionType::Zstd),
        "zstd-not-final" => Some(DBCompressionType::ZstdNotFinal),
        "disable" => Some(DBCompressionType::Disable),
        _ => None,
    }
}

macro_rules! string_enum_serde {
    ($module:ident, $type:ident, $expecting:literal, {$($variant:ident => $name:literal),+ $(,)?}) => {
        pub mod $module {
            use std::fmt;

            use serde::{
                Deserializer, Serializer,
                de::{Error, Unexpected, Visitor},
            };

            use super::$type;

            pub fn serialize<S>(value: &$type, serializer: S) -> Result<S::Ok, S::Error>
            where
                S: Serializer,
            {
                let name = match value {
                    $($type::$variant => $name,)+
                };
                serializer.serialize_str(name)
            }

            pub fn deserialize<'de, D>(deserializer: D) -> Result<$type, D::Error>
            where
                D: Deserializer<'de>,
            {
                struct StringVisitor;

                impl Visitor<'_> for StringVisitor {
                    type Value = $type;

                    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
                        write!(formatter, $expecting)
                    }

                    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
                    where
                        E: Error,
                    {
                        match value.trim().to_lowercase().as_str() {
                            $($name => Ok($type::$variant),)+
                            _ => Err(E::invalid_value(Unexpected::Str(value), &self)),
                        }
                    }
                }

                deserializer.deserialize_str(StringVisitor)
            }
        }
    };
}

string_enum_serde!(checksum_serde, ChecksumType, "a checksum type", {
    NoChecksum => "no",
    CRC32c => "crc32c",
    XxHash => "xxhash",
    XxHash64 => "xxhash64",
    XXH3 => "xxh3",
});

string_enum_serde!(
    prepopulate_block_cache_serde,
    PrepopulateBlockCache,
    "a prepopulate block cache mode",
    { Disabled => "disabled", FlushOnly => "flush-only" }
);

numeric_enum_serializing_mod! {compaction_pri_serde CompactionPriority {
    ByCompensatedSize = 0,
    OldestLargestSeqFirst = 1,
    OldestSmallestSeqFirst = 2,
    MinOverlappingRatio = 3,
}}

numeric_enum_serializing_mod! {rate_limiter_mode_serde DBRateLimiterMode {
    ReadOnly = 1,
    WriteOnly = 2,
    AllIo = 3,
}}

numeric_enum_serializing_mod! {compaction_style_serde DBCompactionStyle {
    Level = 0,
    Universal = 1,
    Fifo = 2,
    None = 3,
}}

numeric_enum_serializing_mod! {recovery_mode_serde DBRecoveryMode {
    TolerateCorruptedTailRecords = 0,
    AbsoluteConsistency = 1,
    PointInTime = 2,
    SkipAnyCorruptedRecords = 3,
}}

#[cfg(test)]
mod tests {
    use serde::{Deserialize, Serialize};

    use super::*;

    #[test]
    fn test_legacy_serde_compatibility() {
        #[derive(Debug, PartialEq, Serialize, Deserialize)]
        struct Config {
            #[serde(with = "compression_type_serde")]
            compression: DBCompressionType,
            #[serde(with = "checksum_serde")]
            checksum: ChecksumType,
            #[serde(with = "compaction_style_serde")]
            style: DBCompactionStyle,
        }

        let config: Config =
            toml::from_str("compression = \"zstd\"\nchecksum = \"xxh3\"\nstyle = \"universal\"\n")
                .unwrap();
        assert_eq!(config.compression, DBCompressionType::Zstd);
        assert_eq!(config.checksum, ChecksumType::XXH3);
        assert_eq!(config.style, DBCompactionStyle::Universal);
        assert_eq!(
            toml::to_string(&config).unwrap(),
            "compression = \"zstd\"\nchecksum = \"xxh3\"\nstyle = 1\n"
        );
    }
}
