/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */
package org.apache.kafka.connect.mirror;

import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.config.ConfigDef;

import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static org.apache.kafka.common.config.ConfigDef.ValidString.in;

public class MirrorSourceConfig extends MirrorConnectorConfig {

    protected static final String REFRESH_TOPICS = "refresh.topics";
    protected static final String SYNC_TOPIC_CONFIGS = "sync.topic.configs";
    protected static final String SYNC_TOPIC_ACLS = "sync.topic.acls";

    public static final String REPLICATION_FACTOR = "replication.factor";
    private static final String REPLICATION_FACTOR_DOC = "Replication factor for newly created remote topics.";
    public static final int REPLICATION_FACTOR_DEFAULT = 2;
    public static final String TOPICS = DefaultTopicFilter.TOPICS_INCLUDE_CONFIG;
    public static final String TOPICS_DEFAULT = DefaultTopicFilter.TOPICS_INCLUDE_DEFAULT;
    private static final String TOPICS_DOC = "Topics to replicate. Supports comma-separated topic names and regexes.";
    public static final String TOPICS_EXCLUDE = DefaultTopicFilter.TOPICS_EXCLUDE_CONFIG;
    public static final String TOPICS_EXCLUDE_DEFAULT = DefaultTopicFilter.TOPICS_EXCLUDE_DEFAULT;
    private static final String TOPICS_EXCLUDE_DOC = "Excluded topics. Supports comma-separated topic names and regexes."
            + " Excludes take precedence over includes.";

    public static final String CONFIG_PROPERTIES_EXCLUDE = DefaultConfigPropertyFilter.CONFIG_PROPERTIES_EXCLUDE_CONFIG;
    public static final String CONFIG_PROPERTIES_EXCLUDE_DEFAULT = DefaultConfigPropertyFilter.CONFIG_PROPERTIES_EXCLUDE_DEFAULT;
    private static final String CONFIG_PROPERTIES_EXCLUDE_DOC = "Topic config properties that should not be replicated. Supports "
            + "comma-separated property names and regexes.";

    public static final String OFFSET_SYNCS_TOPIC_REPLICATION_FACTOR = OFFSET_SYNCS_TOPIC_CONFIG_PREFIX + "replication.factor";
    public static final String OFFSET_SYNCS_TOPIC_REPLICATION_FACTOR_DOC = "Replication factor for offset-syncs topic.";
    public static final short OFFSET_SYNCS_TOPIC_REPLICATION_FACTOR_DEFAULT = 3;

    static final String TASK_TOPIC_PARTITIONS = "task.assigned.partitions";

    public static final String CONSUMER_POLL_TIMEOUT_MILLIS = "consumer.poll.timeout.ms";
    private static final String CONSUMER_POLL_TIMEOUT_MILLIS_DOC = "Timeout when polling source cluster.";
    public static final long CONSUMER_POLL_TIMEOUT_MILLIS_DEFAULT = 1000L;

    public static final String REFRESH_TOPICS_ENABLED = REFRESH_TOPICS + ENABLED_SUFFIX;
    private static final String REFRESH_TOPICS_ENABLED_DOC = "Whether to periodically check for new topics and partitions.";
    public static final boolean REFRESH_TOPICS_ENABLED_DEFAULT = true;
    public static final String REFRESH_TOPICS_INTERVAL_SECONDS = REFRESH_TOPICS + INTERVAL_SECONDS_SUFFIX;
    private static final String REFRESH_TOPICS_INTERVAL_SECONDS_DOC = "Frequency of topic refresh.";
    public static final long REFRESH_TOPICS_INTERVAL_SECONDS_DEFAULT = 10 * 60;

