#include "http2_log.h"

#include <balancer/kernel/helpers/errors.h>
#include <balancer/kernel/log/errorlog.h>

#include <library/cpp/string_utils/base64/base64.h>
#include <library/cpp/string_utils/relaxed_escaper/relaxed_escaper.h>

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

namespace NSrvKernel::NHTTP2 {
    namespace {
        ELogPriority DoGetLogPriority(TLog* log) noexcept {
            // TODO (velavokr): it should have been just FiltrationLevel but we also use DefaultPriority for filtering
            return log ? (ELogPriority)std::min(log->DefaultPriority(), log->FiltrationLevel()) : TLOG_EMERG;
        }
    }

    // TMultilogBackend ================================================================================================

    TBilogBackend::TBilogBackend(TLog* first, TLog* second) noexcept
        : First_(DoGetLogPriority(first), first)
        , Second_(DoGetLogPriority(second), second)
        , MaxPriority_(std::max(First_.first, Second_.first))
    {}

    void TBilogBackend::WriteData(const TLogRecord& rec) noexcept {
        try {
            for (auto log : {First_, Second_}) {
                if (log.second && log.first >= rec.Priority) {
                    // The constructor guarantees we do not have nullptrs here
                    log.second->Write(rec.Priority, rec.Data, rec.Len);
                }
            }
        } catch (...) {
            Y_VERIFY_DEBUG(false, "exception during logging: %s", CurrentExceptionMessage().data());
        }
    }

    void TBilogBackend::ReopenLog() {
        for (auto log : {First_, Second_}) {
            if (log.second) {
                log.second->ReopenLog();
            }
        }
    }

    ELogPriority TBilogBackend::FiltrationLevel() const noexcept {
        return MaxPriority_;
    }


    // TLogLabel =======================================================================================================

    TLogLabel::TLogLabel(TStringBuf moduleName, const TConnDescr& mainDescr) noexcept
        : ModuleName_(moduleName)
        , MainDescr_(&mainDescr)
    {}

    TLogLabel::TLogLabel(const TString& prefix, const TString& defaultSuffix) noexcept
        : Prefix_(prefix)
        , DefaultSuffix_(defaultSuffix)
    {}

    TStringBuf TLogLabel::GetPrefix() noexcept {
        try {
            if (!Prefix_ && MainDescr_) {
                TStringOutput sout(Prefix_);
                NLogImpl::DoPrintRemoteAddr(sout, *MainDescr_);
                sout << ' ';
            }
        } catch (...) {
            Y_VERIFY_DEBUG(false, "exception during logging: %s", CurrentExceptionMessage().data());
            Prefix_ = "FAILED_PRINT";
        }
        return Prefix_;
    }

    TStringBuf TLogLabel::GetSuffix(ELogPriority logLevel) noexcept {
        if (logLevel < Suffixes_.size() && MainDescr_) {
            auto& suffix = Suffixes_[logLevel];

            try {
                if (!suffix) {
                    TStringOutput sout(suffix);
                    sout << ' ';
                    NLogImpl::DoPrintModule(sout, logLevel, ModuleName_);
                    sout << '\t';
                }
            } catch (...) {
                Y_VERIFY_DEBUG(false, "exception during logging: %s", CurrentExceptionMessage().data());
                return "FAILED_PRINT";
            }

            return suffix;
        } else {
            return DefaultSuffix_;
        }
    }


    // TLogger =========================================================================================================

    TString PrintCont(const TCont* cont) noexcept {
        TStringBuilder str;
        PrintCont(str.Out, cont);
        return str;
    }

    void PrintCont(IOutputStream& out, const TCont* cont) noexcept {
        try {
            try {
                if (cont) {
                    out << "cont(name=" << cont->Name() << ",ptr=" << (void*) &cont
                        << ")";
                } else {
                    out << "cont(nullptr)";
                }
            } catch (...) {
                out << "FAILED_PRINT(" << CurrentExceptionMessage() << ')';
            }
        } catch (...) {
            Y_VERIFY_DEBUG(false, "exception during logging: %s", CurrentExceptionMessage().data());
        }
    }

