#include "stats_panel.h"

#include <QColor>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QPainter>
#include <QPushButton>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QVBoxLayout>
#include <QWidget>
#include <chrono>
#include <algorithm>
#include <cmath>

// ── SparkLine ─────────────────────────────────────────────────────────

SparkLine::SparkLine(QWidget* parent) : QWidget(parent)
{
    setMinimumSize(60, 24);
    setMaximumHeight(24);
}

void SparkLine::setSamples(const std::deque<float>& hist, float peak)
{
    samples_.assign(hist.begin(), hist.end());
    peak_ = (peak > 0.0f) ? peak : 1.0f;
    update();
}

void SparkLine::paintEvent(QPaintEvent*)
{
    QPainter p(this);
    p.setRenderHint(QPainter::Antialiasing);

    QRect r = rect().adjusted(1, 1, -1, -1);
    p.fillRect(rect(), QColor(20, 20, 20));

    if (samples_.empty()) return;

    const int n = static_cast<int>(samples_.size());
    const float w = float(r.width()) / n;
    const int   h = r.height();

    for (int i = 0; i < n; ++i) {
        float ratio = std::min(samples_[i] / peak_, 1.0f);
        int barH = static_cast<int>(ratio * h);
        if (barH < 1) continue;
        float x = r.left() + i * w;
        // colour: green→yellow→red based on ratio
        int red   = static_cast<int>(255 * std::min(ratio * 2.0f, 1.0f));
        int green = static_cast<int>(255 * std::min((1.0f - ratio) * 2.0f, 1.0f));
        p.fillRect(QRectF(x, r.bottom() - barH, w - 1.0f, barH),
                   QColor(red, green, 40));
    }
}

// ── helpers ───────────────────────────────────────────────────────────

static QString fmtBytes(uint64_t b)
{
    if (b < 1024)        return QString("%1 B").arg(b);
    if (b < 1024*1024)   return QString("%1 KB").arg(b / 1024.0, 0, 'f', 1);
    return               QString("%1 MB").arg(b / 1048576.0, 0, 'f', 1);
}

static QString fmtRate(double r)
{
    if (r < 1e3) return QString("%1/s").arg(r, 0, 'f', 0);
    if (r < 1e6) return QString("%1 k/s").arg(r / 1e3, 0, 'f', 1);
    return       QString("%1 M/s").arg(r / 1e6, 0, 'f', 2);
}

// ── StatsPanel ────────────────────────────────────────────────────────

static const char* kColHdrs[] = {
    "Source", "Status", "Pkts/s", "Bytes/s",
    "Latency", "Lost %", "Total Pkts", "Signals", "Rate"
};
static constexpr int kNCols = 9;
static constexpr int kRateCol = 8;  // SparkLine column

StatsPanel::StatsPanel(QWidget* parent) : QWidget(parent)
{
    setMaximumHeight(28);  // collapsed: header only

    auto* outer = new QVBoxLayout(this);
    outer->setSpacing(0);
    outer->setContentsMargins(0, 0, 0, 0);

    // ── Header bar (always visible) ───────────────────────────────────
    auto* hdr = new QWidget(this);
    hdr->setFixedHeight(28);
    hdr->setObjectName("statsPanelHeader");
    auto* hdrLay = new QHBoxLayout(hdr);
    hdrLay->setContentsMargins(6, 0, 6, 0);
    hdrLay->setSpacing(8);

    toggleBtn_ = new QPushButton("\u25b2 Stats", hdr);
    toggleBtn_->setFlat(true);
    toggleBtn_->setFixedWidth(80);
    toggleBtn_->setObjectName("statsPanelToggle");
    connect(toggleBtn_, &QPushButton::clicked, this, &StatsPanel::toggle);
    hdrLay->addWidget(toggleBtn_);

    summaryLbl_ = new QLabel("No sources connected", hdr);
    summaryLbl_->setStyleSheet("color:#888; font-size:11px;");
    hdrLay->addWidget(summaryLbl_, 1);
    outer->addWidget(hdr);

    // ── Expandable content ────────────────────────────────────────────
    content_ = new QWidget(this);
    content_->setVisible(false);
    auto* cLay = new QVBoxLayout(content_);
    cLay->setContentsMargins(2, 2, 2, 2);
    cLay->setSpacing(0);

    table_ = new QTableWidget(0, kNCols, content_);
    QStringList hdrs;
    for (const char* h : kColHdrs) hdrs << QString(h);
    table_->setHorizontalHeaderLabels(hdrs);
    table_->horizontalHeader()->setStretchLastSection(false);
    table_->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
    table_->verticalHeader()->setVisible(false);
    table_->setEditTriggers(QAbstractItemView::NoEditTriggers);
    table_->setSelectionMode(QAbstractItemView::NoSelection);
    table_->setAlternatingRowColors(true);
    table_->setFixedHeight(130);
    cLay->addWidget(table_);

    outer->addWidget(content_);
}

void StatsPanel::setExpanded(bool on)
{
    expanded_ = on;
    content_->setVisible(on);
    toggleBtn_->setText(on ? "\u25bc Stats" : "\u25b2 Stats");
    setMaximumHeight(on ? 200 : 28);
    setMinimumHeight(28);
}

