#include "builder.h"

#include <saas/library/sandbox/recipe_download/download.h>
#include <saas/rtyserver/config/config.h>

#include <robot/library/oxygen/indexer/processor/protos/config.pb.h>

#include <robot/jupiter/library/stamp_tag/config.pb.h>
#include <robot/jupiter/library/stamp_tag/stamp_tag_builder.h>

#include <kernel/shard_conf/shard_conf.h>

#include <util/system/fs.h>

namespace NRTYServer {
    namespace {
        constexpr TStringBuf SHARD_NAME_PREFIX = "primus";
        constexpr TStringBuf SHARD_CONF = "shard.conf";

        template <typename TCallback>
        bool IfPathDoesNotExist(const TString& path, bool forceRemove, TCallback&& callback) {
            if (!NFs::Exists(path)) {
                INFO_LOG << path << " does not exist, restore it" << Endl;
            } else {
                if (!forceRemove) {
                    INFO_LOG << path << " already exists, skip it" << Endl;
                    return false;
                }
                WARNING_LOG << path << " already exists, remove it and replace with the new one" << Endl;
            }
            // NFs::Exists(path) returns false for symlinks pointing to removed paths. So we need to always remove the path
            TFsPath(path).ForceDelete();
            callback();
            return true;
        }
    }

    TStaticFilesBuilder::TStaticFilesBuilder(const TString& componentName, const TStaticFilesConfig& config)
        : IIndexComponentBuilder(componentName)
        , Config(config)
    {
    }

    bool TStaticFilesBuilder::Start() {
        return true;
    }

    bool TStaticFilesBuilder::Stop() {
        return true;
    }

    void TStaticFilesBuilder::InitInteractions(const IIndexBuildersStorage&) {
    }

    void TStaticFilesBuilder::Index(int /*threadID*/, const TParsedDocument&, const ui32 /*docId*/) {
    }

    NRTYServer::IIndexComponentManager* TStaticFilesBuilder::GetManager() {
        return nullptr;
    }

    bool TStaticFilesBuilder::DoClose(const TBuilderCloseContext& context) {
        BuildAndReplaceStaticFiles(Config, context.DstDir.PathName(), "Close builder");
        return true;
    }

    void BuildStaticFilesImpl(const TStaticFilesConfig& config, const TString& outputDirectory, TStringBuf reason, bool forceReplace) {
        INFO_LOG << "BuildStaticFiles(" << outputDirectory << ") is called from '" << reason << "'" << Endl;
        const auto oxygenOptions = &config;

        auto now = TInstant::Now();
        if (oxygenOptions->HasStampTagOptions()) {
            IfPathDoesNotExist(TFsPath(outputDirectory) / "stamp.TAG", forceReplace, [&]() {
                auto& oxyOptions = oxygenOptions->GetStampTagOptions();
                auto& shardId = oxyOptions.GetShardId();

                NJupiter::TStampTagOptions options;
                options.MutableShard()->SetTier(shardId.GetTier());
                if (shardId.HasGroup()) {
                    options.MutableShard()->SetGroup(shardId.GetGroup());
                }
                if (shardId.HasId()) {
                    options.MutableShard()->SetId(shardId.GetId());
                }
                options.MutableShard()->SetShardNamePrefix(TString{SHARD_NAME_PREFIX});
                options.SetLemmatizedLanguages(oxyOptions.GetLemmatizedLanguages());
                options.SetFlatBastardLanguages(oxyOptions.GetFlatBastardLanguages());

                BuildStampTag(outputDirectory, now.Seconds(), options);
            });
        }

        if (oxygenOptions->HasShardConfOptions()) {
            IfPathDoesNotExist(TFsPath(outputDirectory) / SHARD_CONF, forceReplace, [&]() {
                TShardConfWriter shardConf(now, oxygenOptions->GetShardConfOptions().GetShardName(), {}, {});
                shardConf.Write(outputDirectory, TString{SHARD_CONF});
            });
        }

        if (oxygenOptions->HasStaticDataOptions()) {
            const auto& staticDataOptions = oxygenOptions->GetStaticDataOptions();
            const auto recipe = NSaas::BuildRecipeFromStaticDataOptions(staticDataOptions);

            auto beforeCallback = [&outputDirectory](const TString& path) {
                INFO_LOG << "Deliver static file " << path << " to " << outputDirectory << Endl;
            };
            auto afterCallback = [&outputDirectory](const TString& path) {
                INFO_LOG << "Static file " << path << " successfully delivered to " << outputDirectory << Endl;
            };
            auto filterCallback = [forceReplace](const TString& destPath) {
                return IfPathDoesNotExist(destPath, forceReplace, []{});
            };
            NSaas::TSandboxOptions sandboxOptions;
            sandboxOptions.UseSandbox = false;
            NSaas::DownloadRecipeFilesImpl(recipe, outputDirectory, true, false, false, false, sandboxOptions,
                beforeCallback, afterCallback, filterCallback);
        }
    }

    void BuildAndReplaceStaticFiles(const TStaticFilesConfig& config, const TString& outputDirectory, TStringBuf reason) {
        BuildStaticFilesImpl(config, outputDirectory, reason, true /*forceReplace*/);
    }

    void RepairStaticFiles(const TStaticFilesConfig& config, const TString& outputDirectory, TStringBuf reason) {
        BuildStaticFilesImpl(config, outputDirectory, reason, false /*forceReplace*/);
    }
}
