#include "ki_normalizer.h"
#include <saas/rtyserver/components/oxy/doom/ki_doom_merger_io.h>
#include <robot/library/oxygen/indexer/library/wrangle/wrangle.h>
#include <robot/library/oxygen/indexer/processor/protos/config.pb.h>

#include <robot/jupiter/tools/shardmerge_utils/lib/document_index/indexattrs_seg_tree_merge.h>

//
// NB: when changing logic in this file, do not forget to update createprocessors.cpp
//

namespace {
    static void RenameKeyInv(const TFsPath dir, const TString& oldPrefix, const TString& newPrefix) {
        TFsPath oldKey = dir / (oldPrefix + "key");
        TFsPath oldInv = dir / (oldPrefix + "inv");
        AssertCorrectIndex(NFs::Exists(oldKey) && NFs::Exists(oldInv),
                           "bad key/inv files combination for prefix %s", TString(dir / oldPrefix).data());
        TFsPath newKey = dir / (newPrefix + "key");
        TFsPath newInv = dir / (newPrefix + "inv");
        newKey.DeleteIfExists();
        newInv.DeleteIfExists();
        NFs::Rename(oldKey, newKey);
        NFs::Rename(oldInv, newInv);
    }
}

const char* TOXYKeyInvNormalizer::Name() const {
    return "TOXYKeyInvNormalizer";
}

// Gets the target state of the shard
TOXYKeyInvNormalizer::TIndexTraits TOXYKeyInvNormalizer::GetIndexTraits(const NOxygen::TOxygenOptions* oxygenOptions) {
    TIndexTraits traits;
    if (oxygenOptions->HasKeyInvOptions()) {
        VERIFY_WITH_LOG(!oxygenOptions->HasTextWadOptions(), "incorrect OxygenOptions"); //should not happen
        traits.GlobalKeyInv = true;
    } else if (oxygenOptions->HasTextWadOptions()) {
        const NOxygen::TTextWadOptions& opts = oxygenOptions->GetTextWadOptions();
        if (opts.GetWadOptions().GetWadAndOldMode()) {
            traits.GlobalKeyInv = !opts.GetRenamedKeyInvCompatibilityMode();
            traits.RenamedGlobalKeyInv = !traits.GlobalKeyInv;
        }
        traits.IndexAttrs = !opts.GetDisableKeyInvAttrs();
        traits.TextWad = !opts.GetDisableKeyInvWad();
    }
    traits.IndexAttrsOld = false; // this normalizer never builds the old indexattrs
    CHECK_WITH_LOG(!(traits.GlobalKeyInv && traits.RenamedGlobalKeyInv));
    return traits;
}

bool TOXYKeyInvNormalizer::CheckTraits(const TIndexTraits& expected, const TIndexTraits& actual) {
    Y_ASSERT(!expected.IndexAttrsOld);
    bool ok = true;
    ok = ok && (!expected.GlobalKeyInv || actual.GlobalKeyInv);
    ok = ok && (!expected.RenamedGlobalKeyInv || actual.RenamedGlobalKeyInv && !actual.GlobalKeyInv);
    ok = ok && (!expected.IndexAttrsOld || actual.IndexAttrsOld);
    ok = ok && (!expected.IndexAttrs || actual.IndexAttrs);
    ok = ok && (!expected.TextWad || actual.TextWad);
    return ok;
}


bool TOXYKeyInvNormalizer::AllRight(const NRTYServer::TNormalizerContext& context, const THolder<TFileMap>& /*indexFrq*/) const {
    const NOxygen::TOxygenOptions* oxygenOptions = GetOxygenOptions(context);
    AssertCorrectConfig(oxygenOptions, "OxygenOptions are required for TOXYKeyInvNormalizer normalizer");

    const TFsPath directory = context.Dir.PathName();
    TIndexTraits expected = GetIndexTraits(oxygenOptions);
    TIndexTraits actual = GetIndexTraits(directory);
    return CheckTraits(expected, actual);
}

// Gets the actual state of the shard
TOXYKeyInvNormalizer::TIndexTraits TOXYKeyInvNormalizer::GetIndexTraits(const TFsPath dir) {
    TIndexTraits traits = {0};
    traits.GlobalKeyInv = NFs::Exists(dir / "indexkey") && NFs::Exists(dir / "indexinv");
    traits.RenamedGlobalKeyInv = NFs::Exists(dir / "indexoldkey") && NFs::Exists(dir / "indexoldinv");
    traits.IndexAttrsOld = NFs::Exists(dir / "indexattrskey") && NFs::Exists(dir / "indexattrsinv");;
    traits.IndexAttrs = NFs::Exists(dir / "indexattrs.key.wad") && NFs::Exists(dir / "indexattrs.inv.wad") && NFs::Exists(dir / "indexattrs.date.seg_tree.flat");
    traits.TextWad = NFs::Exists(dir / "indexkeyinv.wad");
    return traits;
}