    TSourceLocation GetShortLocation(TSourceLocation src) noexcept {
        TStringBuf fName = src.File.RNextTok(LOCSLASH_C);
        return TSourceLocation(fName, src.Line);
    }

    TStringBuf StripPrettyFunction(TStringBuf func) noexcept {
        // TODO (velavokr): doesn't support complex cases
        return func.Before('(').After(' ').After(' ');
    }

    TString FormatException(const yexception& e) noexcept {
        return TString::Join("(", TypeName(e), ") ", e.what());
    }


    TLoggerBlockGuard::TLoggerBlockGuard(TLogger& logger, ELogPriority logLevel, TStringBuf func) noexcept
        : Logger_(&logger)
        , Func_(func)
        , LogLevel_(logLevel)
        , Unwinding_(UncaughtException())
    {}

    TLoggerBlockGuard::~TLoggerBlockGuard() {
        if (auto* log = (Logger_ ? Logger_->GetLog(LogLevel_) : nullptr)) {
            Logger_->DoLogBlockEnd(*log, LogLevel_, Func_, Unwinding_);
        }
    }

    TLogger::TLogger(TLog* log, TLogLabel& logLabel, const TContExecutor& executor) noexcept
        : Log_(log)
        , LogLabel_(logLabel)
        , Executor_(executor)
        , LogLevel_(log ? (ELogPriority)std::min(log->DefaultPriority(), log->FiltrationLevel()) : TLOG_EMERG)
    {}

    TLogger::~TLogger() {
        Y_VERIFY(Cache_.empty());
    }

    void TLogger::DoLogBlockEnd(TLog& log, ELogPriority logLevel, TStringBuf name, bool wasUnwinding) noexcept {
        try {
            if (UncaughtException() && !wasUnwinding) {
                DoLogMessage(log, logLevel, name, {"[Exception]"}, {}, TStringBuf());
            } else {
                DoLogMessage(log, logLevel, name, {"[Done]"}, {}, TStringBuf());
            }
        } catch (...) {
            Y_VERIFY_DEBUG(false, "exception during logging: %s", CurrentExceptionMessage().data());
        }
        Pop();
    }

    void TLogger::Push() noexcept {
        // TODO (velavokr): could be made much faster
        const auto* cont = GetCurrentCont();

        TContCache::insert_ctx ctx;
        auto it = Cache_.find(cont, ctx);
        if (it == Cache_.end()) {
            Cache_.insert_direct(std::make_pair(cont, PrintCont(cont)), ctx)->second.Push();
        } else {
            it->second.Push();
        }
    }

    void TLogger::Pop() noexcept {
        const auto* cont = GetCurrentCont();
        auto it = Cache_.find(cont);
        if (it != Cache_.end()) {
            if (!it->second.Pop()) {
                Cache_.erase(it);
            }
        }
    }

    const TCont* TLogger::GetCurrentCont() const noexcept {
        const auto* contRep = Executor_.Running();
        return contRep ? contRep : nullptr;
    }

    namespace NImpl {
        TString DoEscape(TStringBuf str, EEscapeMode mode) noexcept {
            try {
                switch (mode) {
                case EEscapeMode::Unicode:
                    return NEscJ::EscapeJ<true>(str);
                case EEscapeMode::C:
                    return TString::Join("\"", EscapeC(str), "\"");
                case EEscapeMode::Hex:
                    return HexEncode(str);
                case EEscapeMode::Base64:
                    return Base64Encode(str);
                }
            } catch (...) {
                return TString{"FAILED_ESCAPE"};
            }
        }

        TString DoEscape(const TChunkList& lst, EEscapeMode mode) noexcept {
            return DoEscape(Union(lst)->AsStringBuf(), mode);
        }

        TString DoEscape(const TChunk& ch, EEscapeMode mode) noexcept {
            return DoEscape(ch.AsStringBuf(), mode);
        }

        TString DoEscape(const TChunkPtr& ch, EEscapeMode mode) noexcept {
            return DoEscape(ch ? ch->AsStringBuf() : "", mode);
        }

        TString DoEscape(const THeader& h, EEscapeMode mode) noexcept {
            return TStringBuilder() << DoEscape(h.Key, mode) << ": " << DoEscape(h.Value, mode);
        }
    }
}
