#include <contrib/libs/re2/re2/re2.h>

#include "log_parsers.h"

#include <util/string/util.h>


namespace NH2Log {

    namespace {
        struct TPatterns {
            RE2 Cont{"cont[(]name=([^,]+),ptr=0x([^)]+)[)]"};
            RE2 StreamId{"[sS]treamId_?=([0-9]+)[,}]"};
            RE2 GOAWAY{"[eE]rrorCode=([A-Z_]+), EscC[(][dD]ebugData[)]=([^}]*)"};
            RE2 RST_STREAM{"[eE]rrorCode=([A-Z_]+)[,}]"};
            RE2 UserAgent{"user-agent: (.*?)(?:\\[CRLF\\])"};
            RE2 MethAndUrl{"=([A-Z]+) ([^ ]+) HTTP/1"};
            RE2 StreamException{"TStream::OnException"};
            RE2 HeadersBlock{"EscBase64[(][a-zA-Z_>.-]*[)]=([A-Za-z0-9/+=]*)[,}]"};
            RE2 ReqContentLength{"ContentLength_=([0-9]+)[,}]"};
            RE2 ReqRecvLength{"RecvLength_=([0-9]+)[,}]"};
            RE2 RespContentLength{"content-length: ([0-9]+)(?:\\[CRLF\\])"};
        };

        bool IsH1Session(const TLogEntry& item) {
            return item.Event.Level == 1 && item.Event.Name.StartsWith("http/1");
        }

        bool IsH2Session(const TLogEntry& item) {
            return item.Event.Level == 1 && item.Event.Name == "http/2 session";
        }

        bool IsRecvEvent(const TLogEntry& item) {
            return item.Event.EventType == EEventType::Event && item.Event.Name.StartsWith("Received ");
        }

        TContInfo ParseCont(TStringBuf rawCont) {
            TContInfo result;

            re2::StringPiece re2Name, re2Ptr;
            Y_ENSURE_EX(RE2::FullMatch({rawCont.data(), rawCont.size()}, Default<TPatterns>().Cont, &re2Name, &re2Ptr),
                        yexception() << rawCont);
            result.Name = TStringBuf(re2Name.data(), re2Name.size());
            result.Ptr = TStringBuf(re2Ptr.data(), re2Ptr.size());

            if (result.Name.StartsWith("http2stream#")) {
                result.StreamId = FromString<ui32>(result.Name.After('#'));
                result.ContType = EContType::Stream;
            } else if (result.Name == "http2output") {
                result.ContType = EContType::Output;
            } else if (result.Name == "connection") {
                result.ContType = EContType::Conn;
            } else if (result.Name == "clientToBackend") {
                result.ContType = EContType::ProxyClientToBackend;
            }

            return result;
        }

        TEventInfo ParseEvent(TStringBuf rawEvent) {
            TEventInfo result;
            result.RawEvent = rawEvent;
            TStringBuf rawName;
            rawEvent.Split('\t', rawName, result.Data);

            result.Name = StripStringLeft(rawName, EqualsStripAdapter('.'));
            result.Level = rawName.size() - result.Name.size();
            result.IsUnwinding = result.Name.SkipPrefix("[UNWIND]");

            Y_ENSURE_EX(!!result.Name,
                        yexception() << rawEvent);

            if (result.Name.SkipPrefix("[Event] ")) {
                result.BlockType = EBlockType::Inline;
                result.EventType = EEventType::Event;
            } else if (result.Name.ChopSuffix(" [Exception]")) {
                result.EventType = EEventType::Exception;
            } else if (result.Name.ChopSuffix(" [Done]")) {
                result.EventType = EEventType::Exit;
            }

            if (result.Name.StartsWith("catch ...")) {
                result.BlockType = EBlockType::CatchAll;
            } else if (result.Name.StartsWith("catch ")) {
                result.BlockType = EBlockType::Catch;
            } else if (result.Name.find(' ') == TStringBuf::npos) {
                result.BlockType = EBlockType::Func;
            }

            return result;
        }

        TMaybe<TString> ParseRstStream(const TLogEntry& item) {
            re2::StringPiece re2ErrorCode;
            if (RE2::PartialMatch({item.Event.Data.data(), item.Event.Data.size()}, Default<TPatterns>().RST_STREAM, &re2ErrorCode)) {
                return TString(re2ErrorCode.data(), re2ErrorCode.size());
            } else {
                return Nothing();
            }
        }

        struct TGoAway {
            TString ErrorCode;
            TString DebugData;
        };

