#include "index_hash_checker.h"

#include <saas/rtyserver/config/common_indexers_config.h>
#include <saas/rtyserver/config/config.h>

TString TIndexHashChecker::CalcFileHash(const NRTYServer::IIndexComponent& component, const TString& fileName) {
    char md5Value[48];
    const TFsPath fullPath(DirPath / fileName);
    if (!fullPath.Exists() || component.CheckAlways()) {
        return TString();
    }
    TFileStat fs(fullPath.GetPath().data());

    MD5 md5sum;
    md5sum.Update(&fs.MTime, sizeof(fs.MTime));
    md5sum.Update(&fs.Size, sizeof(fs.Size));
    TString result = md5sum.End(md5Value);
    return result;
}

TString TIndexHashChecker::BuildHash(const NRTYServer::IIndexComponent& component, bool afterRepair) {
    auto saved = CurrentHashes.insert(std::pair<TString, TString>(component.GetName(), TString()));
    TString& hashData = saved.first->second;
    if (!afterRepair && !saved.second)
        return hashData;
    NRTYServer::TInfoChecker ic;
    VERIFY_WITH_LOG(component.GetInfoChecker(ic), "Incorrect hash builder usage");
    typedef NRTYServer::IIndexComponent::TIndexFiles TIndexFiles;
    const TIndexFiles& files = component.GetIndexFiles();
    hashData.clear();
    if (component.CheckAlways())
        hashData = Config.ToString() + ",";
    else if (const TRTYServerConfig::IComponentConfig* cc = Config.ComponentsConfig.Get<TRTYServerConfig::IComponentConfig>(component.GetName())) {
        TStringOutput ss(hashData);
        cc->ToString(ss);
        ss.Finish();
        hashData += ",";
    }
    hashData += ic.AdditionalHash + ",";
    for (TIndexFiles::const_iterator i = files.begin(); i != files.end(); ++i) {
        if (!i->Checked) {
            DEBUG_LOG << "Skip file " << i->Name << Endl;
            continue;
        }
        DEBUG_LOG << "Check file " << i->Name << Endl;
        TFileList prefixedFiles;
        prefixedFiles.Fill(DirPath.GetPath(), i->Name);
        const char* fileName = nullptr;
        while (fileName = prefixedFiles.Next()) {
            hashData += CalcFileHash(component, fileName) + ",";
        }
    }
    return hashData;
}

const NRTYService::TFileCheck* TIndexHashChecker::GetCheckInfo(const TString& checkName) {
    if (SavedHashes.find(checkName) != SavedHashes.end())
        return &SavedHashes[checkName];
    else
        return nullptr;
}

TIndexHashChecker::TIndexHashChecker(const TRTYServerConfig& config, const TString& filesPath, bool saveHashes)
    : DirPath(filesPath)
    , Config(config)
    , SaveHashesFlag(saveHashes)
{
    VERIFY_WITH_LOG(DirPath.Exists(), "Incorrect TIndexHashChecker usage for path %s", DirPath.GetPath().data());
    TFsPath fiPath = DirPath / "files_info";
    if (fiPath.Exists()) {
        NRTYService::TFilesInfo finfo;
        TUnbufferedFileInput fi(fiPath);
        finfo.ParseFromArcadiaStream(&fi);
        for (ui32 i = 0; i < finfo.CheckersSize(); ++i) {
            SavedHashes[finfo.GetCheckers(i).GetComponentName()] = finfo.GetCheckers(i);
        }
    }
}

void TIndexHashChecker::SaveCheckData(const NRTYServer::IIndexComponent& component, bool afterRepair) {
    DEBUG_LOG << "Save check data for " << DirPath << "for component " << component.GetName() << Endl;
    NRTYService::TFileCheck fc;
    NRTYServer::TInfoChecker ic;
    VERIFY_WITH_LOG(component.GetInfoChecker(ic), "Incorrect hash builder usage");
    fc.SetComponentName(component.GetName());
    fc.SetVersion(ic.Version);
    TString hashData = BuildHash(component, afterRepair);
    fc.SetHash(hashData);
    if (!!hashData)
        SavedHashes[component.GetName()] = fc;
    else
        SavedHashes.erase(SavedHashes.find(component.GetName()));
}

bool TIndexHashChecker::NeedInCheck(const NRTYServer::IIndexComponent& component) {
    if (component.CheckAlways()) {
        return true;
    }
    const NRTYService::TFileCheck* fc = GetCheckInfo(component.GetName());
    if (fc) {
        NRTYServer::TInfoChecker ic;
        VERIFY_WITH_LOG(component.GetInfoChecker(ic), "Incorrect hash builder usage");
        if (fc->GetVersion() < ic.Version)
            return true;
        TString hashData = BuildHash(component, false);
        if (!hashData || hashData != fc->GetHash())
            return true;
        return false;
    } else {
        return true;
    }
}

TIndexHashChecker::~TIndexHashChecker() {
    if (!SaveHashesFlag || !Config.GetCommonIndexers().Enabled) {
        return;
    }

    NRTYService::TFilesInfo finfo;
    for (THashMap<TString, NRTYService::TFileCheck>::const_iterator i = SavedHashes.begin(), e = SavedHashes.end(); i != e; ++i) {
        NRTYService::TFileCheck* checker = finfo.AddCheckers();
        *checker = i->second;
    }
    TFsPath tmpPath = DirPath / "~files_info";
    {
        TFixedBufferFileOutput fo(tmpPath);
        finfo.SerializePartialToArcadiaStream(&fo);
    }
    tmpPath.RenameTo(DirPath / "files_info");
}
