#include "data_backend.h"

#include <QFile>
#include <QIODevice>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QTextStream>
#include <QDir>
#include <cmath>
#include <qtpreprocessorsupport.h>
#include <qurl.h>

DataBackend::DataBackend(QObject* parent)
    : QObject(parent) {
    m_playbackTimer.setTimerType(Qt::PreciseTimer);
    connect(&m_playbackTimer, &QTimer::timeout, this, [this]() {
        if (m_playbackIndex >= m_frames.size()) {
            stopPlayback();
            return;
        }
        const RenderCallback& cb = m_playbackCallback ? m_playbackCallback : m_liveCallback;
        emitFrame_(m_frames[m_playbackIndex], cb);
        m_playbackIndex++;
    });
    // seedDebugFrames_();
}

void DataBackend::ingestFrame(const DataFrame& frame) {
    m_frames.push_back(frame);
    emit frameCountChanged();
    emitFrame_(frame, m_liveCallback);
}

void DataBackend::clear() {
    stopPlayback();
    m_frames.clear();
    m_playbackIndex = 0;
    updateMetrics_(DataFrame());
    emit frameCountChanged();
}

bool DataBackend::exportJson(const QString& path) const {
    QFile file(path);
    if (!file.open(QIODevice::WriteOnly))
        return false;
    file.write(buildJson_());
    return true;
}

bool DataBackend::exportCsv(const QString& path) const {
    QFile file(path);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
        return false;
    file.write(buildCsv_());
    return true;
}

bool DataBackend::exportXlsx(const QString& path) const {
    /* QFile file(path);
    if (!file.open(QIODevice::WriteOnly))
        return false;
    */
    return buildXlsx_(path);
}

bool DataBackend::importJson(const QString& path) {
    QFile file(path);
    if (!file.open(QIODevice::ReadOnly))
        return false;
    return loadJson_(file.readAll());
}

bool DataBackend::importCsv(const QString& path) {
    QFile file(path);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
        return false;
    return loadCsv_(file.readAll());
}

bool DataBackend::importXlsx(const QString& path) {
  QXlsx::Document doc(path);
  if (!doc.isLoadPackage()) {
    qCritical() << "failed to open file: " << path;
    return false;
  }

  stopPlayback();
  clear();
  doc.selectSheet(1);
  QXlsx::CellRange range = doc.dimension();
  int firstRow = range.firstRow();
  int lastRow = range.lastRow();
  int firstCol = range.firstColumn();
  int lastCol = range.lastColumn();

  if (firstRow == 0 && lastRow == 0 && firstCol == 0 && lastCol == 0) {
    return false;
  }
  int row_count = lastRow - firstRow + 1;
  int col_count = lastCol - firstCol + 1;
  // TODO 完善xlsx数据导入
  struct SpanInfo {
    int row = 0;
    int column = 0;
    int rowSpan = 1;
    int colSpan = 1;
  };
  QVector<QVector<QVariant>> cells;
  QVector<SpanInfo> spans;
  for (int r = 0; r < row_count; ++r) {
    cells.resize(col_count);
    for (int c = 0; c < col_count; ++c) {
      int excel_row = firstRow + r;
      int excel_col = firstCol + c;

      std::shared_ptr<QXlsx::Cell> cell_obj = doc.cellAt(excel_row, excel_col);
      QVariant v;
      
      if (cell_obj) {
        v = cell_obj->readValue();
      }
      else {
        v = doc.read(excel_row, excel_col);
      }

      cells[r][c] = v;
    }
  }

    return true;
}

void DataBackend::startPlayback(int intervalMs) {
    if (m_frames.isEmpty())
        return;
    if (intervalMs < 1)
        intervalMs = 1;
    m_playbackIndex = 0;
    m_playbackRunning = true;
    emit playbackRunningChanged();
    m_playbackTimer.start(intervalMs);
}

void DataBackend::stopPlayback() {
    if (!m_playbackRunning)
        return;
    m_playbackRunning = false;
    emit playbackRunningChanged();
    m_playbackTimer.stop();
}