    public static final String SYNC_TOPIC_CONFIGS_ENABLED = SYNC_TOPIC_CONFIGS + ENABLED_SUFFIX;
    private static final String SYNC_TOPIC_CONFIGS_ENABLED_DOC = "Whether to periodically configure remote topics to match their corresponding upstream topics.";
    public static final boolean SYNC_TOPIC_CONFIGS_ENABLED_DEFAULT = true;
    public static final String SYNC_TOPIC_CONFIGS_INTERVAL_SECONDS = SYNC_TOPIC_CONFIGS + INTERVAL_SECONDS_SUFFIX;
    private static final String SYNC_TOPIC_CONFIGS_INTERVAL_SECONDS_DOC = "Frequency of topic config sync.";
    public static final long SYNC_TOPIC_CONFIGS_INTERVAL_SECONDS_DEFAULT = 10 * 60;

    public static final String SYNC_TOPIC_ACLS_ENABLED = SYNC_TOPIC_ACLS + ENABLED_SUFFIX;
    private static final String SYNC_TOPIC_ACLS_ENABLED_DOC = "Whether to periodically configure remote topic ACLs to match their corresponding upstream topics.";
    public static final boolean SYNC_TOPIC_ACLS_ENABLED_DEFAULT = true;
    public static final String SYNC_TOPIC_ACLS_INTERVAL_SECONDS = SYNC_TOPIC_ACLS + INTERVAL_SECONDS_SUFFIX;
    private static final String SYNC_TOPIC_ACLS_INTERVAL_SECONDS_DOC = "Frequency of topic ACL sync.";
    public static final long SYNC_TOPIC_ACLS_INTERVAL_SECONDS_DEFAULT = 10 * 60;

    public static final String CONFIG_PROPERTY_FILTER_CLASS = "config.property.filter.class";
    private static final String CONFIG_PROPERTY_FILTER_CLASS_DOC = "ConfigPropertyFilter to use. Selects topic config "
            + " properties to replicate.";
    public static final Class<?> CONFIG_PROPERTY_FILTER_CLASS_DEFAULT = DefaultConfigPropertyFilter.class;

    public static final String OFFSET_LAG_MAX = "offset.lag.max";
    private static final String OFFSET_LAG_MAX_DOC = "How out-of-sync a remote partition can be before it is resynced.";
    public static final long OFFSET_LAG_MAX_DEFAULT = 100L;

    public static final String HEARTBEATS_REPLICATION_ENABLED = "heartbeats.replication" + ENABLED_SUFFIX;
    private static final String HEARTBEATS_REPLICATION_ENABLED_DOC = "Whether to replicate the heartbeats topics even when the topic filter does not include them." +
            " If set to true, heartbeats topics identified by the replication policy will always be replicated, regardless of the topic filter configuration." +
            " If set to false, heartbeats topics will only be replicated if the topic filter allows.";
    public static final boolean HEARTBEATS_REPLICATION_ENABLED_DEFAULT = true;

    public static final String METRIC_NAMES_FORMAT = "metric.names.formats";
    public static final List<String> METRIC_NAMES_FORMAT_DEFAULT = List.of(METRIC_NAMES_LEGACY);
    public static final String METRIC_NAMES_FORMAT_DOC = "Deprecated. The formats in which metrics are emitted. Valid values are legacy and new. " +
            "When set to legacy, the metrics have the following name \"kafka.connect.mirror:type=MirrorSourceConnector,source={SOURCE},target={TARGET},topic={TOPIC},partition={PARTITION}\". " +
            "When set to new, the metrics have the following name \"kafka.connect:type=plugins,connector={CONNECTOR},task={TASK},source={SOURCE},target={TARGET},topic={TOPIC},partition={PARTITION}\". " +
            "When set to \"legacy,new\" the connector will emit metrics with both names, this can be useful when migrating to the new format but it doubles the amount of metrics that are emitted.\n" +
            "In Kafka 5.0 the legacy format and this configuration will be removed and the metrics will always use the new names.";

