#include <QTest>
#include <QSignalSpy>
#include <QApplication>
#include <QComboBox>
#include <QLineEdit>
#include <QCheckBox>
#include <QPushButton>
#include <QProgressBar>
#include <QTableWidget>
#include <QHeaderView>
#include <QLabel>
#include <QClipboard>
#include <QElapsedTimer>
#include <cstring>
#include "scannerpanel.h"
#include "scanner.h"
#include "providers/buffer_provider.h"
#include "providers/null_provider.h"

using namespace rcx;

class TestScannerUI : public QObject {
    Q_OBJECT

private:
    ScannerPanel* m_panel = nullptr;

private slots:

    void init() {
        m_panel = new ScannerPanel();
        m_panel->show();
        // Clear mode-dependent filter defaults so BufferProvider scans
        // (which have no executable regions) work without filter issues.
        // The initialState_filterCheckboxes test verifies defaults separately.
        m_panel->execCheck()->setChecked(false);
        m_panel->writeCheck()->setChecked(false);
        QApplication::processEvents();
    }

    void cleanup() {
        delete m_panel;
        m_panel = nullptr;
    }

    // ═══════════════════════════════════════════════════════════════════
    // Widget creation and initial state
    // ═══════════════════════════════════════════════════════════════════

    void initialState_modeCombo() {
        QCOMPARE(m_panel->modeCombo()->count(), 2);
        QCOMPARE(m_panel->modeCombo()->currentIndex(), 0); // Signature
        QCOMPARE(m_panel->modeCombo()->itemText(0), QStringLiteral("Signature"));
        QCOMPARE(m_panel->modeCombo()->itemText(1), QStringLiteral("Value"));
    }

    void initialState_signatureMode() {
        // In signature mode: pattern visible, value hidden
        QVERIFY(m_panel->patternEdit()->isVisible());
        QVERIFY(!m_panel->typeCombo()->isVisible());
        QVERIFY(!m_panel->valueEdit()->isVisible());
    }

    void initialState_scanButton() {
        QCOMPARE(m_panel->scanButton()->text(), QStringLiteral("Scan"));
    }

    void initialState_progressBarHidden() {
        QVERIFY(!m_panel->progressBar()->isVisible());
    }

    void initialState_resultsEmpty() {
        QCOMPARE(m_panel->resultsTable()->rowCount(), 0);
    }

    void initialState_buttonsDisabled() {
        QVERIFY(!m_panel->gotoButton()->isEnabled());
        QVERIFY(!m_panel->copyButton()->isEnabled());
    }

    void initialState_statusLabel() {
        QCOMPARE(m_panel->statusLabel()->text(), QStringLiteral("Ready"));
    }

    void initialState_filterCheckboxes() {
        // Verify defaults on a fresh panel (init() clears filters for test convenience)
        ScannerPanel fresh;
        // Signature mode default: executable checked, writable unchecked
        QVERIFY(fresh.execCheck()->isChecked());
        QVERIFY(!fresh.writeCheck()->isChecked());
        // Switching to value mode flips them
        fresh.modeCombo()->setCurrentIndex(1);
        QVERIFY(!fresh.execCheck()->isChecked());
        QVERIFY(fresh.writeCheck()->isChecked());
        // Switching back restores signature defaults
        fresh.modeCombo()->setCurrentIndex(0);
        QVERIFY(fresh.execCheck()->isChecked());
        QVERIFY(!fresh.writeCheck()->isChecked());
    }

    void initialState_patternPlaceholder() {
        QVERIFY(!m_panel->patternEdit()->placeholderText().isEmpty());
    }

    void initialState_typeComboHasAllTypes() {
        QCOMPARE(m_panel->typeCombo()->count(), 10); // int8..double = 10 types
    }

    void initialState_resultsTableColumns() {
        QCOMPARE(m_panel->resultsTable()->columnCount(), 2);
    }

    void initialState_noHeaders() {
        QVERIFY(!m_panel->resultsTable()->horizontalHeader()->isVisible());
        QVERIFY(!m_panel->resultsTable()->verticalHeader()->isVisible());
    }

    void initialState_noGrid() {
        QVERIFY(!m_panel->resultsTable()->showGrid());
    }

    // ═══════════════════════════════════════════════════════════════════
    // Mode switching
    // ═══════════════════════════════════════════════════════════════════

    void switchToValueMode() {
        m_panel->modeCombo()->setCurrentIndex(1); // Value

        QVERIFY(!m_panel->patternEdit()->isVisible());
        QVERIFY(m_panel->typeCombo()->isVisible());
        QVERIFY(m_panel->valueEdit()->isVisible());
    }

    void switchBackToSignatureMode() {
        m_panel->modeCombo()->setCurrentIndex(1); // Value
        m_panel->modeCombo()->setCurrentIndex(0); // Signature

        QVERIFY(m_panel->patternEdit()->isVisible());
        QVERIFY(!m_panel->typeCombo()->isVisible());
        QVERIFY(!m_panel->valueEdit()->isVisible());
    }