        TMaybe<TGoAway> ParseGoAway(const TLogEntry& item) {
            re2::StringPiece re2ErrorCode, re2DebugData;
            if (RE2::PartialMatch(
                {item.Event.Data.data(), item.Event.Data.size()}, Default<TPatterns>().GOAWAY, &re2ErrorCode, &re2DebugData
            )) {
                TGoAway result;
                result.ErrorCode = TString(re2ErrorCode.data(), re2ErrorCode.size());
                result.DebugData = TString(re2DebugData.data(), re2DebugData.size());
                return result;
            } else {
                return Nothing();
            }
        }

        TMaybe<TStringBuf> ExtractAfterMarker(TStringBuf data, const TStringBuf marker, const str_spn& range) {
            const auto start = data.find(marker);
            if (start == TStringBuf::npos) {
                return Nothing();
            }
            data = data.SubStr(start + marker.size());
            return TStringBuf(
                data.begin(),
                range.cbrk(data.begin(), data.end())
            );
        }
    }


    TString& StripLine(TString& line) {
        line.Detach();
        auto end = line.cend();
        StripRangeEnd(line.cbegin(), end);
        line.resize(end - line.begin());
        return line;
    }

    bool IsTraceLine(TStringBuf line) {
        static const str_spn entryStart("1-9[", true);
        return line && entryStart.c_chars_table[(ui8)line.front()];
    }

    TLogEntry ParseLogLine(const TString& line) {
        TLogEntry result;
        TString raw = line;
        TStringBuf rawLine = (result.RawPrefix = line);
        result.Addr = rawLine.NextTok(' ');
        result.TimeRaw = rawLine.NextTok(' ');
        result.TimeParsed = TInstant::ParseIso8601(result.TimeRaw);
        result.Module = rawLine.NextTok(' ');
        result.LogLevel = FromString<int>(rawLine.NextTok('\t').After('[').Before(']'));
        result.Cont = ParseCont(rawLine.NextTok('\t'));
        result.RawPrefix.resize(line.size() - rawLine.size() - 1);
        result.Event = ParseEvent(rawLine);
        return result;
    }

    TStringBuf ParseAddr(TStringBuf line) {
        auto addr = line.Before(' ');
        Y_ENSURE(!!addr);
        return addr;
    }

    bool IsConnCont(const TLogEntry& item) {
        return item.Cont.ContType == EContType::Conn;
    }

    bool IsOutputCont(const TLogEntry& item) {
        return item.Cont.ContType == EContType::Output;
    }

    bool IsException(const TLogEntry& item) {
        return item.Event.EventType == EEventType::Exception;
    }

    bool IsCatch(const TLogEntry& item) {
        return item.Event.EventType == EEventType::Entry
               && IsIn({EBlockType::Catch, EBlockType::CatchAll}, item.Event.BlockType);
    }

    bool IsRecvPriFail(const TLogEntry& item) {
        return item.Event.EventType == EEventType::Exception
               && item.Event.BlockType == EBlockType::Func
               && item.Event.Name.EndsWith("TConnInput::RecvPreface");
    }

    bool IsH1SessionStart(const TLogEntry& item) {
        return IsH1Session(item) && item.Event.EventType == EEventType::Entry;
    }

    bool IsH1SessionEnd(const TLogEntry& item) {
        return IsH1Session(item) && !IsH2SessionStart(item);
    }

    bool IsH2SessionStart(const TLogEntry& item) {
        return IsH2Session(item) && item.Event.EventType == EEventType::Entry;
    }

    bool IsH2SessionEnd(const TLogEntry& item) {
        return IsH2Session(item) && !IsH2SessionStart(item);
    }

    bool IsStreamOpen(const TLogEntry& item) {
        return item.Event.EventType == EEventType::Entry && item.Event.Name.EndsWith("TStream::Open");
    }

    bool IsStreamFinish(const TLogEntry& item) {
        return item.Event.EventType == EEventType::Entry && item.Event.Name.EndsWith("TStream::OnFinish");
    }

    bool IsStreamDispose(const TLogEntry& item) {
        return item.Event.EventType == EEventType::Entry && item.Event.Name.EndsWith("TConnection::DisposeStream");
    }

    bool IsFinalStateClosed(const TLogEntry& item) {
        return item.Event.Data.find("FinalState=Closed") != TStringBuf::npos;
    }

    bool IsFinalStateResetByClient(const TLogEntry& item) {
        return item.Event.Data.find("FinalState=ResetByClient") != TStringBuf::npos;
    }