    public static final String OFFSET_SYNCS_SOURCE_PRODUCER_ROLE = OFFSET_SYNCS_CLIENT_ROLE_PREFIX + "source-producer";
    public static final String OFFSET_SYNCS_TARGET_PRODUCER_ROLE = OFFSET_SYNCS_CLIENT_ROLE_PREFIX + "target-producer";
    public static final String OFFSET_SYNCS_SOURCE_ADMIN_ROLE = OFFSET_SYNCS_CLIENT_ROLE_PREFIX + "source-admin";
    public static final String OFFSET_SYNCS_TARGET_ADMIN_ROLE = OFFSET_SYNCS_CLIENT_ROLE_PREFIX + "target-admin";

    public MirrorSourceConfig(Map<String, String> props) {
        super(CONNECTOR_CONFIG_DEF, props);
    }

    public MirrorSourceConfig(ConfigDef configDef, Map<String, String> props) {
        super(configDef, props);
    }

    Map<String, String> taskConfigForTopicPartitions(List<TopicPartition> topicPartitions, int taskIndex) {
        Map<String, String> props = originalsStrings();
        String topicPartitionsString = topicPartitions.stream()
                .map(MirrorUtils::encodeTopicPartition)
                .collect(Collectors.joining(","));
        props.put(TASK_TOPIC_PARTITIONS, topicPartitionsString);
        props.put(TASK_INDEX, String.valueOf(taskIndex));
        return props;
    }

    String offsetSyncsTopic() {
        String otherClusterAlias = SOURCE_CLUSTER_ALIAS_DEFAULT.equals(offsetSyncsTopicLocation())
                ? targetClusterAlias()
                : sourceClusterAlias();
        return replicationPolicy().offsetSyncsTopic(otherClusterAlias);
    }

    String offsetSyncsTopicLocation() {
        return getString(OFFSET_SYNCS_TOPIC_LOCATION);
    }

    Map<String, Object> offsetSyncsTopicAdminConfig() {
        return SOURCE_CLUSTER_ALIAS_DEFAULT.equals(offsetSyncsTopicLocation())
                ? sourceAdminConfig(OFFSET_SYNCS_SOURCE_ADMIN_ROLE)
                : targetAdminConfig(OFFSET_SYNCS_TARGET_ADMIN_ROLE);
    }

    Map<String, Object> offsetSyncsTopicProducerConfig() {
        return SOURCE_CLUSTER_ALIAS_DEFAULT.equals(offsetSyncsTopicLocation())
                ? sourceProducerConfig(OFFSET_SYNCS_SOURCE_PRODUCER_ROLE)
                : targetProducerConfig(OFFSET_SYNCS_TARGET_PRODUCER_ROLE);
    }

    String checkpointsTopic() {
        return replicationPolicy().checkpointsTopic(sourceClusterAlias());
    }

    long maxOffsetLag() {
        return getLong(OFFSET_LAG_MAX);
    }

    Duration refreshTopicsInterval() {
        if (getBoolean(REFRESH_TOPICS_ENABLED)) {
            return Duration.ofSeconds(getLong(REFRESH_TOPICS_INTERVAL_SECONDS));
        } else {
            // negative interval to disable
            return Duration.ofMillis(-1);
        }
    }

    Duration syncTopicConfigsInterval() {
        if (getBoolean(SYNC_TOPIC_CONFIGS_ENABLED)) {
            return Duration.ofSeconds(getLong(SYNC_TOPIC_CONFIGS_INTERVAL_SECONDS));
        } else {
            // negative interval to disable
            return Duration.ofMillis(-1);
        }
    }

    Duration syncTopicAclsInterval() {
        if (getBoolean(SYNC_TOPIC_ACLS_ENABLED)) {
            return Duration.ofSeconds(getLong(SYNC_TOPIC_ACLS_INTERVAL_SECONDS));
        } else {
            // negative interval to disable
            return Duration.ofMillis(-1);
        }
    }

    int replicationFactor() {
        return getInt(REPLICATION_FACTOR);
    }

    short offsetSyncsTopicReplicationFactor() {
        return getShort(OFFSET_SYNCS_TOPIC_REPLICATION_FACTOR);
    }

