/**
 * LiveComponents Performance Benchmarks
 *
 * Measures and compares performance characteristics of:
 * - Fragment-based rendering vs Full HTML rendering
 * - Single vs Multiple fragment updates
 * - Small vs Large component updates
 * - Network payload sizes
 * - DOM update times
 *
 * Run with: npx playwright test performance-benchmarks.spec.js
 *
 * Generate report: node tests/e2e/livecomponents/generate-performance-report.js
 */

import { test, expect } from '@playwright/test';

// Performance thresholds
const THRESHOLDS = {
    fragmentRender: {
        small: 50,      // ms for single small fragment
        medium: 100,    // ms for 5-10 fragments
        large: 200      // ms for complex component
    },
    fullRender: {
        small: 150,     // ms for full render (small component)
        medium: 300,    // ms for full render (medium component)
        large: 500      // ms for full render (large component)
    },
    networkPayload: {
        fragmentMax: 5000,      // bytes for fragment response
        fullMax: 50000          // bytes for full HTML response
    }
};

// Store benchmark results for report generation
const benchmarkResults = [];

/**
 * Helper: Measure action execution time
 */
async function measureActionTime(page, componentId, action, params = {}, options = {}) {
    const startTime = await page.evaluate(() => performance.now());

    await page.evaluate(({ id, actionName, actionParams, opts }) => {
        const component = window.LiveComponents.get(id);
        return component.call(actionName, actionParams, opts);
    }, {
        id: componentId,
        actionName: action,
        actionParams: params,
        opts: options
    });

    // Wait for update to complete
    await page.waitForTimeout(50);

    const endTime = await page.evaluate(() => performance.now());
    return endTime - startTime;
}

/**
 * Helper: Measure network payload size
 */
async function measurePayloadSize(page, action) {
    let payloadSize = 0;

    const responsePromise = page.waitForResponse(
        response => response.url().includes('/live-component/'),
        { timeout: 5000 }
    );

    await action();

    const response = await responsePromise;
    const body = await response.text();
    payloadSize = new TextEncoder().encode(body).length;

    return payloadSize;
}

/**
 * Helper: Store benchmark result
 */
function storeBenchmarkResult(scenario, metric, value, threshold, unit = 'ms') {
    const passed = value <= threshold;
    benchmarkResults.push({
        scenario,
        metric,
        value,
        threshold,
        unit,
        passed,
        timestamp: new Date().toISOString()
    });
}