    bool IsFinalStateResetByServer(const TLogEntry& item) {
        return item.Event.Data.find("FinalState=ResetByServer") != TStringBuf::npos;
    }

    bool IsStreamRunStart(const TLogEntry& item) {
        return item.Event.EventType == EEventType::Entry && item.Event.Name.EndsWith("TStream::OnRun");
    }

    bool IsStreamRunEnd(const TLogEntry& item) {
        return item.Event.EventType == EEventType::Exit && item.Event.Name.EndsWith("TStream::OnRun");
    }

    bool IsStreamSendStart(const TLogEntry& item) {
        return item.Event.EventType == EEventType::Entry && item.Event.Name.EndsWith("TStream::DoSend");
    }

    bool IsStreamSendEnd(const TLogEntry& item) {
        return item.Event.EventType == EEventType::Exit && item.Event.Name.EndsWith("TStream::DoSend");
    }

    bool IsRecvSettingsHeaderTableSize(const TLogEntry& item) {
        return item.Event.EventType == EEventType::Event
            && item.Event.Name.EndsWith("Setting change")
            && item.Event.Data.StartsWith("{HeaderTableSize");
    }

    ui32 ExtractRecvSettingsValue(const TLogEntry& item) {
        static const TStringBuf marker = "settingsValue=";
        static const str_spn num("0-9", true);
        auto value = ExtractAfterMarker(item.Event.Data, marker, num);
        Y_ENSURE(!!value, yexception() << item.Event.Data);
        return FromString(*value);
    }

    bool IsRecvHeadersBlockStart(const TLogEntry& item) {
        return item.Event.EventType == EEventType::Entry && item.Event.Name.EndsWith("TConnection::RecvFullHeaderBlock");
    }

    bool IsRecvHeadersBlockEnd(const TLogEntry& item) {
        return item.Event.EventType == EEventType::Exit && item.Event.Name.EndsWith("TConnection::RecvFullHeaderBlock");
    }

    bool IsRecvHeadersPayload(const TLogEntry& item) {
        return item.Event.EventType == EEventType::Event && item.Event.Name.EndsWith("HEADERS payload");
    }

    bool IsRecvContinuationPayload(const TLogEntry& item) {
        return item.Event.EventType == EEventType::Event && item.Event.Name.EndsWith("CONTINUATION payload");
    }

    TString ExtractRecvHeaderBlock(const TLogEntry& item) {
        re2::StringPiece headersBlock;
        if (RE2::PartialMatch({item.Event.Data.data(), item.Event.Data.size()}, Default<TPatterns>().HeadersBlock, &headersBlock)) {
            return {headersBlock.data(), headersBlock.size()};
        }
        ythrow yexception() << "could not parse header block " << item.Event.Data;
    }

    bool IsRecvRstStream(const TLogEntry& item) {
        return IsRecvEvent(item) && item.Event.Name.EndsWith("RST_STREAM");
    }

    bool IsSendRstStream(const TLogEntry& item) {
        return item.Event.EventType == EEventType::Entry
               && item.Event.Name.find("TConnOutput") != TStringBuf::npos
               && item.Event.Name.EndsWith("SendRstStream");
    }

    bool IsRecvGoAway(const TLogEntry& item) {
        return IsRecvEvent(item) && item.Event.Name.EndsWith("GOAWAY");
    }

    bool IsSendGoAway(const TLogEntry& item) {
        return item.Event.EventType == EEventType::Entry && item.Event.Name.EndsWith("TConnection::OnConnError");
    }

    bool IsErrorMessage(const TLogEntry& item) {
        return IsSendGoAway(item) || IsRecvGoAway(item) || IsSendRstStream(item) || IsRecvRstStream(item);
    }

    bool IsRespHeaders(const TLogEntry& item) {
        return item.Event.EventType == EEventType::Entry && item.Event.Name.EndsWith("Response headers");
    }

    TMaybe<TString> ParseUserAgent(const TLogEntry& item) {
        re2::StringPiece re2UserAgent;
        if (IsStreamOpen(item) && RE2::PartialMatch(
            {item.Event.Data.data(), item.Event.Data.size()}, Default<TPatterns>().UserAgent, &re2UserAgent
        )) {
            return TString(re2UserAgent.data(), re2UserAgent.size());
        } else {
            return Nothing();
        }
    }

