#include "exp_common.h"

#include <balancer/kernel/helpers/misc.h>
#include <balancer/kernel/http/parser/request_builder.h>

#include <util/generic/maybe.h>
#include <util/stream/zlib.h>

using namespace NExpCommon;

static const NRegExp::TFsm EXP_HEADERS = NRegExp::TFsm(
        "X-Yandex-ExpConfigVersion(-Pre)?|X-Yandex-ExpBoxes(-Pre)?|X-Yandex-ExpFlags(-Pre)?|X-Yandex-LogstatUID"
        "|X-Yandex-ExpSplitParams|X-Yandex-ExpBoxes-Crypted(-Pre)?",
    NRegExp::TFsm::TOptions().SetCaseInsensitive(true)
);

const NRegExp::TFsm& NExpCommon::ExpHeaders() noexcept {
    return EXP_HEADERS;
}

TError TRemoteLogOutput::DoSendHead(TResponse&& response, const bool forceClose, TInstant deadline) {
    if (response.ResponseLine().StatusCode >= 200) {
        StatusCode_ = response.ResponseLine().StatusCode;
    }
    return Slave_->SendHead(std::move(response), forceClose, deadline);
}

TError TRemoteLogOutput::DoSend(TChunkList lst, TInstant deadline) noexcept {
    return Slave_->Send(std::move(lst), deadline);
}

TJsonNoExceptWriter::TJsonNoExceptWriter(IExceptionlessOutputStream* out) noexcept {
    try {
        TJsonWriterConfig conf;
        Writer_.Reset(new TJsonWriter(out, conf, true)); // don't flush in destructor - all flushes are explicit, implicit one may use moved output
    } catch (...) {
        HasExceptWriter_ = true;
    }
}

void TJsonNoExceptWriter::Open() noexcept {
    if (!HasExceptWriter_) {
        try {
            Writer_->OpenMap();
        } catch (...) {
            HasExceptWriter_ = true;
        }
    }
}

void TJsonNoExceptWriter::Close() noexcept {
    if (!HasExceptWriter_) {
        try {
            Writer_->CloseMap();
            Writer_->Flush();
        } catch (...) {
            HasExceptWriter_ = true;
        }
    }
}

void TJsonNoExceptWriter::WriteRequestLine(const TRequestLine& requestLine) noexcept {
    if (!HasExceptWriter_) {
        try {
            Writer_->Write("method", ToString(requestLine.Method));
            Writer_->Write("request", ToString(requestLine.Path) + ToString(requestLine.CGI));
            Writer_->Write("protocol", "HTTP/" + ToString(requestLine.MajorVersion) +
                                          '.' + ToString(requestLine.MinorVersion));
        } catch (...) {
            HasExceptWriter_ = true;
        }
    }
}

void TJsonNoExceptWriter::WriteHeaders(const THeaders& headers) noexcept {
    if (!HasExceptWriter_) {
        try {
            Writer_->Write("headers");
            Writer_->OpenArray();
            for (const auto& hdr : headers) {
                for(const auto& hdrValue : hdr.second){
                    Writer_->OpenArray();
                    Writer_->Write(hdr.first.AsStringBuf());
                    Writer_->Write(ToString(hdrValue));
                    Writer_->CloseArray();
                }
            }
            Writer_->CloseArray();
        } catch (...) {
            HasExceptWriter_ = true;
        }
    }
}

void TJsonNoExceptWriter::WriteProcessedHeaders(const THeaders& headers, IHeaderProcessor* headerProcessor) noexcept {
    if (!HasExceptWriter_) {
        try {
            Writer_->Write("headers");
            Writer_->OpenArray();
            for (const auto& hdr : headers) {
                for (const auto& value : hdr.second){
                    TMaybe<TString> processedValue =
                        headerProcessor->Process(hdr.first.AsStringBuf(), value.AsStringBuf());
                    if (processedValue.Defined()) {
                        Writer_->OpenArray();
                        Writer_->Write(hdr.first.AsStringBuf());
                        Writer_->Write(*processedValue);
                        Writer_->CloseArray();
                    }
                }
            }
            Writer_->CloseArray();
        } catch (...) {
            HasExceptWriter_ = true;
        }
    }
}