    // ═══════════════════════════════════════════════════════════════════
    // No provider — error handling
    // ═══════════════════════════════════════════════════════════════════

    void scan_noProvider() {
        // No provider getter set
        m_panel->patternEdit()->setText("48 8B");
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);

        QVERIFY(m_panel->statusLabel()->text().contains("No source"));
    }

    void scan_nullProvider() {
        m_panel->setProviderGetter([]() -> std::shared_ptr<Provider> {
            return nullptr;
        });
        m_panel->patternEdit()->setText("48 8B");
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);

        QVERIFY(m_panel->statusLabel()->text().contains("No source"));
    }

    // ═══════════════════════════════════════════════════════════════════
    // Invalid pattern — error handling
    // ═══════════════════════════════════════════════════════════════════

    void scan_emptyPattern() {
        auto prov = std::make_shared<BufferProvider>(QByteArray(16, '\0'));
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->patternEdit()->setText("");
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);

        QVERIFY(m_panel->statusLabel()->text().contains("error", Qt::CaseInsensitive));
    }

    void scan_invalidPattern() {
        auto prov = std::make_shared<BufferProvider>(QByteArray(16, '\0'));
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->patternEdit()->setText("GG HH");
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);

        QVERIFY(m_panel->statusLabel()->text().contains("error", Qt::CaseInsensitive));
    }

    // ═══════════════════════════════════════════════════════════════════
    // Successful scan — signature mode
    // ═══════════════════════════════════════════════════════════════════

    void scan_signatureFindsResults() {
        QByteArray data(32, '\0');
        data[8] = 0x48; data[9] = 0x8B;

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->patternEdit()->setText("48 8B");
        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);

        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);

        QVERIFY(finSpy.wait(5000));
        QApplication::processEvents();

        QCOMPARE(m_panel->resultsTable()->rowCount(), 1);
        QVERIFY(m_panel->statusLabel()->text().contains("1 result"));
    }

    void scan_signatureNoResults() {
        QByteArray data(32, '\0');
        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->patternEdit()->setText("FF FF");
        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);

        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy.wait(5000));
        QApplication::processEvents();

        QCOMPARE(m_panel->resultsTable()->rowCount(), 0);
        QVERIFY(m_panel->statusLabel()->text().contains("0 result"));
    }

    // ═══════════════════════════════════════════════════════════════════
    // Scan button toggle (Scan ↔ Cancel)
    // ═══════════════════════════════════════════════════════════════════

    void scan_buttonShowsCancel() {
        // Use a large buffer so scan takes a measurable amount of time
        QByteArray data(4 * 1024 * 1024, '\0'); // 4MB
        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->patternEdit()->setText("FF");
        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);

        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);

        // If scan hasn't finished yet, button should be Cancel
        if (m_panel->engine()->isRunning()) {
            QCOMPARE(m_panel->scanButton()->text(), QStringLiteral("Cancel"));
            QVERIFY(m_panel->progressBar()->isVisible());
        }

        // Wait for finish
        QVERIFY(finSpy.wait(30000));
        QApplication::processEvents();

        // Button back to Scan
        QCOMPARE(m_panel->scanButton()->text(), QStringLiteral("Scan"));
        QVERIFY(!m_panel->progressBar()->isVisible());
    }

    void scan_cancelMidScan() {
        // This test verifies the cancel codepath works.
        // The scan may complete faster than we can click cancel,
        // so we just verify the final state is correct.
        QByteArray data(1024 * 1024, '\0'); // 1MB
        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->patternEdit()->setText("FF");
        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);

        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);

        // Immediately try to cancel (may or may not succeed depending on timing)
        m_panel->engine()->abort();

        // Wait for finished signal
        if (finSpy.isEmpty())
            QVERIFY(finSpy.wait(10000));
        QApplication::processEvents();

        // After scan completes (or is cancelled), verify button returns to "Scan"
        // Need to process any remaining events (the finished handler sets button text)
        QApplication::processEvents();
        // If the panel still shows "Cancel", click it to reset
        if (m_panel->scanButton()->text() == QStringLiteral("Cancel")) {
            QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
            QApplication::processEvents();
        }
        QCOMPARE(m_panel->scanButton()->text(), QStringLiteral("Scan"));
    }

    // ═══════════════════════════════════════════════════════════════════
    // Value mode scan
    // ═══════════════════════════════════════════════════════════════════

    void scan_valueInt32() {
        QByteArray data(64, '\0');
        int32_t target = 42;
        std::memcpy(data.data() + 8, &target, 4);

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->modeCombo()->setCurrentIndex(1); // Value mode
        // Find int32 in combo
        for (int i = 0; i < m_panel->typeCombo()->count(); i++) {
            if (m_panel->typeCombo()->itemData(i).toInt() == (int)ValueType::Int32) {
                m_panel->typeCombo()->setCurrentIndex(i);
                break;
            }
        }
        m_panel->valueEdit()->setText("42");

        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy.wait(5000));
        QApplication::processEvents();

        QCOMPARE(m_panel->resultsTable()->rowCount(), 1);
        // Preview should show native int32 value, not hex
        auto* prevItem = m_panel->resultsTable()->item(0, 1);
        QVERIFY(prevItem);
        QCOMPARE(prevItem->text(), QStringLiteral("42"));
    }

    void scan_valueInvalidInput() {
        auto prov = std::make_shared<BufferProvider>(QByteArray(16, '\0'));
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->modeCombo()->setCurrentIndex(1); // Value
        m_panel->valueEdit()->setText("not_a_number");

        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);

        QVERIFY(m_panel->statusLabel()->text().contains("error", Qt::CaseInsensitive));
    }

    // ═══════════════════════════════════════════════════════════════════
    // Go to Address signal
    // ═══════════════════════════════════════════════════════════════════

    void goToAddress_signal() {
        QByteArray data(32, '\0');
        data[16] = 0xAB; data[17] = 0xCD;

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->patternEdit()->setText("AB CD");
        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy.wait(5000));
        QApplication::processEvents();

        QCOMPARE(m_panel->resultsTable()->rowCount(), 1);

        // Select the row
        m_panel->resultsTable()->selectRow(0);
        QVERIFY(m_panel->gotoButton()->isEnabled());

        QSignalSpy goSpy(m_panel, &ScannerPanel::goToAddress);
        QTest::mouseClick(m_panel->gotoButton(), Qt::LeftButton);
        QCOMPARE(goSpy.size(), 1);
        QCOMPARE(goSpy.first().first().value<uint64_t>(), (uint64_t)16);
    }

    void doubleClick_startsEditing() {
        // Double-click now starts inline editing, not goToAddress
        QByteArray data(32, '\0');
        data[4] = 0xEF;

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->patternEdit()->setText("EF");
        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy.wait(5000));
        QApplication::processEvents();

        QCOMPARE(m_panel->resultsTable()->rowCount(), 1);

        // Double-click should NOT emit goToAddress directly
        QSignalSpy goSpy(m_panel, &ScannerPanel::goToAddress);
        emit m_panel->resultsTable()->cellDoubleClicked(0, 0);
        QCOMPARE(goSpy.size(), 0);

        // Edit triggers should be DoubleClicked
        QVERIFY(m_panel->resultsTable()->editTriggers() & QAbstractItemView::DoubleClicked);
    }

    // ═══════════════════════════════════════════════════════════════════
    // Copy Address
    // ═══════════════════════════════════════════════════════════════════

    void copyAddress() {
        QByteArray data(32, '\0');
        data[20] = 0xAA;

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->patternEdit()->setText("AA");
        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy.wait(5000));
        QApplication::processEvents();

        m_panel->resultsTable()->selectRow(0);
        QVERIFY(m_panel->copyButton()->isEnabled());

        QTest::mouseClick(m_panel->copyButton(), Qt::LeftButton);

        QString clip = QApplication::clipboard()->text();
        QVERIFY(clip.startsWith("0x", Qt::CaseInsensitive));
        // 20 = 0x14, verify it's present somewhere
        QVERIFY(clip.toUpper().endsWith("14"));
    }

    // ═══════════════════════════════════════════════════════════════════
    // Results table formatting
    // ═══════════════════════════════════════════════════════════════════

    void results_addressFormat() {
        QByteArray data(32, '\0');
        data[0] = (char)0xFF;

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->patternEdit()->setText("FF");
        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy.wait(5000));
        QApplication::processEvents();

        // Check address column has WinDbg backtick format
        auto* item = m_panel->resultsTable()->item(0, 0);
        QVERIFY(item);
        QVERIFY(item->text().contains('`')); // backtick separator
        // Address 0 should be: 00000000`00000000
        QCOMPARE(item->text(), QStringLiteral("00000000`00000000"));
    }

    void results_previewColumn() {
        QByteArray data(32, '\0');
        data[0] = 0xDE; data[1] = 0xAD; data[2] = 0xBE; data[3] = 0xEF;

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->patternEdit()->setText("DE AD");
        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy.wait(5000));
        QApplication::processEvents();

        auto* item = m_panel->resultsTable()->item(0, 1);
        QVERIFY(item);
        QVERIFY(item->text().contains("DE"));
        QVERIFY(item->text().contains("AD"));
        QVERIFY(item->text().contains("BE"));
        QVERIFY(item->text().contains("EF"));
    }

    // ═══════════════════════════════════════════════════════════════════
    // Filter checkboxes
    // ═══════════════════════════════════════════════════════════════════

    void filters_toggleExecutable() {
        m_panel->execCheck()->setChecked(true);
        QVERIFY(m_panel->execCheck()->isChecked());
        m_panel->execCheck()->setChecked(false);
        QVERIFY(!m_panel->execCheck()->isChecked());
    }

    void filters_toggleWritable() {
        m_panel->writeCheck()->setChecked(true);
        QVERIFY(m_panel->writeCheck()->isChecked());
    }

    // ═══════════════════════════════════════════════════════════════════
    // Theme application
    // ═══════════════════════════════════════════════════════════════════

    void theme_apply() {
        Theme t;
        t.background    = QColor("#1e1e1e");
        t.backgroundAlt = QColor("#252526");
        t.text          = QColor("#d4d4d4");
        t.textDim       = QColor("#858585");
        t.textMuted     = QColor("#555555");
        t.textFaint     = QColor("#333333");
        t.border        = QColor("#333333");
        t.borderFocused = QColor("#007acc");
        t.hover         = QColor("#2a2d2e");
        t.selection     = QColor("#264f78");
        t.button        = QColor("#333333");
        t.indHoverSpan  = QColor("#007acc");

        // Should not crash
        m_panel->applyTheme(t);

        // Verify stylesheet was applied (background color in stylesheet)
        QVERIFY(m_panel->resultsTable()->styleSheet().contains(t.background.name()));
        QVERIFY(m_panel->resultsTable()->styleSheet().contains(t.hover.name()));
    }

    // ═══════════════════════════════════════════════════════════════════
    // Multiple scans — results get replaced
    // ═══════════════════════════════════════════════════════════════════

    void scan_resultsReplaced() {
        QByteArray data(32, '\0');
        data[0] = 0xAA;

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        // First scan
        m_panel->patternEdit()->setText("AA");
        QSignalSpy finSpy1(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy1.wait(5000));
        QApplication::processEvents();
        QCOMPARE(m_panel->resultsTable()->rowCount(), 1);

        // Second scan with different pattern (no results)
        m_panel->patternEdit()->setText("FF FF FF");
        QSignalSpy finSpy2(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy2.wait(5000));
        QApplication::processEvents();
        QCOMPARE(m_panel->resultsTable()->rowCount(), 0);
    }

    // ═══════════════════════════════════════════════════════════════════
    // Selection enables/disables action buttons
    // ═══════════════════════════════════════════════════════════════════

    void buttons_enableOnSelection() {
        QByteArray data(32, '\0');
        data[0] = 0xBB; data[16] = 0xBB;

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->patternEdit()->setText("BB");
        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy.wait(5000));
        QApplication::processEvents();

        QCOMPARE(m_panel->resultsTable()->rowCount(), 2);

        // No selection yet
        QVERIFY(!m_panel->gotoButton()->isEnabled());
        QVERIFY(!m_panel->copyButton()->isEnabled());

        // Select row
        m_panel->resultsTable()->selectRow(0);
        QVERIFY(m_panel->gotoButton()->isEnabled());
        QVERIFY(m_panel->copyButton()->isEnabled());

        // Clear selection
        m_panel->resultsTable()->clearSelection();
        QVERIFY(!m_panel->gotoButton()->isEnabled());
        QVERIFY(!m_panel->copyButton()->isEnabled());
    }

    // ═══════════════════════════════════════════════════════════════════
    // Wildcard signature scan
    // ═══════════════════════════════════════════════════════════════════

    void scan_wildcardSignature() {
        QByteArray data(32, '\0');
        data[0]  = 0x48; data[1]  = 0x99; data[2]  = 0x05;
        data[16] = 0x48; data[17] = 0xAA; data[18] = 0x05;

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->patternEdit()->setText("48 ?? 05");
        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy.wait(5000));
        QApplication::processEvents();

        QCOMPARE(m_panel->resultsTable()->rowCount(), 2);
    }

    // ═══════════════════════════════════════════════════════════════════
    // Multiple results — status label pluralization
    // ═══════════════════════════════════════════════════════════════════

    void status_singleResult() {
        QByteArray data(16, '\0');
        data[0] = 0xCC;

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->patternEdit()->setText("CC");
        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy.wait(5000));
        QApplication::processEvents();

        QCOMPARE(m_panel->statusLabel()->text(), QStringLiteral("1 result"));
    }

    void status_multipleResults() {
        QByteArray data(16, '\0');
        data[0] = 0xDD; data[8] = 0xDD;

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->patternEdit()->setText("DD");
        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy.wait(5000));
        QApplication::processEvents();

        QCOMPARE(m_panel->statusLabel()->text(), QStringLiteral("2 results"));
    }

    // ═══════════════════════════════════════════════════════════════════
    // Provider getter is lazy (captures at scan time)
    // ═══════════════════════════════════════════════════════════════════

    // ═══════════════════════════════════════════════════════════════════
    // Inline editing
    // ═══════════════════════════════════════════════════════════════════

    void edit_addressExpression() {
        QByteArray data(64, '\0');
        data[0] = 0xAA;
        data[32] = 0x55;

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->patternEdit()->setText("AA");
        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy.wait(5000));
        QApplication::processEvents();
        QCOMPARE(m_panel->resultsTable()->rowCount(), 1);

        // Edit address cell with hex expression
        QSignalSpy goSpy(m_panel, &ScannerPanel::goToAddress);
        m_panel->resultsTable()->item(0, 0)->setText("0x20");
        QApplication::processEvents();

        // Should emit goToAddress with the new address
        QCOMPARE(goSpy.size(), 1);
        QCOMPARE(goSpy.first().first().value<uint64_t>(), (uint64_t)0x20);
    }

    void edit_previewHex() {
        QByteArray data(32, '\0');
        data[0] = 0xCC;

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->patternEdit()->setText("CC");
        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy.wait(5000));
        QApplication::processEvents();
        QCOMPARE(m_panel->resultsTable()->rowCount(), 1);

        // Edit preview cell with new hex bytes
        m_panel->resultsTable()->item(0, 1)->setText("DE AD BE EF");
        QApplication::processEvents();

        // Verify bytes were written
        QByteArray written = prov->readBytes(0, 4);
        QCOMPARE((uint8_t)written[0], (uint8_t)0xDE);
        QCOMPARE((uint8_t)written[1], (uint8_t)0xAD);
        QCOMPARE((uint8_t)written[2], (uint8_t)0xBE);
        QCOMPARE((uint8_t)written[3], (uint8_t)0xEF);
    }

    void edit_previewValueMode() {
        QByteArray data(64, '\0');
        int32_t target = 100;
        std::memcpy(data.data() + 8, &target, 4);

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->modeCombo()->setCurrentIndex(1); // Value mode
        for (int i = 0; i < m_panel->typeCombo()->count(); i++) {
            if (m_panel->typeCombo()->itemData(i).toInt() == (int)ValueType::Int32) {
                m_panel->typeCombo()->setCurrentIndex(i);
                break;
            }
        }
        m_panel->valueEdit()->setText("100");
        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy.wait(5000));
        QApplication::processEvents();
        QCOMPARE(m_panel->resultsTable()->rowCount(), 1);

        // Preview shows "100", edit to "999"
        QCOMPARE(m_panel->resultsTable()->item(0, 1)->text(), QStringLiteral("100"));
        m_panel->resultsTable()->item(0, 1)->setText("999");
        QApplication::processEvents();

        // Verify int32 999 was written at offset 8
        int32_t written;
        QByteArray raw = prov->readBytes(8, 4);
        std::memcpy(&written, raw.constData(), 4);
        QCOMPARE(written, 999);
    }

    void edit_invalidAddress() {
        QByteArray data(32, '\0');
        data[0] = 0xDD;

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->patternEdit()->setText("DD");
        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy.wait(5000));
        QApplication::processEvents();

        // Edit with invalid expression — should show error and restore original
        m_panel->resultsTable()->item(0, 0)->setText("invalid!!!");
        QApplication::processEvents();

        QVERIFY(m_panel->statusLabel()->text().contains("error", Qt::CaseInsensitive));
        // Address should be restored to original (00000000`00000000)
        QVERIFY(m_panel->resultsTable()->item(0, 0)->text().contains('`'));
    }

    // ═══════════════════════════════════════════════════════════════════
    // Update button (rescan)
    // ═══════════════════════════════════════════════════════════════════

    void update_disabledInitially() {
        QVERIFY(!m_panel->updateButton()->isEnabled());
    }

    void update_enabledAfterScan() {
        QByteArray data(32, '\0');
        data[0] = 0xAA;

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->patternEdit()->setText("AA");
        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy.wait(5000));
        QApplication::processEvents();

        QVERIFY(m_panel->updateButton()->isEnabled());
    }

    void update_showsPreviousColumn() {
        QByteArray data(64, '\0');
        int32_t val = 50;
        std::memcpy(data.data() + 8, &val, 4);

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->modeCombo()->setCurrentIndex(1); // Value
        for (int i = 0; i < m_panel->typeCombo()->count(); i++) {
            if (m_panel->typeCombo()->itemData(i).toInt() == (int)ValueType::Int32) {
                m_panel->typeCombo()->setCurrentIndex(i);
                break;
            }
        }
        m_panel->valueEdit()->setText("50");
        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy.wait(5000));
        QApplication::processEvents();
        QCOMPARE(m_panel->resultsTable()->rowCount(), 1);
        QCOMPARE(m_panel->resultsTable()->columnCount(), 2); // no previous yet

        // Modify via provider — change value from 50 to 99
        int32_t newVal = 99;
        QByteArray newBytes(4, '\0');
        std::memcpy(newBytes.data(), &newVal, 4);
        prov->writeBytes(8, newBytes);

        // Click update — runs async
        QSignalSpy rescanSpy(m_panel->engine(), &ScanEngine::rescanFinished);
        QTest::mouseClick(m_panel->updateButton(), Qt::LeftButton);
        QVERIFY(rescanSpy.wait(5000));
        QApplication::processEvents();
        QCOMPARE(m_panel->resultsTable()->columnCount(), 3);
        // Current value = 99, previous = 50
        QCOMPARE(m_panel->resultsTable()->item(0, 1)->text(), QStringLiteral("99"));
        QCOMPARE(m_panel->resultsTable()->item(0, 2)->text(), QStringLiteral("50"));
    }

    // ═══════════════════════════════════════════════════════════════════
    // Re-scan: progress completes, values update, table populates
    // ═══════════════════════════════════════════════════════════════════

    void update_progressCompletes() {
        // Use a buffer with many results to verify progress reaches 100%
        // and doesn't hang. Place int32 value "7" every 4 bytes.
        QByteArray data(4096, '\0');
        int32_t val = 7;
        for (int i = 0; i < 1024; i++)
            std::memcpy(data.data() + i * 4, &val, 4);

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->modeCombo()->setCurrentIndex(1); // Value
        for (int i = 0; i < m_panel->typeCombo()->count(); i++) {
            if (m_panel->typeCombo()->itemData(i).toInt() == (int)ValueType::Int32) {
                m_panel->typeCombo()->setCurrentIndex(i);
                break;
            }
        }
        m_panel->valueEdit()->setText("7");
        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy.wait(5000));
        QApplication::processEvents();
        QVERIFY(m_panel->resultsTable()->rowCount() >= 512);
        QCOMPARE(m_panel->resultsTable()->columnCount(), 2);

        // Modify all values: 7 → 21
        int32_t newVal = 21;
        for (int i = 0; i < 1024; i++) {
            QByteArray nb(4, '\0');
            std::memcpy(nb.data(), &newVal, 4);
            prov->writeBytes(i * 4, nb);
        }

        // Click Re-scan — runs async
        QSignalSpy rescanSpy(m_panel->engine(), &ScanEngine::rescanFinished);
        QTest::mouseClick(m_panel->updateButton(), Qt::LeftButton);
        QVERIFY(rescanSpy.wait(5000));
        QApplication::processEvents();

        // Progress bar should be hidden (completed)
        QVERIFY(!m_panel->progressBar()->isVisible());
        // Table should have 3 columns now
        QCOMPARE(m_panel->resultsTable()->columnCount(), 3);
        // Spot check first and last row
        QCOMPARE(m_panel->resultsTable()->item(0, 1)->text(), QStringLiteral("21"));
        QCOMPARE(m_panel->resultsTable()->item(0, 2)->text(), QStringLiteral("7"));
        int lastRow = m_panel->resultsTable()->rowCount() - 1;
        QVERIFY(m_panel->resultsTable()->item(lastRow, 0) != nullptr);
        QCOMPARE(m_panel->resultsTable()->item(lastRow, 1)->text(), QStringLiteral("21"));
        QCOMPARE(m_panel->resultsTable()->item(lastRow, 2)->text(), QStringLiteral("7"));
        // Status should say "Updated"
        QVERIFY(m_panel->statusLabel()->text().contains("Updated"));
        // Buttons should be re-enabled
        QVERIFY(m_panel->updateButton()->isEnabled());
        QVERIFY(m_panel->scanButton()->isEnabled());
    }

    void update_signatureMode() {
        // Re-scan in signature mode (reads actual bytes, not cached pattern)
        QByteArray data(64, '\0');
        data[0] = 0x48; data[1] = 0x8B; data[2] = 0xAA;
        data[32] = 0x48; data[33] = 0x8B; data[34] = 0xBB;

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->modeCombo()->setCurrentIndex(0); // Signature
        m_panel->execCheck()->setChecked(false);  // BufferProvider has no exec regions
        m_panel->patternEdit()->setText("48 8B ??");
        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy.wait(5000));
        QApplication::processEvents();
        QCOMPARE(m_panel->resultsTable()->rowCount(), 2);

        // Modify bytes at first match
        QByteArray mod(3, '\0');
        mod[0] = 0x48; mod[1] = 0x8B; mod[2] = (char)0xFF;
        prov->writeBytes(0, mod);

        // Re-scan — runs async
        QSignalSpy rescanSpy(m_panel->engine(), &ScanEngine::rescanFinished);
        QTest::mouseClick(m_panel->updateButton(), Qt::LeftButton);
        QVERIFY(rescanSpy.wait(5000));
        QApplication::processEvents();

        QVERIFY(!m_panel->progressBar()->isVisible());
        QCOMPARE(m_panel->resultsTable()->columnCount(), 3);
        // First result current value should contain FF
        QVERIFY(m_panel->resultsTable()->item(0, 1)->text().contains("FF"));
        // First result previous value should contain AA
        QVERIFY(m_panel->resultsTable()->item(0, 2)->text().contains("AA"));
    }

    void update_doubleRescan() {
        // Two consecutive re-scans: previous column updates each time
        QByteArray data(32, '\0');
        int32_t v1 = 10;
        std::memcpy(data.data() + 4, &v1, 4);

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->modeCombo()->setCurrentIndex(1);
        for (int i = 0; i < m_panel->typeCombo()->count(); i++) {
            if (m_panel->typeCombo()->itemData(i).toInt() == (int)ValueType::Int32) {
                m_panel->typeCombo()->setCurrentIndex(i);
                break;
            }
        }
        m_panel->valueEdit()->setText("10");
        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy.wait(5000));
        QApplication::processEvents();
        QCOMPARE(m_panel->resultsTable()->rowCount(), 1);
        QCOMPARE(m_panel->resultsTable()->item(0, 1)->text(), QStringLiteral("10"));

        // First update: 10 → 20
        int32_t v2 = 20;
        QByteArray nb2(4, '\0');
        std::memcpy(nb2.data(), &v2, 4);
        prov->writeBytes(4, nb2);
        {
            QSignalSpy rescanSpy(m_panel->engine(), &ScanEngine::rescanFinished);
            QTest::mouseClick(m_panel->updateButton(), Qt::LeftButton);
            QVERIFY(rescanSpy.wait(5000));
            QApplication::processEvents();
        }
        QCOMPARE(m_panel->resultsTable()->item(0, 1)->text(), QStringLiteral("20"));
        QCOMPARE(m_panel->resultsTable()->item(0, 2)->text(), QStringLiteral("10"));

        // Second update: 20 → 30
        int32_t v3 = 30;
        QByteArray nb3(4, '\0');
        std::memcpy(nb3.data(), &v3, 4);
        prov->writeBytes(4, nb3);
        {
            QSignalSpy rescanSpy(m_panel->engine(), &ScanEngine::rescanFinished);
            QTest::mouseClick(m_panel->updateButton(), Qt::LeftButton);
            QVERIFY(rescanSpy.wait(5000));
            QApplication::processEvents();
        }
        QCOMPARE(m_panel->resultsTable()->item(0, 1)->text(), QStringLiteral("30"));
        QCOMPARE(m_panel->resultsTable()->item(0, 2)->text(), QStringLiteral("20"));
    }

    // ═══════════════════════════════════════════════════════════════════
    // Provider getter is lazy (captures at scan time)
    // ═══════════════════════════════════════════════════════════════════

    // ═══════════════════════════════════════════════════════════════════
    // Benchmark: initial scan + 10 re-scans on a large buffer
    // ═══════════════════════════════════════════════════════════════════

    void bench_rescan10x() {
        // 1MB buffer, int32 value=42 placed every 512 bytes → 2048 results
        constexpr int kBufSize = 1 * 1024 * 1024;
        constexpr int kStride = 512;
        constexpr int32_t kVal = 42;

        QByteArray data(kBufSize, '\0');
        int planted = 0;
        for (int off = 0; off + 4 <= kBufSize; off += kStride) {
            std::memcpy(data.data() + off, &kVal, 4);
            planted++;
        }
        qDebug() << "[bench] buffer:" << (kBufSize / 1024) << "KB,"
                 << planted << "planted values, stride:" << kStride;

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->modeCombo()->setCurrentIndex(1);
        for (int i = 0; i < m_panel->typeCombo()->count(); i++) {
            if (m_panel->typeCombo()->itemData(i).toInt() == (int)ValueType::Int32) {
                m_panel->typeCombo()->setCurrentIndex(i);
                break;
            }
        }
        m_panel->valueEdit()->setText(QString::number(kVal));

        // ── Initial scan ──
        QElapsedTimer totalTimer;
        totalTimer.start();

        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy.wait(30000));
        QApplication::processEvents();

        int resultCount = m_panel->resultsTable()->rowCount();
        qDebug() << "[bench] initial scan:" << totalTimer.elapsed() << "ms,"
                 << resultCount << "results displayed";
        QVERIFY(resultCount > 0);
        QVERIFY(m_panel->updateButton()->isEnabled());

        // ── 10 re-scans — mutate values each time ──
        for (int iter = 1; iter <= 10; iter++) {
            int32_t newVal = kVal + iter;
            for (int off = 0; off + 4 <= kBufSize; off += kStride)
                std::memcpy(prov->data().data() + off, &newVal, 4);

            QElapsedTimer iterTimer;
            iterTimer.start();

            QSignalSpy rescanSpy(m_panel->engine(), &ScanEngine::rescanFinished);
            QTest::mouseClick(m_panel->updateButton(), Qt::LeftButton);
            QVERIFY2(rescanSpy.wait(30000),
                     qPrintable(QString("rescan #%1 timed out").arg(iter)));
            QApplication::processEvents();

            qDebug() << "[bench] rescan #" << iter << ":" << iterTimer.elapsed() << "ms"
                     << "| val:" << newVal
                     << "| rows:" << m_panel->resultsTable()->rowCount()
                     << "| status:" << m_panel->statusLabel()->text();

            QVERIFY(m_panel->resultsTable()->item(0, 1) != nullptr);
            QCOMPARE(m_panel->resultsTable()->item(0, 1)->text(),
                     QString::number(newVal));
            if (m_panel->resultsTable()->columnCount() >= 3) {
                QCOMPARE(m_panel->resultsTable()->item(0, 2)->text(),
                         QString::number(newVal - 1));
            }
            QCOMPARE(m_panel->scanButton()->text(), QStringLiteral("Scan"));
            QVERIFY(!m_panel->progressBar()->isVisible());
            QVERIFY(m_panel->updateButton()->isEnabled());
        }

        qDebug() << "[bench] total (scan + 10 rescans):" << totalTimer.elapsed() << "ms";
    }

    // ═══════════════════════════════════════════════════════════════════
    // Benchmark: signature re-scan (16 bytes per result)
    // ═══════════════════════════════════════════════════════════════════

    void bench_rescanSignature10x() {
        // 1MB buffer with "MZ" planted every 4096 bytes → 256 results
        constexpr int kBufSize = 1 * 1024 * 1024;
        constexpr int kStride = 4096;

        QByteArray data(kBufSize, '\0');
        int planted = 0;
        for (int off = 0; off + 2 <= kBufSize; off += kStride) {
            data[off] = 0x4D; data[off + 1] = 0x5A;
            planted++;
        }
        qDebug() << "[bench-sig] buffer:" << (kBufSize / 1024) << "KB,"
                 << planted << "planted sigs";

        auto prov = std::make_shared<BufferProvider>(data);
        m_panel->setProviderGetter([prov]() { return prov; });

        m_panel->modeCombo()->setCurrentIndex(0);
        m_panel->patternEdit()->setText("4D 5A");

        QElapsedTimer totalTimer;
        totalTimer.start();

        QSignalSpy finSpy(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy.wait(30000));
        QApplication::processEvents();

        qDebug() << "[bench-sig] initial scan:" << totalTimer.elapsed() << "ms,"
                 << m_panel->resultsTable()->rowCount() << "results";
        QVERIFY(m_panel->resultsTable()->rowCount() > 0);

        for (int iter = 1; iter <= 10; iter++) {
            for (int off = 0; off + 3 <= kBufSize; off += kStride)
                prov->data().data()[off + 2] = (char)(iter & 0xFF);

            QElapsedTimer iterTimer;
            iterTimer.start();

            QSignalSpy rescanSpy(m_panel->engine(), &ScanEngine::rescanFinished);
            QTest::mouseClick(m_panel->updateButton(), Qt::LeftButton);
            QVERIFY2(rescanSpy.wait(30000),
                     qPrintable(QString("sig rescan #%1 timed out").arg(iter)));
            QApplication::processEvents();

            qDebug() << "[bench-sig] rescan #" << iter << ":" << iterTimer.elapsed() << "ms"
                     << "| status:" << m_panel->statusLabel()->text();

            QCOMPARE(m_panel->scanButton()->text(), QStringLiteral("Scan"));
            QVERIFY(!m_panel->progressBar()->isVisible());
            QVERIFY(m_panel->updateButton()->isEnabled());
        }

        qDebug() << "[bench-sig] total:" << totalTimer.elapsed() << "ms";
    }

    // ═══════════════════════════════════════════════════════════════════
    // Provider getter is lazy (captures at scan time)
    // ═══════════════════════════════════════════════════════════════════

    void providerGetter_lazy() {
        auto prov1 = std::make_shared<BufferProvider>(QByteArray(16, '\xAA'));
        auto prov2 = std::make_shared<BufferProvider>(QByteArray(16, '\xBB'));

        auto current = prov1;
        m_panel->setProviderGetter([&current]() { return current; });

        // Scan with prov1 — should find AA
        m_panel->patternEdit()->setText("AA");
        QSignalSpy finSpy1(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy1.wait(5000));
        QApplication::processEvents();
        QVERIFY(m_panel->resultsTable()->rowCount() > 0);

        // Switch provider
        current = prov2;

        // Scan for BB — should find in new provider
        m_panel->patternEdit()->setText("BB");
        QSignalSpy finSpy2(m_panel->engine(), &ScanEngine::finished);
        QTest::mouseClick(m_panel->scanButton(), Qt::LeftButton);
        QVERIFY(finSpy2.wait(5000));
        QApplication::processEvents();
        QVERIFY(m_panel->resultsTable()->rowCount() > 0);
    }
};

QTEST_MAIN(TestScannerUI)
#include "test_scanner_ui.moc"