    TMaybe<TReqInfo> ParseReqInfo(const TLogEntry& item) {
        re2::StringPiece re2Meth, re2Url;
        if (IsStreamOpen(item) && RE2::PartialMatch(
            {item.Event.Data.data(), item.Event.Data.size()},
            Default<TPatterns>().MethAndUrl,
            &re2Meth,
            &re2Url
        )) {
            return TReqInfo({re2Meth.data(), re2Meth.size()}, {re2Url.data(), re2Url.size()});
        } else {
            return Nothing();
        }
    }

    TMaybe<TString> ParseReqContentLength(const TLogEntry& item) {
        re2::StringPiece re2ContentLength;
        if (IsStreamFinish(item) && RE2::PartialMatch(
            {item.Event.Data.data(), item.Event.Data.size()},
            Default<TPatterns>().ReqContentLength,
            &re2ContentLength
        )) {
            return TString(re2ContentLength.data(), re2ContentLength.size());
        } else {
            return Nothing();
        }
    }

    TMaybe<TString> ParseReqRecvLength(const TLogEntry& item) {
        re2::StringPiece re2RecvLength;
        if (IsStreamFinish(item) && RE2::PartialMatch(
            {item.Event.Data.data(), item.Event.Data.size()},
            Default<TPatterns>().ReqRecvLength,
            &re2RecvLength
        )) {
            return TString(re2RecvLength.data(), re2RecvLength.size());
        } else {
            return Nothing();
        }
    }

    TMaybe<TString> ParseRespContentLength(const TLogEntry& item) {
        re2::StringPiece re2ContentLength;
        if (IsRespHeaders(item) && RE2::PartialMatch(
            {item.Event.Data.data(), item.Event.Data.size()},
            Default<TPatterns>().RespContentLength,
            &re2ContentLength
        )) {
            return TString(re2ContentLength.data(), re2ContentLength.size());
        } else {
            return Nothing();
        }
    }

    TMaybe<ui32> ParseStreamId(const TLogEntry& item) {
        if (item.Cont.StreamId) {
            return item.Cont.StreamId;
        } else {
            long re2StreamId = 0;
            if (RE2::PartialMatch({item.Event.Data.data(), item.Event.Data.size()}, Default<TPatterns>().StreamId, &re2StreamId)) {
                return (ui32)re2StreamId;
            } else {
                return Nothing();
            }
        }
    }

    TMaybe<TErrorDetails> ParseError(const TLogEntry& item) {
        if (IsRecvRstStream(item) || IsSendRstStream(item)) {
            TErrorDetails error;
            error.Type = EErrorType::RST_STREAM;
            error.Source = IsRecvRstStream(item) ? EErrorSource::Recv : EErrorSource::Send;
            auto rst = ParseRstStream(item);
            auto streamId = ParseStreamId(item);
            Y_ENSURE_EX(rst, yexception() << item.RawPrefix << '\t' << item.Event.RawEvent);
            Y_ENSURE_EX(streamId, yexception() << item.RawPrefix << '\t' << item.Event.RawEvent);
            error.Code = *rst;
            error.StreamId = *streamId;
            return error;
        } else if (IsRecvGoAway(item) || IsSendGoAway(item)) {
            TErrorDetails error;
            error.Type = EErrorType::GOAWAY;
            error.Source = IsRecvGoAway(item) ? EErrorSource::Recv : EErrorSource::Send;
            auto goAway = ParseGoAway(item);
            Y_ENSURE_EX(goAway, yexception() << item.RawPrefix << '\t' << item.Event.RawEvent);
            error.Code = goAway->ErrorCode;
            error.DebugData = goAway->DebugData;
            return error;
        } else {
            return Nothing();
        }
    }

    void DumpSession(const TDeque<TLogEntry>& sess, IOutputStream& out) {
        for (const auto& entry : sess) {
            out << entry.RawPrefix << '\t' << entry.Event.RawEvent << '\n';
            for (const auto& traceItem : entry.Trace) {
                out << traceItem << '\n';
            }
        }
    }
}

template <>
void Out<NH2Log::TErrorDetails>(IOutputStream& out, TTypeTraits<NH2Log::TErrorDetails>::TFuncParam value) {
    out << value.Source << " " << value.Type << "=" << value.Code;
    if (NH2Log::EErrorType::GOAWAY == value.Type) {
        out << " " << value.DebugData.Quote();
    }
}

template <>
void Out<NH2Log::TError>(IOutputStream& out, TTypeTraits<NH2Log::TError>::TFuncParam value) {
    out << value.Source << '-' << value.Type << '-' << value.Code;
}
