/**
 * Copyright 2024 Beijing Baolande Software Corporation
 *
 * 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.
 *
 * Runtime Library Exception to the Apache 2.0 License:
 *
 * As an exception, if you use this Software to compile your source code and
 * portions of this Software are embedded into the binary product as a result,
 * you may redistribute such product without providing attribution as would
 * otherwise be required by Sections 4(a), 4(b) and 4(d) of the License.
 */

package redis_sdk.client

/**
 * 哨兵模式下的命令执行器
 */
public open class SentinelCommandExectuor <: CommandExecutor {
    private static let logger: Logger = LoggerFactory.getLogger("redis-sdk")

    private let sentinelMasterProvider: SentinelMasterProvider

    // 最大重试次数
    private let maxAttempts: Int64
    // 最大重试时间
    private let maxTotalRetriesDuration: Duration

    public init(sentinelMasterProvider: SentinelMasterProvider) {
        this.sentinelMasterProvider = sentinelMasterProvider
        this.maxAttempts = 1
        // 不需要使用,设置为一个较小的值
        this.maxTotalRetriesDuration = Duration.second * 1
    }

    public init(sentinelMasterProvider: SentinelMasterProvider, maxAttempts: Int64, maxTotalRetriesDuration: Duration) {
        this.sentinelMasterProvider = sentinelMasterProvider
        this.maxAttempts = maxAttempts
        this.maxTotalRetriesDuration = maxTotalRetriesDuration
    }

    public override func getRespVersion(): ?ProtocolVersion {
        return sentinelMasterProvider.getRespVersion()
    }

    public open func getEndpoint(): ClientTcpEndpoint {
        return sentinelMasterProvider.getMasterEndpoint()
    }

    public open func getEndpoint(command: RedisCommand): ClientTcpEndpoint {
        return getEndpoint()
    }

    /**
     * 支持重试的命令执行
     */
    public open func executeCommand(command: RedisCommand): RedisMessage {
        if (maxAttempts > 1) {
            var attemptsLeft = maxAttempts
            let deadline = DateTime.now().addSeconds(maxTotalRetriesDuration.toSeconds())

            while (attemptsLeft > 0) {
                try {
                    attemptsLeft--
                    let responseMessage = super.executeCommand(command)
                    return responseMessage
                } catch (ex: Exception) {
                    if (attemptsLeft <= 0) {
                        if (let Some(redisEx) <- (ex as RedisException)) {
                            throw redisEx
                        } else {
                            throw RedisException(ex.message, ex)
                        }
                    }

                    if (logger.isDebugEnabled()) {
                        logger.warn(
                            "Failure to execute command ${command.getCommandType().name()}, will retry later",
                            ex
                        )
                    } else {
                        logger.warn(
                            "Failure to execute command ${command.getCommandType().name()}, will retry later, error msg: ${ex.message}"
                        )
                    }

                    handleConnectionProblem(ex, attemptsLeft, deadline)
                }
            }

            throw RedisException("No more attempts left.")
        } else {
            return super.executeCommand(command)
        }
    }

    /**
     * 处理连接异常
     */
    private func handleConnectionProblem(
        ex: Exception,
        attemptsLeft: Int64,
        deadline: DateTime
    ): Unit {
        if (!RecoverUtils.isTransportException(ex)) {
            // 非通信异常,直接抛出异常,不再重试
            if (let Some(redisEx) <- (ex as RedisException)) {
                throw redisEx
            } else {
                throw RedisException(ex.message, ex)
            }
        }

        let sleepTime = RecoverUtils.getBackoffSleepMillis(
            attemptsLeft,
            deadline
        )
        try {
            if (sleepTime > 0) {
                sleep(sleepTime * Duration.millisecond)
            }
        } catch (ex: Exception) {
            throw RedisException(ex.message, ex)
        }
    }

    protected open func internalClose(): Unit {
        sentinelMasterProvider.close()
    }
}