#include <yandex/maps/wiki/common/tskv_logger.h>

#include <maps/libs/common/include/exception.h>
#include <maps/libs/log8/include/log8.h>
#include <yandex/maps/wiki/threadutils/threadedqueue.hpp>

#include <atomic>
#include <thread>
#include <unordered_map>
#include <ostream>
#include <chrono>
#include <map>

namespace maps::wiki::common {

namespace {
const std::string LOGGING_PREFIX = "TskvLogger: ";
const std::chrono::seconds REOPEN_FAIL_SLEEP_DURATION{5};
const std::chrono::seconds LOG_FAIL_SLEEP_DURATION{2};

std::string multiReplace(
    const std::string& str,
    const std::map<char, std::string>& substitusions)
{
    std::string retVal;
    retVal.reserve(str.size());
    for (auto it = str.begin(); it != str.end(); ++it) {
        auto substIt = substitusions.find(*it);
        if (substIt != substitusions.end()) {
            retVal.append(substIt->second);
        } else {
            retVal.push_back(*it);
        }
    }
    return retVal;
}

const std::map<char, std::string> KEY_SUBSTITUTION{
        {'\t', "\\t"},
        {'\n', "\\n"},
        {'\r', "\\r"},
        {'\0', "\\0"},
        {'\\', "\\\\"},
        {'\"', "\\\""},
        {'=', "\\="}
};

const std::map<char, std::string> VALUE_SUBSTITUTION{
        {'\t', "\\t"},
        {'\n', "\\n"},
        {'\r', "\\r"},
        {'\0', "\\0"},
        {'\\', "\\\\"},
        {'\"', "\\\""}
};

} // namespace

///
/// TskvMessage::Impl
///

class TskvMessage::Impl
{
public:
    std::string format;
    std::unordered_map<std::string, std::string> keyValueMap;
};

///
/// TskvMessage
///

TskvMessage::TskvMessage()
    : impl_(new Impl{})
{}

TskvMessage::TskvMessage(std::string tskvFormat)
    : impl_(new Impl{std::move(tskvFormat), {}})
{}

void TskvMessage::setParam(std::string key, std::string value)
{
    impl_->keyValueMap[std::move(key)] = std::move(value);
}

std::string TskvMessage::toString() const
{
    std::string retVal;

    if (!impl_->format.empty()) {
        retVal += "tskv";
        retVal += "\ttskv_format=";
        retVal += impl_->format;
        retVal += "\t";
    }

    bool first = true;
    for (const auto& [key, value]: impl_->keyValueMap) {
        if (!first) {
            retVal += "\t";
        }
        first = false;

        retVal += multiReplace(key, KEY_SUBSTITUTION);
        retVal += "=";
        retVal += multiReplace(value, VALUE_SUBSTITUTION);
    }

    return retVal;
}


COPYABLE_PIMPL_DEFINITIONS(TskvMessage)

///
/// TskvLogger::Impl
///

class TskvLogger::Impl
{
public:
    explicit Impl(std::string logPath);
    ~Impl();

    void log(std::string logMessage);

    void onLogrotate();

private:
    void writeLog() noexcept;

    const std::string logPath_;
    std::atomic_bool reopenFlag_;
    std::ofstream logStream_;
    ThreadedQueue<std::string> eventsQueue_;
    std::thread writerThread_;
};

TskvLogger::Impl::Impl(std::string logPath)
    : logPath_(std::move(logPath))
    , reopenFlag_(true)
    , writerThread_([this] { writeLog(); })
{}

TskvLogger::Impl::~Impl()
{
    eventsQueue_.finish();
    if (writerThread_.joinable()) {
        writerThread_.join();
    }
}

void TskvLogger::Impl::log(std::string logMessage)
{
    eventsQueue_.push(std::move(logMessage));
}

void TskvLogger::Impl::onLogrotate()
{
    reopenFlag_ = true;
}

void TskvLogger::Impl::writeLog() noexcept
{
    try {
        std::list<std::string> eventsBuffer;
        while (!eventsQueue_.finished() || eventsQueue_.pendingItemsCount()) {
            eventsQueue_.popAll(eventsBuffer);
            if (eventsBuffer.empty()) {
                continue;
            }

            while (reopenFlag_.exchange(false)) {
                INFO() << LOGGING_PREFIX
                       << "reopening log file `" << logPath_ << "'";
                logStream_.close();
                logStream_.open(logPath_, std::ios_base::app);

                if (!logStream_) {
                    WARN() << LOGGING_PREFIX
                           << "error reopening log file `" << logPath_ << "'";
                    if (eventsQueue_.finished()) {
                        ERROR() << LOGGING_PREFIX << "lost some records";
                        return;
                    }
                    reopenFlag_ = true;
                    std::this_thread::sleep_for(REOPEN_FAIL_SLEEP_DURATION);
                }
            }

            try {
                for (const auto& event : eventsBuffer) {
                    logStream_ << event << "\n";
                }
                logStream_.flush();
            } catch (const std::exception& ex) {
                WARN() << LOGGING_PREFIX << "error logging events: " << ex.what();
                eventsQueue_.pushAll(std::move(eventsBuffer));
                std::this_thread::sleep_for(LOG_FAIL_SLEEP_DURATION);
            }
        }
    } catch (const maps::Exception& ex) {
        FATAL() << LOGGING_PREFIX << "failed: " << ex;
    } catch (const std::exception& ex) {
        FATAL() << LOGGING_PREFIX << "failed: " << ex.what();
    }
}


///
/// ITskvLogger
///

void ITskvLogger::log(const TskvMessages& logMessages)
{
    for (auto& message : logMessages) {
        log(message);
    }
}

///
/// TskvLogger
///

TskvLogger::TskvLogger(std::string logPath)
        : impl_(new Impl(std::move(logPath)))
{}

void TskvLogger::log(TskvMessage logMessage)
{
    impl_->log(logMessage.toString());
}

void TskvLogger::onLogrotate()
{
    impl_->onLogrotate();
}

NONCOPYABLE_PIMPL_DEFINITIONS(TskvLogger)

} // namespace maps::wiki::common