void DataBackend::exportHandler(const QUrl& folder, const QString& filename,
    const QString& format, const QString& method) {
    const QString trimmedName = filename.trimmed();
    if (trimmedName.isEmpty()) {
        qWarning().noquote() << "Export failed: filename is empty.";
        return;
    }

    QString folderPath = folder.isLocalFile() ? folder.toLocalFile() : folder.toString();
    if (folderPath.startsWith(QStringLiteral("file://")))
        folderPath = QUrl(folderPath).toLocalFile();
    if (folderPath.isEmpty()) {
        qWarning().noquote() << "Export failed: folder is empty.";
        return;
    }

    const QDir dir(folderPath);
    if (!dir.exists()) {
        qWarning().noquote() << "Export failed: folder does not exist:" << folderPath;
        return;
    }

    const QString filePath = dir.filePath(trimmedName);
    QString fmt = format.trimmed().toLower();
    if (fmt.isEmpty()) {
        if (trimmedName.endsWith(QStringLiteral(".csv"), Qt::CaseInsensitive))
            fmt = QStringLiteral("csv");
        else if (trimmedName.endsWith(QStringLiteral(".json"), Qt::CaseInsensitive))
            fmt = QStringLiteral("json");
        else if (trimmedName.endsWith(QStringLiteral(".xlsx"), Qt::CaseInsensitive))
            fmt = QStringLiteral("xlsx");
    }

    QString mode = method.trimmed().toLower();
    if (mode.isEmpty())
        mode = QStringLiteral("overwrite");

    if (fmt == QStringLiteral("csv")) {
        if (mode == QStringLiteral("append")) {
            QFile file(filePath);
            if (!file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
                qWarning().noquote() << "Export CSV append failed:" << file.errorString();
                return;
            }
            file.write(buildCsv_());
            return;
        }
        if (!exportCsv(filePath))
            qWarning().noquote() << "Export CSV failed:" << filePath;
        return;
    }

    if (fmt == QStringLiteral("json")) {
        if (mode == QStringLiteral("append")) {
            QJsonArray arr;
            QFile file(filePath);
            if (file.exists() && file.open(QIODevice::ReadOnly)) {
                const QJsonDocument existing = QJsonDocument::fromJson(file.readAll());
                if (existing.isArray())
                    arr = existing.array();
            }

            for (const DataFrame& frame : m_frames) {
                QJsonObject obj;
                obj.insert(QStringLiteral("pts"), frame.pts);
                obj.insert(QStringLiteral("func"), frame.functionCode);
                QJsonArray dataArr;
                for (float v : frame.data)
                    dataArr.append(static_cast<double>(v));
                obj.insert(QStringLiteral("data"), dataArr);
                arr.append(obj);
            }

            QFile out(filePath);
            if (!out.open(QIODevice::WriteOnly)) {
                qWarning().noquote() << "Export JSON append failed:" << out.errorString();
                return;
            }
            const QJsonDocument doc(arr);
            out.write(doc.toJson(QJsonDocument::Indented));
            return;
        }
        if (!exportJson(filePath))
            qWarning().noquote() << "Export JSON failed:" << filePath;
        return;
    }

    if (fmt == QStringLiteral("xlsx")) {
        qWarning().noquote() << "Export XLSX not supported yet:" << filePath;
        return;
    }

    if (mode == QStringLiteral("zip")) {
        qWarning().noquote() << "Export ZIP not supported yet:" << filePath;
        return;
    }

    qWarning().noquote() << "Export failed: unsupported format:" << format;
}

bool DataBackend::loadJson_(const QByteArray& data) {
    const QJsonDocument doc = QJsonDocument::fromJson(data);
    if (doc.isNull() || !doc.isArray())
        return false;

    stopPlayback();
    clear();
    const QJsonArray arr = doc.array();
    for (const QJsonValue& val : arr) {
        if (!val.isObject())
            continue;
        const QJsonObject obj = val.toObject();
        DataFrame frame;
        frame.pts = obj.value(QStringLiteral("pts")).toString();
        frame.functionCode = static_cast<quint8>(obj.value(QStringLiteral("func")).toInt());

        const QJsonArray dataArr = obj.value(QStringLiteral("data")).toArray();
        frame.data.reserve(dataArr.size());
        for (const QJsonValue& d : dataArr)
            frame.data.push_back(static_cast<float>(d.toDouble()));

        m_frames.push_back(frame);
    }

    emit frameCountChanged();
    return true;
}

bool DataBackend::loadCsv_(const QByteArray& data) {
    stopPlayback();
    clear();
    QString text = QString::fromUtf8(data);
    QTextStream stream(&text);
    while (!stream.atEnd()) {
        const QString line = stream.readLine().trimmed();
        if (line.isEmpty())
            continue;

        const QStringList parts = line.split(',', Qt::KeepEmptyParts);
        if (parts.size() < 2)
            continue;
        if (parts[0].trimmed().toLower() == QStringLiteral("pts"))
            continue;

        DataFrame frame;
        frame.pts = parts[0];
        frame.functionCode = 0;
        for (int i = 1; i < parts.size(); ++i)
            frame.data.push_back(parts[i].toFloat());
        m_frames.push_back(frame);
    }

    emit frameCountChanged();
    return true;
}

