/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved.
 * 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 <set>

#include "service_configure.h"
#include "memfs_api.h"
#include "under_fs_factory.h"
#include "mem_fs_backup_initiator.h"
#include "dfs_backup_initiator.h"
#include "background_log.h"
#include "background_constants.h"
#include "retry_task_pool.h"
#include "backup_file_manager.h"

using namespace ock::bg::util;
using namespace ock::bg::backup;
using BC = ock::bg::BackgroundConstants;

BackupFileManager &BackupFileManager::GetInstance() noexcept
{
    static BackupFileManager instance;
    return instance;
}

std::shared_ptr<BackupInitiator> BackupFileManager::GetInitiator(const std::string &name) noexcept
{
    auto pos = initiators.find(name);
    if (pos == initiators.end()) {
        return nullptr;
    }

    return pos->second;
}

int BackupFileManager::Validate(const common::config::BackupServiceConfig &backupConfig) noexcept
{
    if (backupConfig.threadNum < BC::TASK_MIN_THREAD_NUM || backupConfig.threadNum > BC::TASK_MAX_THREAD_NUM) {
        BKG_LOG_ERROR("backup service thread number(" << backupConfig.threadNum << ") invalid.");
        return -1;
    }

    if (backupConfig.maxFailCntForUnserviceable == 0) {
        BKG_LOG_ERROR("backup service max fail cnt number(" <<
            backupConfig.maxFailCntForUnserviceable << ") invalid, must > 0.");
        return -1;
    }

    return 0;
}

int BackupFileManager::Initialize(const common::config::BackupServiceConfig &backupConfig) noexcept
{
    if (!backupConfig.enabled) {
        BKG_LOG_ERROR("backup service not enabled.");
        return -1;
    }

    if (Validate(backupConfig) != 0) {
        BKG_LOG_ERROR("backup service validate config failed.");
        return -1;
    }

    std::map<std::string, TargetsConf> backupSourceTargets;
    for (auto &backup : backupConfig.backups) {
        if (!backup.opened) {
            continue;
        }

        if (!CheckBackupInstanceConfig(backup)) {
            return -1;
        }
        backupSourceTargets[backup.source].push_back(std::make_pair(backup.destType, backup.destName));
    }

    if (backupSourceTargets.empty()) {
        BKG_LOG_ERROR("no backups valid.");
        return -1;
    }

    if (!CheckBackupFullConfig(backupSourceTargets)) {
        BKG_LOG_ERROR("configure for backups contains same target.");
        return -1;
    }

    dfs = std::make_shared<DfsAdapter>();
    auto ret = CreateThreadPool(backupConfig);
    if (ret != 0) {
        dfs.reset();
        return -1;
    }

    if (CreateBackupInstances(backupSourceTargets) != 0) {
        pool.reset();
        dfs.reset();
        return -1;
    }

    return 0;
}

void BackupFileManager::Destroy() noexcept
{
    pool.reset();
    dfs.reset();
    initiators.clear();
}

int BackupFileManager::CreateBackupInstances(const std::map<std::string, TargetsConf> &sourceTarget) noexcept
{
    for (auto &instance : sourceTarget) {
        auto target = CreateBackupTarget(instance.first, instance.second);
        if (target == nullptr) {
            BKG_LOG_ERROR("create backup for source(" << instance.first.c_str() << ") failed!");
            return -1;
        }

        std::shared_ptr<BackupInitiator> initiator;
        if (instance.first == "mfs") {
            initiator = std::make_shared<MemFsBackupInitiator>();
            memfs::MemFsApi::SetExternalStat(
                [target](const std::string &path, struct stat &buf, memfs::MemfsFileAcl &acl) -> int {
                    ufs::FileAcl ufsAcl;
                    auto ret = target->StatFile(path, buf, ufsAcl);
                    if (ret != 0) {
                        return -errno;
                    }

                    acl.ownerPerm = ufsAcl.ownerPerm;
                    acl.groupPerm = ufsAcl.groupPerm;
                    acl.otherPerm = ufsAcl.otherPerm;
                    acl.permMask = ufsAcl.permMask;
                    acl.usersAcl = std::move(ufsAcl.users);
                    acl.groupsAcl = std::move(ufsAcl.groups);
                    return 0;
                });
        } else {
#ifdef __BUILD_BOTH_MFS_DFS__
            initiator = std::make_shared<DfsBackupInitiator>();
#else
            BKG_LOG_ERROR("unsupported backup initiator(" << instance.first.c_str() << ")");
            return -1;
#endif
        }

        auto ret = initiator->Initialize(pool, target);
        if (ret != 0) {
            BKG_LOG_ERROR("initialize backup initiator for(" << instance.first.c_str() << ") failed!");
            return -1;
        }

        initiators[instance.first] = initiator;
    }

    return 0;
}