    TopicFilter topicFilter() {
        return getConfiguredInstance(TOPIC_FILTER_CLASS, TopicFilter.class);
    }

    ConfigPropertyFilter configPropertyFilter() {
        return getConfiguredInstance(CONFIG_PROPERTY_FILTER_CLASS, ConfigPropertyFilter.class);
    }

    Duration consumerPollTimeout() {
        return Duration.ofMillis(getLong(CONSUMER_POLL_TIMEOUT_MILLIS));
    }

    boolean emitOffsetSyncsEnabled() {
        return getBoolean(EMIT_OFFSET_SYNCS_ENABLED);
    }

    boolean heartbeatsReplicationEnabled() {
        return getBoolean(HEARTBEATS_REPLICATION_ENABLED);
    }

    List<String> metricNamesFormats() {
        return getList(METRIC_NAMES_FORMAT);
    }

    private static ConfigDef defineSourceConfig(ConfigDef baseConfig) {
        return baseConfig
                .define(
                        TOPICS,
                        ConfigDef.Type.LIST,
                        TOPICS_DEFAULT,
                        ConfigDef.ValidList.anyNonDuplicateValues(true, false),
                        ConfigDef.Importance.HIGH,
                        TOPICS_DOC)
                .define(
                        TOPICS_EXCLUDE,
                        ConfigDef.Type.LIST,
                        TOPICS_EXCLUDE_DEFAULT,
                        ConfigDef.ValidList.anyNonDuplicateValues(true, false),
                        ConfigDef.Importance.HIGH,
                        TOPICS_EXCLUDE_DOC)
                .define(
                        CONFIG_PROPERTIES_EXCLUDE,
                        ConfigDef.Type.LIST,
                        CONFIG_PROPERTIES_EXCLUDE_DEFAULT,
                        ConfigDef.ValidList.anyNonDuplicateValues(true, false),
                        ConfigDef.Importance.HIGH,
                        CONFIG_PROPERTIES_EXCLUDE_DOC)
                .define(
                        TOPIC_FILTER_CLASS,
                        ConfigDef.Type.CLASS,
                        TOPIC_FILTER_CLASS_DEFAULT,
                        ConfigDef.Importance.LOW,
                        TOPIC_FILTER_CLASS_DOC)
                .define(
                        CONFIG_PROPERTY_FILTER_CLASS,
                        ConfigDef.Type.CLASS,
                        CONFIG_PROPERTY_FILTER_CLASS_DEFAULT,
                        ConfigDef.Importance.LOW,
                        CONFIG_PROPERTY_FILTER_CLASS_DOC)
                .define(
                        CONSUMER_POLL_TIMEOUT_MILLIS,
                        ConfigDef.Type.LONG,
                        CONSUMER_POLL_TIMEOUT_MILLIS_DEFAULT,
                        ConfigDef.Importance.LOW,
                        CONSUMER_POLL_TIMEOUT_MILLIS_DOC)
                .define(
                        REFRESH_TOPICS_ENABLED,
                        ConfigDef.Type.BOOLEAN,
                        REFRESH_TOPICS_ENABLED_DEFAULT,
                        ConfigDef.Importance.LOW,
                        REFRESH_TOPICS_ENABLED_DOC)
                .define(
                        REFRESH_TOPICS_INTERVAL_SECONDS,
                        ConfigDef.Type.LONG,
                        REFRESH_TOPICS_INTERVAL_SECONDS_DEFAULT,
                        ConfigDef.Importance.LOW,
                        REFRESH_TOPICS_INTERVAL_SECONDS_DOC)
                .define(
                        SYNC_TOPIC_CONFIGS_ENABLED,
                        ConfigDef.Type.BOOLEAN,
                        SYNC_TOPIC_CONFIGS_ENABLED_DEFAULT,
                        ConfigDef.Importance.LOW,
                        SYNC_TOPIC_CONFIGS_ENABLED_DOC)
                .define(
                        SYNC_TOPIC_CONFIGS_INTERVAL_SECONDS,
                        ConfigDef.Type.LONG,
                        SYNC_TOPIC_CONFIGS_INTERVAL_SECONDS_DEFAULT,
                        ConfigDef.Importance.LOW,
                        SYNC_TOPIC_CONFIGS_INTERVAL_SECONDS_DOC)
                .define(
                        SYNC_TOPIC_ACLS_ENABLED,
                        ConfigDef.Type.BOOLEAN,
                        SYNC_TOPIC_ACLS_ENABLED_DEFAULT,
                        ConfigDef.Importance.LOW,
                        SYNC_TOPIC_ACLS_ENABLED_DOC)
                .define(
                        SYNC_TOPIC_ACLS_INTERVAL_SECONDS,
                        ConfigDef.Type.LONG,
                        SYNC_TOPIC_ACLS_INTERVAL_SECONDS_DEFAULT,
                        ConfigDef.Importance.LOW,
                        SYNC_TOPIC_ACLS_INTERVAL_SECONDS_DOC)
                .define(
                        REPLICATION_FACTOR,
                        ConfigDef.Type.INT,
                        REPLICATION_FACTOR_DEFAULT,
                        ConfigDef.Importance.LOW,
                        REPLICATION_FACTOR_DOC)
                .define(
                        OFFSET_SYNCS_TOPIC_REPLICATION_FACTOR,
                        ConfigDef.Type.SHORT,
                        OFFSET_SYNCS_TOPIC_REPLICATION_FACTOR_DEFAULT,
                        ConfigDef.Importance.LOW,
                        OFFSET_SYNCS_TOPIC_REPLICATION_FACTOR_DOC)
                .define(
                        OFFSET_LAG_MAX,
                        ConfigDef.Type.LONG,
                        OFFSET_LAG_MAX_DEFAULT,
                        ConfigDef.Importance.LOW,
                        OFFSET_LAG_MAX_DOC)
                .define(
                        OFFSET_SYNCS_TOPIC_LOCATION,
                        ConfigDef.Type.STRING,
                        OFFSET_SYNCS_TOPIC_LOCATION_DEFAULT,
                        in(SOURCE_CLUSTER_ALIAS_DEFAULT, TARGET_CLUSTER_ALIAS_DEFAULT),
                        ConfigDef.Importance.LOW,
                        OFFSET_SYNCS_TOPIC_LOCATION_DOC)
                .define(
                        EMIT_OFFSET_SYNCS_ENABLED,
                        ConfigDef.Type.BOOLEAN,
                        EMIT_OFFSET_SYNCS_ENABLED_DEFAULT,
                        ConfigDef.Importance.LOW,
                        EMIT_OFFSET_SYNCS_ENABLED_DOC
                )
                .define(
                        HEARTBEATS_REPLICATION_ENABLED,
                        ConfigDef.Type.BOOLEAN,
                        HEARTBEATS_REPLICATION_ENABLED_DEFAULT,
                        ConfigDef.Importance.LOW,
                        HEARTBEATS_REPLICATION_ENABLED_DOC
                )
                .define(
                        METRIC_NAMES_FORMAT,
                        ConfigDef.Type.LIST,
                        METRIC_NAMES_FORMAT_DEFAULT,
                        ConfigDef.ValidList.in(false, METRIC_NAMES_LEGACY, METRIC_NAMES_NEW),
                        ConfigDef.Importance.LOW,
                        METRIC_NAMES_FORMAT_DOC
                );
    }

    protected static final ConfigDef CONNECTOR_CONFIG_DEF = defineSourceConfig(new ConfigDef(BASE_CONNECTOR_CONFIG_DEF));

    public static void main(String[] args) {        
        System.out.println(defineSourceConfig(new ConfigDef()).toHtml(4, config -> "mirror_source_" + config));
    }
}
