// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3

#pragma once

#include <string>
#include <chrono>
#include <fstream>
#include <mutex>
#include <vector>
#include <unordered_map>
#include <cstdint>
#include <algorithm>
#include <cstdio>
#include "../util/logger.h"

namespace dragonx {
namespace util {

/**
 * @brief Lightweight per-frame performance profiler that writes periodic
 *        summaries to a log file.
 *
 * Usage:
 *   PerfLog::instance().beginFrame();
 *   { PERF_SCOPE("EventPoll"); ... }
 *   { PERF_SCOPE("AppUpdate"); ... }
 *   { PERF_SCOPE("AppRender"); ... }
 *   PerfLog::instance().endFrame();
 *
 * Every N frames (default 300 ≈ 5s at 60fps) it appends a summary block
 * to  <ObsidianDragonDir>/perf.log  with min/avg/max/p95 for each zone.
 */
class PerfLog {
public:
    static PerfLog& instance() {
        static PerfLog s;
        return s;
    }

    /// Open (or re-open) the log file. Called once at startup.
    bool init(const std::string& path, int flushIntervalFrames = 300) {
        std::lock_guard<std::mutex> lk(mutex_);
        flushInterval_ = flushIntervalFrames;
        file_.open(path, std::ios::out | std::ios::app);
        if (!file_.is_open()) {
            DEBUG_LOGF("[PerfLog] Failed to open %s\n", path.c_str());
            return false;
        }
        DEBUG_LOGF("[PerfLog] Logging to %s (flush every %d frames)\n",
               path.c_str(), flushInterval_);

        // Header
        file_ << "\n========== PerfLog started ==========\n";
        file_.flush();
        ready_ = true;
        return true;
    }

    /// Call at the very start of the frame.
    void beginFrame() {
        if (!ready_) return;
        frameStart_ = Clock::now();
    }

    /// Call at the very end of the frame (after present/swap).
    void endFrame() {
        if (!ready_) return;
        double totalUs = usFrom(frameStart_);
        record("FRAME_TOTAL", totalUs);
        frameCount_++;
        if (frameCount_ >= flushInterval_) {
            flush();
            frameCount_ = 0;
        }
    }

    /// Begin a named zone. Returns an opaque time point.
    std::chrono::steady_clock::time_point beginZone() {
        return Clock::now();
    }

    /// End a named zone. Pass the time point from beginZone().
    void endZone(const char* name, std::chrono::steady_clock::time_point start) {
        if (!ready_) return;
        record(name, usFrom(start));
    }

private:
    using Clock = std::chrono::steady_clock;
    using TimePoint = Clock::time_point;

    PerfLog() = default;
    ~PerfLog() {
        if (ready_ && frameCount_ > 0) flush();
    }

    PerfLog(const PerfLog&) = delete;
    PerfLog& operator=(const PerfLog&) = delete;

    static double usFrom(TimePoint start) {
        return std::chrono::duration<double, std::micro>(Clock::now() - start).count();
    }

    /// Record a single timing sample (microseconds).
    void record(const char* name, double us) {
        auto& bucket = buckets_[name];
        bucket.samples.push_back(us);
    }

    /// Write accumulated stats and reset.
    void flush() {
        std::lock_guard<std::mutex> lk(mutex_);
        if (!file_.is_open()) return;

        auto now = std::chrono::system_clock::now();
        auto tt = std::chrono::system_clock::to_time_t(now);
        char timeBuf[64];
        std::strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", std::localtime(&tt));

        file_ << "\n--- " << timeBuf << " (" << frameCount_ << " frames) ---\n";

        // Sort zone names for consistent output
        std::vector<std::string> names;
        names.reserve(buckets_.size());
        for (auto& kv : buckets_) names.push_back(kv.first);
        std::sort(names.begin(), names.end());

        for (auto& name : names) {
            auto& b = buckets_[name];
            if (b.samples.empty()) continue;
            std::sort(b.samples.begin(), b.samples.end());
            size_t n = b.samples.size();
            double sum = 0;
            for (double v : b.samples) sum += v;
            double avg = sum / n;
            double mn = b.samples.front();
            double mx = b.samples.back();
            double p95 = b.samples[std::min(n - 1, (size_t)(n * 0.95))];
            double p99 = b.samples[std::min(n - 1, (size_t)(n * 0.99))];

            char line[256];
            std::snprintf(line, sizeof(line),
                "  %-28s  min=%7.0f  avg=%7.0f  p95=%7.0f  p99=%7.0f  max=%7.0f us\n",
                name.c_str(), mn, avg, p95, p99, mx);
            file_ << line;
        }

        // FPS summary from FRAME_TOTAL
        auto it = buckets_.find("FRAME_TOTAL");
        if (it != buckets_.end() && !it->second.samples.empty()) {
            auto& s = it->second.samples;
            double avgFrame = 0;
            for (double v : s) avgFrame += v;
            avgFrame /= s.size();
            double fps = (avgFrame > 0) ? 1000000.0 / avgFrame : 0;
            char fpsLine[128];
            std::snprintf(fpsLine, sizeof(fpsLine),
                "  FPS: %.1f (avg frame %.1f us = %.2f ms)\n",
                fps, avgFrame, avgFrame / 1000.0);
            file_ << fpsLine;
        }

        file_.flush();

        // Reset all buckets
        for (auto& kv : buckets_) kv.second.samples.clear();
    }

    struct Bucket {
        std::vector<double> samples; // microseconds
    };

    std::mutex mutex_;
    std::ofstream file_;
    bool ready_ = false;
    int flushInterval_ = 300;
    int frameCount_ = 0;
    TimePoint frameStart_;
    std::unordered_map<std::string, Bucket> buckets_;
};

/// RAII scope timer — records duration under a named zone.
struct PerfScope {
    const char* name;
    std::chrono::steady_clock::time_point start;
    PerfScope(const char* n) : name(n), start(PerfLog::instance().beginZone()) {}
    ~PerfScope() { PerfLog::instance().endZone(name, start); }
};

#define PERF_SCOPE(name) ::dragonx::util::PerfScope _perf_##__LINE__(name)

// Manual begin/end for zones that span across scopes
#define PERF_BEGIN(var) auto var = ::dragonx::util::PerfLog::instance().beginZone()
#define PERF_END(name, var) ::dragonx::util::PerfLog::instance().endZone(name, var)

} // namespace util
} // namespace dragonx
