/*
 * Copyright (c) 2026 Huawei Device Co., Ltd.
 * 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.
 */

#include "backoff_retry_timer.h"

#include <cstdint>
#include <limits>
#include <string>
#include <utility>

#include "iam_check.h"
#include "iam_logger.h"

#include "relative_timer.h"

#define LOG_TAG "CDA_SA"
#define LOG_FILE_ID LOG_FILE_BACKOFF_RETRY_TIMER

namespace OHOS {
namespace UserIam {
namespace CompanionDeviceAuth {

BackoffRetryTimer::BackoffRetryTimer(const Config &config, RetryCallback &&callback)
    : callback_(std::move(callback)),
      config_(config)
{
    if (config_.baseDelayMs == 0) {
        IAM_LOGE("%{public}s baseDelayMs is invalid, fallback to default %{public}u", config_.name.c_str(),
            Config::DEFAULT_BASE_DELAY_MS);
        config_.baseDelayMs = Config::DEFAULT_BASE_DELAY_MS;
    }
    if (config_.maxDelayMs < config_.baseDelayMs) {
        IAM_LOGW("%{public}s maxDelayMs %{public}u < baseDelayMs %{public}u, clamped to baseDelayMs",
            config_.name.c_str(), config_.maxDelayMs, config_.baseDelayMs);
        config_.maxDelayMs = config_.baseDelayMs;
    }
}

bool BackoffRetryTimer::OnFailure()
{
    timerSubscription_.reset();

    failureCount_ = (failureCount_ < UINT32_MAX) ? (failureCount_ + 1) : UINT32_MAX;
    backoffStep_ = (backoffStep_ < UINT32_MAX) ? (backoffStep_ + 1) : UINT32_MAX;

    if (failureCount_ > config_.maxRetryCount) {
        IAM_LOGE("%{public}s retry exhausted after %{public}u attempts", config_.name.c_str(), failureCount_);
        return false;
    }

    uint32_t delayMs = CalculateNextDelayMs(backoffStep_, config_);
    IAM_LOGI("%{public}s failure recorded %{public}u times, scheduling retry in %{public}u ms", config_.name.c_str(),
        failureCount_, delayMs);

    timerSubscription_ = RelativeTimer::GetInstance().Register(
        [callback = callback_, name = config_.name]() {
            IAM_LOGI("%{public}s executing retry callback", name.c_str());
            ENSURE_OR_RETURN(callback != nullptr);
            callback();
        },
        delayMs);
    return true;
}

void BackoffRetryTimer::ResetBackoff()
{
    timerSubscription_.reset();
    backoffStep_ = 0;
    IAM_LOGI("%{public}s retry backoff reset, keep failure budget %{public}u", config_.name.c_str(), failureCount_);
}

void BackoffRetryTimer::Reset()
{
    timerSubscription_.reset();
    failureCount_ = 0;
    backoffStep_ = 0;
    IAM_LOGI("%{public}s retry timer reset", config_.name.c_str());
}

uint32_t BackoffRetryTimer::CalculateNextDelayMs(uint32_t failureCount, const Config &config)
{
    constexpr uint32_t NUM_MAX_SHIFT_COUNT = 31;
    if (failureCount <= 1) {
        return config.baseDelayMs;
    }

    uint32_t shiftCount = failureCount - 1;
    if (shiftCount > NUM_MAX_SHIFT_COUNT) {
        return config.maxDelayMs;
    }

    uint64_t delayMs = config.baseDelayMs;
    delayMs <<= shiftCount;
    if (delayMs > config.maxDelayMs) {
        return config.maxDelayMs;
    }
    return static_cast<uint32_t>(delayMs);
}

} // namespace CompanionDeviceAuth
} // namespace UserIam
} // namespace OHOS