#include "startup_journal.h"

#include <saas/library/daemon_base/metrics/messages.h>

#include <library/cpp/logger/global/global.h>

#include <util/folder/dirut.h>
#include <util/stream/file.h>
#include <util/string/vector.h>
#include <util/datetime/base.h>

const TString SHUTDOWN_FILE = "/shutdown.info";
const TString JOURNAL_FILE = "/startup.info";

TStartupJournal::TStartupJournal(const TString& journalDir, const TString& suffix)
    : CrashMetric(GetMetricsPrefix() + "Server_ConsecutiveCrashes")
{
    bool afterCrash = true;

    // Load startup journal
    const TString fileName = journalDir + JOURNAL_FILE + "." + suffix;
    if (NFs::Exists(fileName)) {
        TUnbufferedFileInput finput(fileName);
        TString line;
        while (finput.ReadLine(line)) {
            TStartEvent info;
            TVector<TString> lineItems;
            Split(line, ",", lineItems);
            info.Time = FromString<ui32>(lineItems[0]);
            info.AfterCrash = FromString<bool>(lineItems[1]);
            StartupLog.push_back(info);
        }
        CHECK_WITH_LOG(NFs::Remove(fileName));
    } else {
        // Looks like a fresh start in clean folder...
        afterCrash = false;
    }

    // Check if the server was shut down properly last time
    ShutdownFile = journalDir + SHUTDOWN_FILE + "." + suffix;
    if (NFs::Exists(ShutdownFile)) {
        TUnbufferedFileInput finput(ShutdownFile);
        TString line;
        if (finput.ReadLine(line)) {
            TVector<TString> lineItems = SplitString(line, ",", 3);
            if (lineItems.size() < 3) {
                afterCrash = false; // old format of file - server exited correctly
            } else {
                ShutdownInfo.Time = FromString<ui32>(lineItems[0]);
                ShutdownInfo.Code = FromString<ui32>(lineItems[1]);
                ShutdownInfo.Message = lineItems[2];

                if (ShutdownInfo.Time != 0 && StartupLog.size() && StartupLog.back().Time > ShutdownInfo.Time)
                    ShutdownInfo = TStopEvent();
            }
        }

        if (ShutdownInfo.Time != 0 && ShutdownInfo.Code == 0) {
            // server exited correctly
            afterCrash = false;
        }

        CHECK_WITH_LOG(NFs::Remove(ShutdownFile));
    }

    // Append the event to the journal
    TStartEvent startinfo;
    startinfo.Time = Now().Seconds();
    startinfo.AfterCrash = afterCrash;
    StartupLog.push_back(startinfo);
    TFixedBufferFileOutput foutput(fileName);
    for (size_t i = 0; i < StartupLog.size(); ++i) {
        foutput << StartupLog[i].Time << "," << StartupLog[i].AfterCrash << Endl;
    }

    RegisterGlobalMessageProcessor(this);
}

TStartupJournal::~TStartupJournal() {
    UnregisterGlobalMessageProcessor(this);
}

bool TStartupJournal::IsAfterCrash() const {
    return StartupLog.size() && StartupLog.back().AfterCrash;
}

const TStartupJournal::TStopEvent& TStartupJournal::GetShutdownInfo() const {
    return ShutdownInfo;
}

ui32 TStartupJournal::GetCrashCount(ui32 maxAge) const {
    // returns number of consecutive starts after crash in given time frame (starts from this run and going backwards in time)
    ui32 result = 0;
    ui32 minTime = Now().Seconds() - maxAge;
    for (TVector<TStartEvent>::const_reverse_iterator itr = StartupLog.rbegin(); itr != StartupLog.rend(); ++itr) {
        if (itr->Time < minTime) {
            break;
        }
        if (itr->AfterCrash) {
            ++result;
        } else {
            break;
        }
    }
    return result;
}

TString TStartupJournal::Report() {
    ui32 lastTime = 0;
    size_t i = 0;
    if (StartupLog.size() > MAX_REPORT_LINES) {
        // Output at most MAX_REPORT_LINES
        i = StartupLog.size() - MAX_REPORT_LINES;
    }
    TString result = "Server startup journal :\n----------------------------------------\nTIME       | AFTER_CRASH | DELAY_SECONDS\n";
    for (; i < StartupLog.size(); ++i) {
        result += ToString<ui32>(StartupLog[i].Time) + " |      " + ToString<bool>(StartupLog[i].AfterCrash) + "      | ";
        ui32 timeSincePrevStart = (lastTime ? StartupLog[i].Time - lastTime : 0);
        result += ToString<ui32>(timeSincePrevStart) +"\n";
        lastTime = StartupLog[i].Time;
    }
    result += "Current number of consecutive crashes : " + ToString<ui32>(GetCrashCount());
    return result;
}

void TStartupJournal::ServerExits() {
    TFixedBufferFileOutput fout(ShutdownFile);
    fout << Now().Seconds() << ",0,Server exits normally" << Endl; //an abnormal termination may be recorded from the loopscript
}

TString TStartupJournal::Name() const {
    return "StartupJournal";
}

bool TStartupJournal::Process(IMessage* message) {
    if (message->As<TCollectMetricsMessage>()) {
        CrashMetric.Set(GetCrashCount());
        return true;
    }
    return false;
}