QByteArray DataBackend::buildJson_() const {
    QJsonArray arr;
    for (const DataFrame& frame : m_frames) {
        QJsonObject obj;
        obj.insert(QStringLiteral("pts"), frame.pts);
        obj.insert(QStringLiteral("func"), frame.functionCode);
        QJsonArray dataArr;
        for (float v : frame.data)
            dataArr.append(static_cast<double>(v));
        obj.insert(QStringLiteral("data"), dataArr);
        arr.append(obj);
    }
    const QJsonDocument doc(arr);
    return doc.toJson(QJsonDocument::Indented);
}

QByteArray DataBackend::buildCsv_() const {
    QByteArray out;
    QTextStream stream(&out, QIODevice::WriteOnly);
    for (const DataFrame& frame : m_frames) {
        stream << frame.pts;
        for (float v : frame.data)
            stream << ',' << v;
        stream << '\n';
    }
    return out;
}

bool DataBackend::buildXlsx_(const QString& path) const {
    QXlsx::Document xlsx;
    int current_sheet_index = 1;
    int current_sheet_row_start = 1;
    
    int col_count = m_frames.at(0).data.size();
    auto ensure_sheet_for_row = [&](int row){
        if (m_frames.size() <= 0) {
            if (xlsx.currentWorksheet() == nullptr)
                xlsx.addSheet("Sheet1");
            return;
        }
        if (xlsx.currentWorksheet() == nullptr) {
            xlsx.addSheet(QStringLiteral("Sheet%1").arg(current_sheet_index));
            current_sheet_row_start = 1;
            return;
        }
        if (row - current_sheet_row_start >= m_frames.size()) {
            ++current_sheet_index;
            xlsx.addSheet(QStringLiteral("Sheet%1").arg(current_sheet_index));
            current_sheet_row_start = row;
        }
    };

    for (size_t row = 1; row <= m_frames.size(); row++) {
        ensure_sheet_for_row(row);
        xlsx.write(1, 1, m_frames.at(row - 1).pts);
        int col_index = 2;
        for (auto data : m_frames.at(row - 1).data) {
            xlsx.write(row, col_index, data);
            col_index++;
        }
    }
    if (!xlsx.saveAs(path)) {
        qCritical() << "failed to save file: " << path;
        return false;
    }

    return true;
}

void DataBackend::seedDebugFrames_() {
    if (!m_frames.isEmpty())
        return;

    constexpr int kFrameCount = 3;
    constexpr int kSampleCount = 12;
    constexpr int kBase = 100;

    const QDateTime start = QDateTime::currentDateTime();
    m_frames.reserve(kFrameCount);
    for (int f = 0; f < kFrameCount; ++f) {
        DataFrame frame;
        frame.pts = DataFrame::makePts(start.addMSecs(f * 100));
        frame.functionCode = 1;
        frame.data.reserve(kSampleCount);
        for (int i = 0; i < kSampleCount; ++i)
            frame.data.push_back(static_cast<float>(kBase + f * 2 + i * 5));
        m_frames.push_back(frame);
    }

    emit frameCountChanged();
}
void DataBackend::emitFrame_(const DataFrame& frame, const RenderCallback& cb) {
    updateMetrics_(frame);
    if (cb)
        cb(frame);
}

void DataBackend::updateMetrics_(const DataFrame& frame) {
    double peak = 0.0;
    double rms = 0.0;
    double avg = 0.0;
    double delta = 0.0;
    double sum = 0.0;

    if (!frame.data.isEmpty()) {
        double sumSq = 0.0;
        float minV = frame.data.front();
        float maxV = frame.data.front();

        for (float v : frame.data) {
            sum += v;
            sumSq += static_cast<double>(v) * static_cast<double>(v);
            if (v < minV)
                minV = v;
            if (v > maxV)
                maxV = v;
        }

        const double count = static_cast<double>(frame.data.size());
        avg = sum / count;
        rms = std::sqrt(sumSq / count);
        peak = maxV;
        delta = maxV - minV;
    }

    m_metricPeak = peak;
    m_metricRms = rms;
    m_metricAvg = avg;
    m_metricDelta = delta;
    m_metricSum = sum;
    emit metricsChanged();
}
