// Copyright 2023 Greptime Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

pub mod kafka;
pub mod raft_engine;

use std::time::Duration;

use serde::{Deserialize, Serialize};

use crate::config::kafka::common::{
    DEFAULT_AUTO_PRUNE_INTERVAL, DEFAULT_AUTO_PRUNE_LOGICAL_DELETE, DEFAULT_AUTO_PRUNE_PARALLELISM,
    DEFAULT_CHECKPOINT_TRIGGER_SIZE, DEFAULT_FLUSH_TRIGGER_SIZE,
    DEFAULT_PERIODIC_CHECKPOINT_PERSIST_INTERVAL, DEFAULT_REGION_FLUSH_TRIGGER_INTERVAL,
};
use crate::config::kafka::{DatanodeKafkaConfig, MetasrvKafkaConfig};
use crate::config::raft_engine::RaftEngineConfig;
use crate::error::{Error, UnsupportedWalProviderSnafu};

/// Wal configurations for metasrv.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(tag = "provider", rename_all = "snake_case")]
#[allow(clippy::large_enum_variant)]
pub enum MetasrvWalConfig {
    #[default]
    RaftEngine,
    Kafka(MetasrvKafkaConfig),
}

#[allow(clippy::large_enum_variant)]
/// Wal configurations for datanode.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(tag = "provider", rename_all = "snake_case")]
pub enum DatanodeWalConfig {
    RaftEngine(RaftEngineConfig),
    Kafka(DatanodeKafkaConfig),
    Noop,
}

impl Default for DatanodeWalConfig {
    fn default() -> Self {
        Self::RaftEngine(RaftEngineConfig::default())
    }
}

impl TryFrom<DatanodeWalConfig> for MetasrvWalConfig {
    type Error = Error;

    fn try_from(config: DatanodeWalConfig) -> Result<Self, Self::Error> {
        match config {
            DatanodeWalConfig::RaftEngine(_) => Ok(Self::RaftEngine),
            DatanodeWalConfig::Kafka(config) => Ok(Self::Kafka(MetasrvKafkaConfig {
                connection: config.connection,
                kafka_topic: config.kafka_topic,
                auto_create_topics: config.auto_create_topics,
                // This field won't be used in standalone mode
                auto_prune_interval: DEFAULT_AUTO_PRUNE_INTERVAL,
                // This field won't be used in standalone mode
                auto_prune_logical_delete: DEFAULT_AUTO_PRUNE_LOGICAL_DELETE,
                // This field won't be used in standalone mode
                auto_prune_parallelism: DEFAULT_AUTO_PRUNE_PARALLELISM,
                // This field won't be used in standalone mode
                flush_trigger_size: DEFAULT_FLUSH_TRIGGER_SIZE,
                // This field won't be used in standalone mode
                checkpoint_trigger_size: DEFAULT_CHECKPOINT_TRIGGER_SIZE,
                // This field won't be used in standalone mode
                region_flush_trigger_interval: DEFAULT_REGION_FLUSH_TRIGGER_INTERVAL,
                // This field won't be used in standalone mode
                periodic_checkpoint_persist_interval: DEFAULT_PERIODIC_CHECKPOINT_PERSIST_INTERVAL,
            })),
            DatanodeWalConfig::Noop => UnsupportedWalProviderSnafu {
                provider: "noop".to_string(),
            }
            .fail(),
        }
    }
}

impl MetasrvWalConfig {
    /// Returns if active wal pruning is enabled.
    pub fn enable_active_wal_pruning(&self) -> bool {
        match self {
            MetasrvWalConfig::RaftEngine => false,
            MetasrvWalConfig::Kafka(config) => config.auto_prune_interval > Duration::ZERO,
        }
    }

    /// Gets the kafka connection config.
    pub fn remote_wal_options(&self) -> Option<&MetasrvKafkaConfig> {
        match self {
            MetasrvWalConfig::RaftEngine => None,
            MetasrvWalConfig::Kafka(config) => Some(config),
        }
    }
}

impl From<MetasrvWalConfig> for DatanodeWalConfig {
    fn from(config: MetasrvWalConfig) -> Self {
        match config {
            MetasrvWalConfig::RaftEngine => Self::RaftEngine(RaftEngineConfig::default()),
            MetasrvWalConfig::Kafka(config) => Self::Kafka(DatanodeKafkaConfig {
                connection: config.connection,
                kafka_topic: config.kafka_topic,
                ..Default::default()
            }),
        }
    }
}

#[cfg(test)]
mod tests {
    use std::time::Duration;

    use common_base::readable_size::ReadableSize;
    use kafka::common::{
        KafkaClientSasl, KafkaClientSaslConfig, KafkaClientTls, KafkaConnectionConfig,
    };
    use tests::kafka::common::KafkaTopicConfig;

    use super::*;
    use crate::TopicSelectorType;
    use crate::config::{DatanodeKafkaConfig, MetasrvKafkaConfig};

