#include "summarize_sessions.h"
#include "aux_utils.h"
#include "log_parsers.h"

namespace NH2Log {

    namespace {

        struct TCatchInfo {
            TString Cont;
            TString Data;

            TCatchInfo(TStringBuf cont, TStringBuf data)
                : Cont(TString{cont})
                , Data(TString{data})
            {}
        };

        struct TStreamsStats {
            ui64 Opened = 0;
            ui64 Closed = 0;
            ui64 ResetByServer = 0;
            ui64 ResetByClient = 0;
            ui64 Idle = 0;
            bool SeenH2Start = false;
        };

        struct TSumSession : public TSession {
            TMaybe<TString> UserAgent;
            TMaybe<TCatchInfo> FirstConnException;
            TMaybe<TCatchInfo> FirstConnCatch;
            TMaybe<TCatchInfo> FinalConnCatch;
            TMaybe<TErrorDetails> RecvGoAway;
            TMaybe<TStreamsStats> StreamsStats;
        };

        void GetUserAgent(TSumSession& s, const TLogEntry& item) {
            if (!s.UserAgent && IsStreamOpen(item)) {
                s.UserAgent = ParseUserAgent(item);
            }
        }

        bool IsConnOrOutputException(const TLogEntry& item) {
            return IsException(item) && (IsConnCont(item) || IsOutputCont(item));
        }

        bool IsConnOrOutputCatch(const TLogEntry& item) {
            return IsCatch(item) && (IsConnCont(item) || IsOutputCont(item));
        }

        void GetFirstConnException(TSumSession& s, const TLogEntry& item) {
            if (!s.FirstConnException && IsConnOrOutputException(item)) {
                s.FirstConnException.ConstructInPlace(
                    item.Cont.Name, item.Event.Name
                );
            }
        }

        void GetFirstConnCatch(TSumSession& s, const TLogEntry& item) {
            if (!s.FirstConnCatch && IsConnOrOutputCatch(item)) {
                s.FirstConnCatch.ConstructInPlace(
                    item.Cont.Name,item.Event.Data
                );
            }
        }

        void GetFinalConnCatch(TSumSession& s, const TLogEntry& item) {
            if (IsConnOrOutputCatch(item)) {
                s.FinalConnCatch.ConstructInPlace(
                    item.Cont.Name, item.Event.Data
                );
            }
        }

        void GetRecvGoAway(TSumSession& s, const TLogEntry& item) {
            if (!s.RecvGoAway && IsRecvGoAway(item)) {
                s.RecvGoAway = ParseError(item);
            }
        }

        void GetStreamsStats(TSumSession& s, const TLogEntry& item) {
            if (!s.StreamsStats) {
                s.StreamsStats.ConstructInPlace();
                s.StreamsStats->SeenH2Start |= IsH2SessionStart(item);
            }
            if (IsStreamOpen(item)) {
                s.StreamsStats->Opened += 1;
            } else if (IsStreamDispose(item)) {
                if (IsFinalStateClosed(item)) {
                    s.StreamsStats->Closed += 1;
                } else if (IsFinalStateResetByServer(item)) {
                    s.StreamsStats->ResetByServer += 1;
                } else if (IsFinalStateResetByClient(item)) {
                    s.StreamsStats->ResetByClient += 1;
                } else {
                    s.StreamsStats->Idle += 1;
                }
            }
        }

        using TSummarizer = std::function<void(TSumSession&, const TLogEntry&)>;

        TSummarizer GetSummarizer(ESessionSummary summarizer) {
#define Y_H2_LOG_GET_SUMM(summ) case ESessionSummary::summ: return Get##summ
            switch (summarizer) {
            Y_H2_LOG_GET_SUMM(UserAgent);
            Y_H2_LOG_GET_SUMM(FirstConnException);
            Y_H2_LOG_GET_SUMM(FirstConnCatch);
            Y_H2_LOG_GET_SUMM(FinalConnCatch);
            Y_H2_LOG_GET_SUMM(RecvGoAway);
            Y_H2_LOG_GET_SUMM(StreamsStats);
            }
        }
    }

    void SummarizeSessions(TFileManager& fileManager, const TVector<ESessionSummary>& summary) {
        Y_H2_LOG_FUNC(fileManager);
        THashMap<TString, TSumSession> active;
        TSumSession* sess = nullptr;
        TString line;
        while (fileManager.GetInput().ReadLine(line)) {
            if (!(sess = ParseNextLogLine(sess, active, line))) {
                continue;
            }

            auto& item = sess->Log.back();

            for (auto sum : summary) {
                GetSummarizer(sum)(*sess, item);
            }

            if (IsH2SessionEnd(item)) {
                auto& out = fileManager.GetFileOutput(fileManager.GetFileNameBySuffix("summary"));
#define Y_H2_LOG_PRINT_SUMM(sess, out, addr, summary) if (sess->summary) {\
                    out << addr << "\t" << ESessionSummary::summary << "\t" << sess->summary << "\n";\
                }
                Y_H2_LOG_PRINT_SUMM(sess, out, item.Addr, UserAgent);
                Y_H2_LOG_PRINT_SUMM(sess, out, item.Addr, FirstConnException);
                Y_H2_LOG_PRINT_SUMM(sess, out, item.Addr, FirstConnCatch);
                Y_H2_LOG_PRINT_SUMM(sess, out, item.Addr, FinalConnCatch);
                Y_H2_LOG_PRINT_SUMM(sess, out, item.Addr, RecvGoAway);
                Y_H2_LOG_PRINT_SUMM(sess, out, item.Addr, StreamsStats);
                active.erase(item.Addr);
            }
        }
    }
}

template <>
void Out<NH2Log::TCatchInfo>(IOutputStream& out, TTypeTraits<NH2Log::TCatchInfo>::TFuncParam value) {
    out << "(" << value.Cont << ") " << value.Data;
}

template <>
void Out<NH2Log::TStreamsStats>(IOutputStream& out, TTypeTraits<NH2Log::TStreamsStats>::TFuncParam value) {
    out << "Opened=" << value.Opened
        << ", Closed=" << value.Closed
        << ", ResetByServer=" << value.ResetByServer
        << ", ResetByClient=" << value.ResetByClient
        << (value.SeenH2Start ? "" : " (incomplete session)");
}