void TJsonNoExceptWriter::WriteBackendErrors(const size_t backendErrors) noexcept {
    WriteKeyValue("backend_errors", backendErrors);
}

void TJsonNoExceptWriter::WriteStatus(ui32 status) noexcept {
    WriteKeyValue("status", status);
}

void TJsonNoExceptWriter::WriteResponseTime(const TDuration responseTime) noexcept {
    WriteKeyValue("resptime_us", responseTime.MicroSeconds());
}

void TJsonNoExceptWriter::WriteHeadersLength(const size_t headersTotalLength) noexcept {
    WriteKeyValue("hdrs_len", headersTotalLength);
}

void TJsonNoExceptWriter::WriteTimestamp(time_t timestamp) noexcept {
    WriteKeyValue("timestamp", timestamp);
}

void TJsonNoExceptWriter::WriteUaasModeFlag() noexcept {
    WriteKeyValue("uaas_mode", "true");
}

void TJsonNoExceptWriter::WriteAddr(TStringBuf addr) noexcept {
    WriteKeyValue("addr", addr);
}

void TJsonNoExceptWriter::WriteService(TStringBuf service) noexcept {
    WriteKeyValue("service", service);
}


size_t TRemoteLoggerProperties::CompressLevel() const noexcept {
    const auto& data = CompressLevelFileReader_.Data();
    if (data.Id() != CompressLevelFileData_.Id()) {
        CompressLevelFileData_ = data;
        size_t level = 0;
        if (!data.Data().empty() && TryFromString(TStringBuf(data.Data()).substr(0, 1), level)) {
            return level;
        }
    }
    return 0;
}

bool TRemoteLoggerProperties::NoRemoteLogFileExists() const noexcept {
    return NoRemoteLogFileChecker_.Exists();
}

void TRemoteLoggerProperties::Init(IWorkerCtl* process, const TString& noRemoteLogFile, const TString& compressLevelFile) noexcept {
    if (!!noRemoteLogFile) {
        NoRemoteLogFileChecker_ = process->SharedFiles()->FileChecker(noRemoteLogFile, TDuration::Seconds(1));
    }

    if (!!compressLevelFile) {
        CompressLevelFileReader_ = process->SharedFiles()->FileReReader(compressLevelFile, TDuration::Seconds(1));
    }
}


void TRemoteLogger::Add(bool hasExcept, TChunkList&& lst) noexcept {
    if (!hasExcept) {
        TChunkList length(ToString(lst.size()));
        length.Push(" ");
        length.Append(std::move(lst));
        Queue_.Push(std::move(length));
        InQueue_.Set(Queue_.InQueue());
        Dropped_.Set(Queue_.Dropped());
    }
}

TError TRemoteLogger::Send() noexcept {
    TChunkList lst;

    if (Queue_.Pop(lst, TInstant::Max()) == ECANCELED) {
        return Y_MAKE_ERROR(TSystemError(ECANCELED));
    }

    TRequest request = BuildRequest().Method(EMethod::POST).Path("/").Version11();
    request.Headers().Add("Content-Type", "application/json");

    size_t compressLevel = Logger_->CompressLevel();

    if (compressLevel) {
        request.Headers().Add("Content-encoding", "deflate");
        TChunksOutputStream out;
        TZLibCompress zout(&out, ZLib::ZLib, compressLevel);
        for (auto it = lst.ChunksBegin(); it != lst.ChunksEnd(); ++it) {
            zout << it->AsStringBuf();
        }
        zout.Flush();
        zout.Finish();
        lst = std::move(out.Chunks());
    }

    size_t chunksLength = lst.size();
    TChunksInput chunksInput(std::move(lst));

    request.Props().ContentLength = chunksLength;

    TAddrHolder addr{ &TDummyAddr::Instance() };
    TTcpConnProps tcpConnProps(Process_, addr, addr, nullptr);
    TConnProps props(tcpConnProps, Now(), 0, nullptr);
    TNullStream nullStream;
    TConnDescr rpcLogDescr(chunksInput, nullStream, props);
    rpcLogDescr.Request = &request;

    return Logger_->RemoteLogStorage()->Run(rpcLogDescr);
}