bool TOXYKeyInvNormalizer::TryFixByRenaming(const TIndexTraits &expected, TIndexTraits &actual, const TFsPath dir) {
    Y_ASSERT(!(expected.GlobalKeyInv && expected.RenamedGlobalKeyInv));
    TString oldPrefix = "indexold";
    TString newPrefix = "index";
    if (expected.GlobalKeyInv && !actual.GlobalKeyInv && actual.RenamedGlobalKeyInv) {
        ;
    } else if (expected.RenamedGlobalKeyInv && !actual.RenamedGlobalKeyInv && actual.GlobalKeyInv) {
        oldPrefix.swap(newPrefix);
    } else
        return false;

    INFO_LOG << "TOXYKeyInvNormalizer handled " << (expected.GlobalKeyInv ? "GlobalKeyInv" : "RenamedGlobalKeyInv")
             << " requirement by renaming " << oldPrefix << " to " << newPrefix << Endl;

    RenameKeyInv(dir, oldPrefix, newPrefix);
    actual = GetIndexTraits(dir); // updates 'actual'
    return CheckTraits(expected, actual);
}

bool TOXYKeyInvNormalizer::TryFixByWrangling(const TIndexTraits& expected, TIndexTraits& actual, const TFsPath dir) const {
    bool missingAttrs = expected.IndexAttrs && !actual.IndexAttrs;
    bool missingTextWad = expected.TextWad && !actual.TextWad;
    if (!missingAttrs && !missingTextWad)
        return false; // this issue is not about wrangling

    if (!(actual.GlobalKeyInv || actual.RenamedGlobalKeyInv || actual.IndexAttrsOld))
        return false; // there is no source for wrangling - should use FULL_ARCHIVE as the last resort

    const TString keyInvWranglingSource = actual.GlobalKeyInv ? "index" :
            actual.RenamedGlobalKeyInv ? "indexold" : Default<TString>();
    const TString attrsWranglingSource = !keyInvWranglingSource.empty() ? keyInvWranglingSource :
            actual.IndexAttrsOld ? "indexattrs" : Default<TString>();

    using namespace NDoom::NRTYServer;

    if (missingAttrs) {
        Y_ENSURE(!attrsWranglingSource.empty());
        using TWrangler = NOxygen::TWrangler<TYandexReader<TIndexAttrsFilter>, TAttrsWadIo>;
        TWrangler wrangler(Name(), dir / attrsWranglingSource, dir / "indexattrs");
        wrangler.WrangleKeyInv();

        auto removeWadSuffix = [](const TFsPath& path, const TString& suffix) {
            NFs::Rename(path / ("indexattrswad." + suffix + ".wad"), path / ("indexattrs." + suffix + ".wad"));
        };
        removeWadSuffix(dir, "key");
        removeWadSuffix(dir, "inv");
        NJupiter::MergeIndexAttrsSegTree(dir / "indexattrs.");
    }

    if (missingTextWad) {
        Y_ENSURE(!keyInvWranglingSource.empty());
        using TWrangler = NOxygen::TWrangler<TYandexReader<TTextWadFilter>, TTextWadIo>;
        TWrangler wrangler(Name(), dir / keyInvWranglingSource, dir / "indexkeyinv.");
        wrangler.WrangleKeyInv();
    }

    actual = GetIndexTraits(dir);
    return CheckTraits(expected, actual);
}

bool TOXYKeyInvNormalizer::DoFix(const TIndexTraits& expected, TIndexTraits& actual, const TFsPath dir) const {
    if (CheckTraits(expected, actual))
        return true;

    if (TryFixByRenaming(expected, actual, dir))
        return true;

    try {
        return TryFixByWrangling(expected, actual, dir);
    } catch(...) {
        ERROR_LOG << Name() << " convertation failed: " << CurrentExceptionMessage() << Endl;
        return false;
    }

    //TODO:(yrum) would be nice to have a fallback to FULL_ARC reindexing
}


void TOXYKeyInvNormalizer::Fix(const NRTYServer::TNormalizerContext& context, const THolder<TFileMap>& /*indexFrq*/) const {
    const TFsPath dir = context.Dir.PathName();
    const NOxygen::TOxygenOptions* oxygenOptions = GetOxygenOptions(context);
    AssertCorrectConfig(oxygenOptions, "OxygenOptions are required for TOXYKeyInvNormalizer normalizer");

    TIndexTraits expected = GetIndexTraits(oxygenOptions);
    TIndexTraits actual = GetIndexTraits(dir);
    INFO_LOG << Name() << " restoring " << dir.Basename() << " ..." << Endl;

    if (!DoFix(expected, actual, dir)) {
        AbortFromCorruptedIndex("TOXYKeyInvNormalizer cannot restore %s", dir.Basename().data());
    }

    INFO_LOG << Name() << " restoring " << dir.Basename() << " ... OK" << Endl;
}