    #[test]
    fn test_toml_raft_engine() {
        // With none configs.
        let toml_str = r#"
            provider = "raft_engine"
        "#;
        let metasrv_wal_config: MetasrvWalConfig = toml::from_str(toml_str).unwrap();
        assert_eq!(metasrv_wal_config, MetasrvWalConfig::RaftEngine);

        let datanode_wal_config: DatanodeWalConfig = toml::from_str(toml_str).unwrap();
        assert_eq!(
            datanode_wal_config,
            DatanodeWalConfig::RaftEngine(RaftEngineConfig::default())
        );

        // With useless configs.
        let toml_str = r#"
            provider = "raft_engine"
            broker_endpoints = ["127.0.0.1:9092"]
            num_topics = 32
        "#;
        let datanode_wal_config: DatanodeWalConfig = toml::from_str(toml_str).unwrap();
        assert_eq!(
            datanode_wal_config,
            DatanodeWalConfig::RaftEngine(RaftEngineConfig::default())
        );

        // With some useful configs.
        let toml_str = r#"
            provider = "raft_engine"
            file_size = "4MB"
            purge_threshold = "1GB"
            purge_interval = "5mins"
        "#;
        let datanode_wal_config: DatanodeWalConfig = toml::from_str(toml_str).unwrap();
        let expected = RaftEngineConfig {
            file_size: ReadableSize::mb(4),
            purge_threshold: ReadableSize::gb(1),
            purge_interval: Duration::from_secs(5 * 60),
            ..Default::default()
        };
        assert_eq!(datanode_wal_config, DatanodeWalConfig::RaftEngine(expected));
    }

    #[test]
    fn test_toml_kafka() {
        let toml_str = r#"
            provider = "kafka"
            broker_endpoints = ["127.0.0.1:9092"]
            max_batch_bytes = "1MB"
            consumer_wait_timeout = "100ms"
            num_topics = 32
            num_partitions = 1
            selector_type = "round_robin"
            replication_factor = 1
            create_topic_timeout = "30s"
            topic_name_prefix = "greptimedb_wal_topic"
            [tls]
            server_ca_cert_path = "/path/to/server.pem"
            [sasl]
            type = "SCRAM-SHA-512"
            username = "hi"
            password = "test"
        "#;

        // Deserialized to MetasrvWalConfig.
        let metasrv_wal_config: MetasrvWalConfig = toml::from_str(toml_str).unwrap();
        let expected = MetasrvKafkaConfig {
            connection: KafkaConnectionConfig {
                broker_endpoints: vec!["127.0.0.1:9092".to_string()],
                sasl: Some(KafkaClientSasl {
                    config: KafkaClientSaslConfig::ScramSha512 {
                        username: "hi".to_string(),
                        password: "test".to_string(),
                    },
                }),
                tls: Some(KafkaClientTls {
                    server_ca_cert_path: Some("/path/to/server.pem".to_string()),
                    client_cert_path: None,
                    client_key_path: None,
                }),
                connect_timeout: Duration::from_secs(3),
                timeout: Duration::from_secs(3),
            },
            kafka_topic: KafkaTopicConfig {
                num_topics: 32,
                selector_type: TopicSelectorType::RoundRobin,
                topic_name_prefix: "greptimedb_wal_topic".to_string(),
                num_partitions: 1,
                replication_factor: 1,
                create_topic_timeout: Duration::from_secs(30),
            },
            auto_create_topics: true,
            auto_prune_interval: Duration::from_mins(30),
            auto_prune_logical_delete: false,
            auto_prune_parallelism: 10,
            flush_trigger_size: ReadableSize::mb(512),
            checkpoint_trigger_size: ReadableSize::mb(128),
            region_flush_trigger_interval: Duration::from_secs(60),
            periodic_checkpoint_persist_interval: Duration::from_secs(60 * 60),
        };
        assert_eq!(metasrv_wal_config, MetasrvWalConfig::Kafka(expected));

        // Deserialized to DatanodeWalConfig.
        let datanode_wal_config: DatanodeWalConfig = toml::from_str(toml_str).unwrap();
        let expected = DatanodeKafkaConfig {
            connection: KafkaConnectionConfig {
                broker_endpoints: vec!["127.0.0.1:9092".to_string()],
                sasl: Some(KafkaClientSasl {
                    config: KafkaClientSaslConfig::ScramSha512 {
                        username: "hi".to_string(),
                        password: "test".to_string(),
                    },
                }),
                tls: Some(KafkaClientTls {
                    server_ca_cert_path: Some("/path/to/server.pem".to_string()),
                    client_cert_path: None,
                    client_key_path: None,
                }),
                connect_timeout: Duration::from_secs(3),
                timeout: Duration::from_secs(3),
            },
            max_batch_bytes: ReadableSize::mb(1),
            consumer_wait_timeout: Duration::from_millis(100),
            kafka_topic: KafkaTopicConfig {
                num_topics: 32,
                selector_type: TopicSelectorType::RoundRobin,
                topic_name_prefix: "greptimedb_wal_topic".to_string(),
                num_partitions: 1,
                replication_factor: 1,
                create_topic_timeout: Duration::from_secs(30),
            },
            ..Default::default()
        };
        assert_eq!(datanode_wal_config, DatanodeWalConfig::Kafka(expected));
    }

    #[test]
    fn test_kafka_wal_config_debug_redacts_password() {
        let config = MetasrvWalConfig::Kafka(MetasrvKafkaConfig {
            connection: KafkaConnectionConfig {
                sasl: Some(KafkaClientSasl {
                    config: KafkaClientSaslConfig::Plain {
                        username: "greptime".to_string(),
                        password: "kafka-secret".to_string(),
                    },
                }),
                ..Default::default()
            },
            ..Default::default()
        });

        let debug = format!("{config:#?}");

        assert!(debug.contains("greptime"));
        assert!(debug.contains("<REDACTED>"));
        assert!(!debug.contains("kafka-secret"));
    }
}
