#pragma once

#include <drogon/drogon.h>

#include <chrono>
#include <cstddef>
#include <iostream>
#include <string>
#include <utility>

class Access_Logger {
public:
    static void log(const drogon::HttpRequestPtr& request,
                    const drogon::HttpResponsePtr& response,
                    const double elapsed_ms) {
        std::cout << "[http access] method=" << request->methodString()
                  << ", path=" << request->path()
                  << ", status=" << static_cast<int>(response->statusCode())
                  << ", cost_ms=" << elapsed_ms << std::endl;
    }
};

class Slow_Request_Monitor {
public:
    static void log_if_slow(const drogon::HttpRequestPtr& request,
                            const drogon::HttpResponsePtr& response,
                            const double elapsed_ms,
                            const std::size_t threshold_ms) {
        if (elapsed_ms < static_cast<double>(threshold_ms)) return;
        std::cout << "[slow http request] method=" << request->methodString()
                  << ", path=" << request->path()
                  << ", status=" << static_cast<int>(response->statusCode())
                  << ", cost_ms=" << elapsed_ms
                  << ", threshold_ms=" << threshold_ms << std::endl;
    }
};

class Scope_Timer {
public:
    Scope_Timer(std::string name, const std::size_t threshold_ms)
        : name_(std::move(name)),
          threshold_ms_(threshold_ms),
          start_(std::chrono::steady_clock::now()) {}

    ~Scope_Timer() {
        const auto elapsed_ms =
            std::chrono::duration<double, std::milli>(
                std::chrono::steady_clock::now() - start_).count();
        if (elapsed_ms < static_cast<double>(threshold_ms_)) return;
        std::cout << "[slow scope] name=" << name_
                  << ", cost_ms=" << elapsed_ms
                  << ", threshold_ms=" << threshold_ms_ << std::endl;
    }

private:
    std::string name_;
    std::size_t threshold_ms_;
    std::chrono::steady_clock::time_point start_;
};

inline constexpr const char* kHttpRequestStart = "ecap.http_request_start";

inline void register_http_performance_monitor(const bool access_log,
                                              const std::size_t slow_request_threshold_ms) {
    drogon::app().registerSyncAdvice(
        [](const drogon::HttpRequestPtr& request) -> drogon::HttpResponsePtr {
            request->attributes()->insert(kHttpRequestStart, std::chrono::steady_clock::now());
            return {};
        });
    drogon::app().registerPreSendingAdvice(
        [access_log, slow_request_threshold_ms](
            const drogon::HttpRequestPtr& request,
            const drogon::HttpResponsePtr& response) {
            const auto& start =
                request->attributes()->get<std::chrono::steady_clock::time_point>(
                    kHttpRequestStart);
            if (start == std::chrono::steady_clock::time_point{}) return;
            const auto elapsed_ms =
                std::chrono::duration<double, std::milli>(
                    std::chrono::steady_clock::now() - start).count();
            if (access_log) Access_Logger::log(request, response, elapsed_ms);
            Slow_Request_Monitor::log_if_slow(
                request, response, elapsed_ms, slow_request_threshold_ms);
        });
}