test.describe('Performance Benchmarks: Fragment vs Full Render', () => {
    test.beforeEach(async ({ page }) => {
        await page.goto('https://localhost/livecomponents/test/performance');
        await page.waitForFunction(() => window.LiveComponents !== undefined);
    });

    test('Benchmark: Single small fragment update', async ({ page }) => {
        // Measure fragment update
        const fragmentTime = await measureActionTime(
            page,
            'counter:benchmark',
            'increment',
            {},
            { fragments: ['#counter-value'] }
        );

        // Measure full render
        const fullTime = await measureActionTime(
            page,
            'counter:benchmark',
            'increment',
            {}
        );

        // Store results
        storeBenchmarkResult('Single Small Fragment', 'Fragment Update Time', fragmentTime, THRESHOLDS.fragmentRender.small);
        storeBenchmarkResult('Single Small Fragment', 'Full Render Time', fullTime, THRESHOLDS.fullRender.small);

        // Assertions
        expect(fragmentTime).toBeLessThan(THRESHOLDS.fragmentRender.small);
        expect(fragmentTime).toBeLessThan(fullTime); // Fragment should be faster

        const speedup = ((fullTime - fragmentTime) / fullTime * 100).toFixed(1);
        console.log(`Fragment speedup: ${speedup}% faster than full render`);
    });

    test('Benchmark: Multiple fragment updates (5 fragments)', async ({ page }) => {
        const fragments = [
            '#item-1',
            '#item-2',
            '#item-3',
            '#item-4',
            '#item-5'
        ];

        // Measure fragment update
        const fragmentTime = await measureActionTime(
            page,
            'list:benchmark',
            'updateItems',
            { count: 5 },
            { fragments }
        );

        // Measure full render
        const fullTime = await measureActionTime(
            page,
            'list:benchmark',
            'updateItems',
            { count: 5 }
        );

        // Store results
        storeBenchmarkResult('Multiple Fragments (5)', 'Fragment Update Time', fragmentTime, THRESHOLDS.fragmentRender.medium);
        storeBenchmarkResult('Multiple Fragments (5)', 'Full Render Time', fullTime, THRESHOLDS.fullRender.medium);

        // Assertions
        expect(fragmentTime).toBeLessThan(THRESHOLDS.fragmentRender.medium);
        expect(fragmentTime).toBeLessThan(fullTime);

        const speedup = ((fullTime - fragmentTime) / fullTime * 100).toFixed(1);
        console.log(`5-fragment speedup: ${speedup}% faster than full render`);
    });

    test('Benchmark: Large component update (100 items)', async ({ page }) => {
        const fragments = ['#item-list'];

        // Measure fragment update
        const fragmentTime = await measureActionTime(
            page,
            'product-list:benchmark',
            'loadItems',
            { count: 100 },
            { fragments }
        );

        // Measure full render
        const fullTime = await measureActionTime(
            page,
            'product-list:benchmark',
            'loadItems',
            { count: 100 }
        );

        // Store results
        storeBenchmarkResult('Large Component (100 items)', 'Fragment Update Time', fragmentTime, THRESHOLDS.fragmentRender.large);
        storeBenchmarkResult('Large Component (100 items)', 'Full Render Time', fullTime, THRESHOLDS.fullRender.large);

        // Assertions
        expect(fragmentTime).toBeLessThan(THRESHOLDS.fragmentRender.large);
        expect(fragmentTime).toBeLessThan(fullTime);

        const speedup = ((fullTime - fragmentTime) / fullTime * 100).toFixed(1);
        console.log(`100-item speedup: ${speedup}% faster than full render`);
    });

    test('Benchmark: Network payload size comparison', async ({ page }) => {
        // Measure fragment payload
        const fragmentPayload = await measurePayloadSize(page, async () => {
            await page.evaluate(() => {
                window.LiveComponents.get('counter:benchmark').call('increment', {}, {
                    fragments: ['#counter-value']
                });
            });
        });

        // Reset state
        await page.evaluate(() => {
            window.LiveComponents.get('counter:benchmark').call('reset');
        });
        await page.waitForTimeout(100);

        // Measure full HTML payload
        const fullPayload = await measurePayloadSize(page, async () => {
            await page.evaluate(() => {
                window.LiveComponents.get('counter:benchmark').call('increment');
            });
        });

        // Store results
        storeBenchmarkResult('Network Payload', 'Fragment Payload Size', fragmentPayload, THRESHOLDS.networkPayload.fragmentMax, 'bytes');
        storeBenchmarkResult('Network Payload', 'Full HTML Payload Size', fullPayload, THRESHOLDS.networkPayload.fullMax, 'bytes');

        // Assertions
        expect(fragmentPayload).toBeLessThan(THRESHOLDS.networkPayload.fragmentMax);
        expect(fullPayload).toBeLessThan(THRESHOLDS.networkPayload.fullMax);
        expect(fragmentPayload).toBeLessThan(fullPayload);

        const reduction = ((fullPayload - fragmentPayload) / fullPayload * 100).toFixed(1);
        console.log(`Fragment payload reduction: ${reduction}% smaller than full HTML`);
        console.log(`Fragment: ${fragmentPayload} bytes, Full: ${fullPayload} bytes`);
    });

    test('Benchmark: Rapid successive updates (10 updates)', async ({ page }) => {
        const updateCount = 10;

        // Measure fragment updates
        const fragmentStartTime = await page.evaluate(() => performance.now());

        for (let i = 0; i < updateCount; i++) {
            await page.evaluate(() => {
                window.LiveComponents.get('counter:benchmark').call('increment', {}, {
                    fragments: ['#counter-value']
                });
            });
            await page.waitForTimeout(10); // Small delay between updates
        }

        const fragmentEndTime = await page.evaluate(() => performance.now());
        const fragmentTotalTime = fragmentEndTime - fragmentStartTime;

        // Reset
        await page.evaluate(() => {
            window.LiveComponents.get('counter:benchmark').call('reset');
        });
        await page.waitForTimeout(100);

        // Measure full renders
        const fullStartTime = await page.evaluate(() => performance.now());

        for (let i = 0; i < updateCount; i++) {
            await page.evaluate(() => {
                window.LiveComponents.get('counter:benchmark').call('increment');
            });
            await page.waitForTimeout(10);
        }

        const fullEndTime = await page.evaluate(() => performance.now());
        const fullTotalTime = fullEndTime - fullStartTime;

        // Store results
        storeBenchmarkResult('Rapid Updates (10x)', 'Fragment Total Time', fragmentTotalTime, 500);
        storeBenchmarkResult('Rapid Updates (10x)', 'Full Render Total Time', fullTotalTime, 1500);

        // Assertions
        expect(fragmentTotalTime).toBeLessThan(fullTotalTime);

        const avgFragment = fragmentTotalTime / updateCount;
        const avgFull = fullTotalTime / updateCount;
        console.log(`Average fragment update: ${avgFragment.toFixed(2)}ms`);
        console.log(`Average full render: ${avgFull.toFixed(2)}ms`);
        console.log(`Fragment ${((fullTotalTime / fragmentTotalTime).toFixed(1))}x faster for rapid updates`);
    });

    test('Benchmark: DOM manipulation overhead', async ({ page }) => {
        // Measure pure DOM update time (client-side only)
        const domUpdateTime = await page.evaluate(() => {
            const container = document.querySelector('[data-component-id="counter:benchmark"]');
            const fragment = container.querySelector('[data-lc-fragment="counter-value"]');

            const startTime = performance.now();

            // Simulate fragment update
            fragment.textContent = 'Updated Value';

            const endTime = performance.now();
            return endTime - startTime;
        });

        // Measure network + server time for fragment update
        const startTime = await page.evaluate(() => performance.now());

        await page.evaluate(() => {
            window.LiveComponents.get('counter:benchmark').call('increment', {}, {
                fragments: ['#counter-value']
            });
        });

        await page.waitForTimeout(50);
        const endTime = await page.evaluate(() => performance.now());
        const totalTime = endTime - startTime;

        const networkServerTime = totalTime - domUpdateTime;

        // Store results
        storeBenchmarkResult('DOM Overhead', 'Pure DOM Update', domUpdateTime, 5);
        storeBenchmarkResult('DOM Overhead', 'Network + Server Time', networkServerTime, 100);
        storeBenchmarkResult('DOM Overhead', 'Total Fragment Update', totalTime, 150);

        console.log(`DOM update: ${domUpdateTime.toFixed(2)}ms`);
        console.log(`Network + Server: ${networkServerTime.toFixed(2)}ms`);
        console.log(`Total: ${totalTime.toFixed(2)}ms`);

        expect(domUpdateTime).toBeLessThan(5); // DOM should be very fast
    });

    test('Benchmark: Memory consumption comparison', async ({ page }) => {
        // Measure memory before
        const memoryBefore = await page.evaluate(() => {
            if (performance.memory) {
                return performance.memory.usedJSHeapSize;
            }
            return 0;
        });

        // Perform 50 fragment updates
        for (let i = 0; i < 50; i++) {
            await page.evaluate(() => {
                window.LiveComponents.get('counter:benchmark').call('increment', {}, {
                    fragments: ['#counter-value']
                });
            });
            await page.waitForTimeout(10);
        }

        // Measure memory after
        const memoryAfterFragments = await page.evaluate(() => {
            if (performance.memory) {
                return performance.memory.usedJSHeapSize;
            }
            return 0;
        });

        // Reset
        await page.reload();
        await page.waitForFunction(() => window.LiveComponents !== undefined);

        const memoryBeforeFull = await page.evaluate(() => {
            if (performance.memory) {
                return performance.memory.usedJSHeapSize;
            }
            return 0;
        });

        // Perform 50 full renders
        for (let i = 0; i < 50; i++) {
            await page.evaluate(() => {
                window.LiveComponents.get('counter:benchmark').call('increment');
            });
            await page.waitForTimeout(10);
        }

        const memoryAfterFull = await page.evaluate(() => {
            if (performance.memory) {
                return performance.memory.usedJSHeapSize;
            }
            return 0;
        });

        if (memoryBefore > 0) {
            const fragmentMemoryDelta = memoryAfterFragments - memoryBefore;
            const fullMemoryDelta = memoryAfterFull - memoryBeforeFull;

            storeBenchmarkResult('Memory Consumption (50 updates)', 'Fragment Updates Memory Delta', fragmentMemoryDelta, 1000000, 'bytes');
            storeBenchmarkResult('Memory Consumption (50 updates)', 'Full Renders Memory Delta', fullMemoryDelta, 2000000, 'bytes');

            console.log(`Fragment memory delta: ${(fragmentMemoryDelta / 1024).toFixed(2)} KB`);
            console.log(`Full render memory delta: ${(fullMemoryDelta / 1024).toFixed(2)} KB`);
        } else {
            console.log('Memory API not available (Firefox/Safari)');
        }
    });

    test('Benchmark: Cache effectiveness', async ({ page }) => {
        // First update (no cache)
        const firstUpdateTime = await measureActionTime(
            page,
            'counter:benchmark',
            'increment',
            {},
            { fragments: ['#counter-value'] }
        );

        // Second update (potentially cached)
        const secondUpdateTime = await measureActionTime(
            page,
            'counter:benchmark',
            'increment',
            {},
            { fragments: ['#counter-value'] }
        );

        // Third update (cached)
        const thirdUpdateTime = await measureActionTime(
            page,
            'counter:benchmark',
            'increment',
            {},
            { fragments: ['#counter-value'] }
        );

        const avgCachedTime = (secondUpdateTime + thirdUpdateTime) / 2;

        storeBenchmarkResult('Cache Effectiveness', 'First Update (Cold)', firstUpdateTime, 100);
        storeBenchmarkResult('Cache Effectiveness', 'Average Cached Update', avgCachedTime, 80);

        console.log(`First update (cold): ${firstUpdateTime.toFixed(2)}ms`);
        console.log(`Average cached: ${avgCachedTime.toFixed(2)}ms`);

        if (avgCachedTime < firstUpdateTime) {
            const improvement = ((firstUpdateTime - avgCachedTime) / firstUpdateTime * 100).toFixed(1);
            console.log(`Cache improves performance by ${improvement}%`);
        }
    });

    // After all tests, write results to file for report generation
    test.afterAll(async () => {
        const fs = await import('fs');
        const path = await import('path');

        const resultsFile = path.default.join(
            process.cwd(),
            'test-results',
            'benchmark-results.json'
        );

        // Ensure directory exists
        const dir = path.default.dirname(resultsFile);
        if (!fs.default.existsSync(dir)) {
            fs.default.mkdirSync(dir, { recursive: true });
        }

        fs.default.writeFileSync(
            resultsFile,
            JSON.stringify({
                timestamp: new Date().toISOString(),
                results: benchmarkResults,
                summary: {
                    total: benchmarkResults.length,
                    passed: benchmarkResults.filter(r => r.passed).length,
                    failed: benchmarkResults.filter(r => !r.passed).length
                }
            }, null, 2)
        );

        console.log(`\nBenchmark results written to: ${resultsFile}`);
        console.log(`Total benchmarks: ${benchmarkResults.length}`);
        console.log(`Passed: ${benchmarkResults.filter(r => r.passed).length}`);
        console.log(`Failed: ${benchmarkResults.filter(r => !r.passed).length}`);
    });
});