void StatsPanel::buildTable(int nSrc)
{
    table_->setRowCount(nSrc);
    // Set SparkLine widget in rate column for each row
    for (int r = 0; r < nSrc; ++r) {
        if (!table_->cellWidget(r, kRateCol)) {
            auto* sl = new SparkLine(table_);
            table_->setCellWidget(r, kRateCol, sl);
            table_->setRowHeight(r, 28);
        }
    }
    lastSrcCount_ = nSrc;
}

static QTableWidgetItem* makeCell(const QString& txt,
                                   Qt::Alignment align = Qt::AlignVCenter | Qt::AlignRight)
{
    auto* it = new QTableWidgetItem(txt);
    it->setTextAlignment(align);
    return it;
}

void StatsPanel::refresh(const std::vector<std::shared_ptr<Source>>& sources)
{
    using clock = std::chrono::system_clock;
    double now = std::chrono::duration<double>(
        clock::now().time_since_epoch()).count();

    int n = static_cast<int>(sources.size());
    if (n != lastSrcCount_) {
        srcStats_.resize(n);
        buildTable(n);
    }

    uint64_t totalPkts    = 0;
    size_t   connected    = 0;
    double   totalPktRate = 0;
    double   maxLatency   = 0;

    for (int i = 0; i < n; ++i) {
        auto& src = sources[i];
        auto& ss  = srcStats_[i];
        bool  conn = src->connected();
        auto  st   = src->stats();
        if (conn) ++connected;

        // Rate computation via exponentially-weighted average
        double dt = now - ss.lastTime;
        if (ss.lastTime > 0 && dt > 0.0) {
            double alpha = std::min(dt / 0.5, 1.0);  // ~0.5s time constant
            double rawPktRate  = double(st.packets_rx - ss.lastPkts) / dt;
            double rawByteRate = rawPktRate * (st.bytes_rx > 0 ?
                double(st.bytes_rx) / double(st.packets_rx > 0 ? st.packets_rx : 1) : 0);
            ss.pktRate  = ss.pktRate  * (1.0 - alpha) + rawPktRate  * alpha;
            ss.byteRate = ss.byteRate * (1.0 - alpha) + rawByteRate * alpha;
        }
        ss.lastPkts = st.packets_rx;
        ss.lastTime = now;

        // Sparkline history (keep 60 samples)
        ss.rateHist.push_back(float(ss.pktRate));
        if (ss.rateHist.size() > 60) ss.rateHist.pop_front();

        // Latency: estimate as time since latest buffer write
        double latMs = 0;
        int    nsig  = 0;
        {
            std::lock_guard<std::mutex> lk(src->signals_mutex);
            nsig = static_cast<int>(src->signal_states.size());
            for (auto& sig : src->signal_states) {
                if (sig.buffer) {
                    double lt = sig.buffer->latest_time();
                    if (lt > 0) {
                        double l = (now - lt) * 1000.0;
                        if (l > latMs) latMs = l;
                    }
                }
            }
        }
        if (latMs > maxLatency) maxLatency = latMs;

        totalPkts    += st.packets_rx;
        totalPktRate += ss.pktRate;

        if (!expanded_) continue;

        // Loss percentage
        uint64_t total   = st.packets_rx + st.packets_lost;
        double   lossPct = total > 0 ? 100.0 * st.packets_lost / total : 0.0;

        QString srcLabel = QString("%1@%2:%3")
            .arg(QString::fromStdString(src->label()))
            .arg(QString::fromStdString(src->host()))
            .arg(src->port());

        table_->setItem(i, 0, makeCell(srcLabel, Qt::AlignVCenter | Qt::AlignLeft));

        auto* stIt = makeCell(conn ? "\u25cf Connected" : "\u25cb Disconnected");
        stIt->setForeground(conn ? QColor(80, 200, 80) : QColor(200, 80, 80));
        table_->setItem(i, 1, stIt);

        table_->setItem(i, 2, makeCell(fmtRate(ss.pktRate)));
        table_->setItem(i, 3, makeCell(fmtBytes(uint64_t(ss.byteRate))));

        QString latStr = (latMs < 1.0)
            ? QString("%1 µs").arg(latMs * 1000.0, 0, 'f', 0)
            : QString("%1 ms").arg(latMs, 0, 'f', 1);
        table_->setItem(i, 4, makeCell(latStr));
        table_->setItem(i, 5, makeCell(QString("%1%").arg(lossPct, 0, 'f', 1)));
        table_->setItem(i, 6, makeCell(QString::number(st.packets_rx)));
        table_->setItem(i, 7, makeCell(QString::number(nsig)));

        // Update sparkline
        float peak = *std::max_element(ss.rateHist.begin(), ss.rateHist.end());
        if (auto* sl = qobject_cast<SparkLine*>(table_->cellWidget(i, kRateCol)))
            sl->setSamples(ss.rateHist, peak);
    }

    // Update summary label (always visible)
    summaryLbl_->setText(
        QString("%1/%2 connected  |  %3 pkts/s  |  max latency: %4 ms")
            .arg(connected).arg(n)
            .arg(totalPktRate, 0, 'f', 0)
            .arg(maxLatency, 0, 'f', 1));
}
