#pragma once

#include <util/generic/string.h>
#include <util/generic/strbuf.h>

#include <util/generic/yexception.h>
#include <util/string/cast.h>
#include <util/string/strip.h>
#include <balancer/kernel/http2/server/http2_frame.h>

namespace NH2Log {

    enum class EEventType : ui8 {
        Event, Entry, Exit, Exception
    };

    enum class EBlockType : ui8 {
        Inline, Block, Func, Catch, CatchAll
    };

    enum class EContType : ui8 {
        Unknown, Conn, Output, Stream, ProxyClientToBackend
    };

    struct TContInfo {
        TStringBuf Name;
        TStringBuf Ptr;
        ui32 StreamId = 0;
        EContType ContType = EContType::Unknown;
    };

    struct TEventInfo {
        TString RawEvent;
        TStringBuf Name;
        TStringBuf Data;
        ui32 Level = 0;
        EEventType EventType = EEventType::Entry;
        EBlockType BlockType = EBlockType::Block;
        bool IsUnwinding = false;
    };

    struct TLogEntry : TMoveOnly {
        TStringBuf Addr;
        TStringBuf TimeRaw;
        TInstant TimeParsed;
        TStringBuf Module;
        TContInfo Cont;
        TEventInfo Event;
        TString RawPrefix;
        TVector<TString> Trace;
        int LogLevel = 0;
    };

    enum class EErrorType : ui8 {
        None, GOAWAY, RST_STREAM
    };

    enum class EErrorSource : ui8 {
        None, Send, Recv
    };

    struct TError {
        TString Code;
        EErrorSource Source = EErrorSource::None;
        EErrorType Type = EErrorType::None;

        auto AsTuple() const {
            return std::make_tuple(Code, (ui8)Source, (ui8)Type);
        }

        auto AsSourceType() const {
            return std::make_tuple(Source, Type);
        }

        bool operator< (const TError& other) const {
            return AsTuple() < other.AsTuple();
        }

        bool operator== (const TError& other) const {
            return AsTuple() == other.AsTuple();
        }
    };


    struct TErrorDetails : public TError {
        TString DebugData;
        ui32 StreamId = 0;
        bool Benigh = false;
    };


    struct TSession {
        TDeque<TLogEntry> Log;
        TSet<TError> Errors;
    };


    TString& StripLine(TString& line);

    TStringBuf ParseAddr(TStringBuf line);

    TLogEntry ParseLogLine(const TString& line);

    bool IsTraceLine(TStringBuf line);

    bool IsConnCont(const TLogEntry& item);

    bool IsOutputCont(const TLogEntry& item);

    bool IsException(const TLogEntry& item);

    bool IsCatch(const TLogEntry& item);

    bool IsRecvPriFail(const TLogEntry& item);

    bool IsH1SessionStart(const TLogEntry& item);

    bool IsH1SessionEnd(const TLogEntry& item);

    bool IsH2SessionStart(const TLogEntry& item);

    bool IsH2SessionEnd(const TLogEntry& item);

    bool IsStreamOpen(const TLogEntry& item);

    bool IsStreamFinish(const TLogEntry& item);

    bool IsStreamDispose(const TLogEntry& item);

    bool IsFinalStateClosed(const TLogEntry& item);

    bool IsFinalStateResetByClient(const TLogEntry& item);

    bool IsFinalStateResetByServer(const TLogEntry& item);

    bool IsStreamRunStart(const TLogEntry& item);

    bool IsStreamRunEnd(const TLogEntry& item);

    bool IsStreamSendStart(const TLogEntry& item);

    bool IsStreamSendEnd(const TLogEntry& item);

    bool IsRecvSettingsHeaderTableSize(const TLogEntry& item);

    ui32 ExtractRecvSettingsValue(const TLogEntry& item);

    bool IsRecvHeadersBlockStart(const TLogEntry& item);

    bool IsRecvHeadersBlockEnd(const TLogEntry& item);

    bool IsRecvHeadersPayload(const TLogEntry& item);

    bool IsRecvContinuationPayload(const TLogEntry& item);

    TString ExtractRecvHeaderBlock(const TLogEntry& item);

    bool IsRecvRstStream(const TLogEntry& item);

    bool IsSendRstStream(const TLogEntry& item);

    bool IsRecvGoAway(const TLogEntry& item);

    bool IsSendGoAway(const TLogEntry& item);

    bool IsErrorMessage(const TLogEntry& item);

    bool IsRespHeaders(const TLogEntry& item);

    TMaybe<ui32> ParseStreamId(const TLogEntry& item);

    struct TReqInfo {
        TString Method;
        TString Url;

        TReqInfo(TString meth, TString url)
            : Method(std::move(meth))
            , Url(std::move(url))
        {}

        TString ToString() const {
            return Method + ' ' + Url;
        }
    };

    TMaybe<TReqInfo> ParseReqInfo(const TLogEntry& item);

    TMaybe<TString> ParseUserAgent(const TLogEntry& item);

    TMaybe<TString> ParseReqContentLength(const TLogEntry& item);

    TMaybe<TString> ParseReqRecvLength(const TLogEntry& item);

    TMaybe<TString> ParseRespContentLength(const TLogEntry& item);

    TMaybe<TErrorDetails> ParseError(const TLogEntry& item);


    void DumpSession(const TDeque<TLogEntry>& sess, IOutputStream& out);


    template <class TSession, class TSessions>
    TSession* ParseNextLogLine(TSession* sess, TSessions& active, TString& line) {
        if (!StripLine(line)) {
            return nullptr;
        }

        if (IsTraceLine(line)) {
            if (sess && !sess->Log.empty()) {
                sess->Log.back().Trace.push_back(line);
            }
            return nullptr;
        }

        sess = &active[ParseAddr(line)];

        try {
            sess->Log.emplace_back(ParseLogLine(line));
        } catch (const yexception& e) {
            Cerr << __LOCATION__ << " could not parse line (" << e.what() << "): " << line.Quote() << Endl;
            return nullptr;
        }

        return sess;
    }
}
