#include "index_consume_transaction.h"

#include <util/folder/filelist.h>
#include <util/stream/file.h>

#include <memory>

TIndexConsumeTransaction::TIndexConsumeTransaction(TIndexStorage::TPtr storage, const TString& indexRootDirectory)
    : IndexStorage(storage)
    , IndexRootDirectory(indexRootDirectory)
    , StartConsumeMeta(::MakeTempName(indexRootDirectory.c_str(), StartConsumeMetaPrefix.c_str()), EOpenModeFlag::CreateAlways | EOpenModeFlag::Sync) {

    INFO_LOG << "open index transaction file: " << StartConsumeMeta.GetName() << Endl;
}

TIndexConsumeTransaction::~TIndexConsumeTransaction() {
    try {
        // If Commit() or Revert() haven't been called -> revert the transaction
        if (!Finished) {
            Revert();
        }
    } catch (...) {
        ERROR_LOG << "Unexpected failure during index consumption transaction destructor: " << CurrentExceptionMessage() << Endl;
    }
}

TIndexControllerPtr TIndexConsumeTransaction::ConsumeIndex(const TString& indexDirName, NRTYServer::EConsumeMode mode) {
    Y_ENSURE(!Finished, "Transaction is finished");

    TString finalIndex;
    if (!IndexStorage->PrepareIndexForConsumption(indexDirName, false, finalIndex)) {
        return nullptr;
    }

    // Write to file before renaming, after renaming if restarts happens it will be a part of index
    WriteLine(StartConsumeMeta, finalIndex.c_str());

    const TFsPath root(IndexRootDirectory);
    TFsPath consumed(indexDirName);
    consumed.RenameTo(root / finalIndex);

    TIndexControllerPtr index = IndexStorage->ConsumeAllocatedIndex(finalIndex, mode);
    if (!!index) {
        AddedIndices.push_back(index->Directory().BaseName());
    }
    return index;
}

 void TIndexConsumeTransaction::Commit() {
    Y_ENSURE(!Finished, "Transaction is finished");
    INFO_LOG << "Committing " << ListIndicies() << " indices" << Endl;
    FinishTransaction();
}

void TIndexConsumeTransaction::Revert() {
    Y_ENSURE(!Finished, "Transaction is finished");
    WARNING_LOG << "Reverting " << ListIndicies() << " indices" << Endl;
    IndexStorage->RemoveIndexes(AddedIndices, false);
    FinishTransaction();
}

void TIndexConsumeTransaction::FinishTransaction() {
    Finished = true;
    StartConsumeMeta.Resize(0); // both truncate it and remove
    StartConsumeMeta.Close();
    if (!NFs::Remove(StartConsumeMeta.GetName())) {
        ERROR_LOG << "Failed to remove transaction file: " << StartConsumeMeta.GetName() << Endl;
    }
    AddedIndices.clear();
}

TString TIndexConsumeTransaction::ListIndicies() const {
    if (AddedIndices.empty()) {
        return {};
    }
    TStringStream result;
    result << "[" << AddedIndices[0];
    for (size_t i = 0; i < AddedIndices.size(); i++) {
        result << ", " << AddedIndices[i];
    }
    return result.Str();
}

void TIndexConsumeTransaction::WriteLine(TFile& f, const TString& line) {
    TString LineWithEnding = line + '\n';
    f.Write(LineWithEnding.data(), LineWithEnding.size());
}

void TIndexConsumeTransaction::GetIncompletedTransactionIndicies(const TString& indexDir, TVector<TString>& transactionIndicies, TVector<TString>& metaFiles) {
    const TFsPath index {indexDir};
    if (!index.IsDirectory()) {
        return;
    }

    TFileList fileList;
    fileList.Fill(index.GetPath(), StartConsumeMetaPrefix);

    while (true) {
        const TString fileName(fileList.Next());
        if (fileName.empty()) {
            break;
        }

        const TFsPath metaFile = index / fileName;
        if (!metaFile.IsFile()) {
            continue;
        }

        TBuffered<TUnbufferedFileInput> fileReader(1024, metaFile);
        TString indexName;
        while (fileReader.ReadLine(indexName) > 0) {
            if (indexName.empty()) {
                continue;
            }
            transactionIndicies.push_back(std::move(indexName));
        }

        metaFiles.push_back(metaFile.GetPath());
    }
}

void TIndexConsumeTransaction::CreateTransactionFile(const TString& indexDir, const TVector<TString>& indicies) {
    TFile metaFile { ::MakeTempName(indexDir.c_str(), StartConsumeMetaPrefix.c_str()), EOpenModeFlag::CreateAlways };
    for (const auto& index: indicies) {
        WriteLine(metaFile, index);
    }
}

const TString TIndexConsumeTransaction::StartConsumeMetaPrefix = "_start_consume_transaction";