std::shared_ptr<BackupTarget> BackupFileManager::CreateBackupTarget(const std::string &src,
    const TargetsConf &targetInfo) noexcept
{
    std::list<std::shared_ptr<ufs::BaseFileService>> targetFs;
    for (auto &e : targetInfo) {
        if (e.first == "dfs") {
            targetFs.push_back(dfs);
        } else {
            targetFs.push_back(ufs::UnderFsFactory::GetInstance().Get(e.second));
        }
    }

    auto backupTarget = std::make_shared<BackupTarget>();
    auto ret = backupTarget->Initialize(src, pool, targetFs);
    if (ret != 0) {
        return nullptr;
    }

    return backupTarget;
}

int BackupFileManager::CreateThreadPool(const common::config::BackupServiceConfig &backupConfig) noexcept
{
    RetryTaskPool::RetryTaskConfig config;
    config.name = "MindBG";
    config.autoEvictFile = backupConfig.autoEvictFile;
    config.thCnt = backupConfig.threadNum;
    config.maxFailCntForUnserviceable = backupConfig.maxFailCntForUnserviceable;
    config.retryTimes = backupConfig.retryTimes;
    config.retryIntervalSec = backupConfig.retryIntervalSec;
    config.firstWaitMs = BC::TASK_RETRY_FIRST_WAIT_MILL_SECONDS;

    if (config.thCnt == 0) {
        BKG_LOG_ERROR("create thread pool with size(0)");
        return -1;
    }

    pool = std::make_shared<RetryTaskPool>(config);
    if (pool == nullptr) {
        BKG_LOG_ERROR("create thread pool with size(" << config.thCnt << ") failed.");
        return -1;
    }

    auto ret = pool->Start();
    if (ret != 0) {
        BKG_LOG_ERROR("start thread pool failed: " << ret);
        pool.reset();
        return -1;
    }

    return 0;
}

bool BackupFileManager::CheckBackupInstanceConfig(const common::config::BackupInstance &backup) noexcept
{
    if (backup.source != "mfs" && backup.source != "dfs") {
        BKG_LOG_ERROR("unknown backup source(" << backup.source.c_str() << ")");
        return false;
    }

    if (backup.source == backup.destType) {
        BKG_LOG_ERROR("backup cannot to itself(" << backup.source.c_str() << ")");
        return false;
    }

    if (backup.destType != "under_fs" && backup.destType != "dfs") {
        BKG_LOG_ERROR("unknown backup source(" << backup.destType.c_str() << ")");
        return false;
    }

    if (backup.destType == "dfs") {
        return true;
    }

    auto ufs = ock::ufs::UnderFsFactory::GetInstance().Get(backup.destName);
    if (ufs == nullptr) {
        BKG_LOG_ERROR("backup target ufs(" << backup.destName.c_str() << ") not exist");
        return false;
    }

    return true;
}

bool BackupFileManager::CheckBackupFullConfig(const std::map<std::string, TargetsConf> &sourceTarget) noexcept
{
    for (auto &e : sourceTarget) {
        int dfsCount = 0;
        std::map<std::string, int> ufsNameCount;
        for (auto &d : e.second) {
            if (d.first == "dfs") {
                dfsCount++;
            } else {
                ufsNameCount[d.second]++;
            }
        }

        if (dfsCount > 1) {
            return false;
        }

        for (auto &count : ufsNameCount) {
            if (count.second > 1) {
                return false;
            }
        }
    }

    return true;
}