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

/**
 * LiveComponents E2E Integration Tests
 *
 * Tests für Cross-Cutting Concerns:
 * - Partial Rendering (Fragment-basierte Updates)
 * - Batch Operations (Mehrere Actions zusammen)
 * - Server-Sent Events (Echtzeit-Kommunikation)
 *
 * @requires Test-Seite unter /livecomponents/test/integration
 * @requires LiveComponents Framework initialisiert
 */

test.describe('Partial Rendering', () => {
    test.beforeEach(async ({ page }) => {
        await page.goto('https://localhost/livecomponents/test/integration');
        await page.waitForFunction(() => window.LiveComponents !== undefined, { timeout: 5000 });
    });

    test('should update only targeted fragment without full component re-render', async ({ page }) => {
        // Timestamp vor Update erfassen
        const initialTimestamp = await page.locator('#component-timestamp').textContent();

        // Fragment-spezifische Action triggern
        await page.click('button#update-fragment');
        await page.waitForTimeout(500);

        // Fragment wurde aktualisiert
        const fragmentContent = await page.locator('#target-fragment').textContent();
        expect(fragmentContent).toContain('Updated');

        // Component timestamp NICHT geändert (kein Full Render)
        const finalTimestamp = await page.locator('#component-timestamp').textContent();
        expect(finalTimestamp).toBe(initialTimestamp);

        // Nur Fragment wurde im DOM aktualisiert
        const updateCount = await page.evaluate(() => {
            return window.__fragmentUpdateCount || 0;
        });
        expect(updateCount).toBe(1);
    });

    test('should update multiple fragments in single request', async ({ page }) => {
        await page.click('button#update-multiple-fragments');
        await page.waitForTimeout(500);

        // Beide Fragments aktualisiert
        const fragment1 = await page.locator('#fragment-1').textContent();
        const fragment2 = await page.locator('#fragment-2').textContent();

        expect(fragment1).toContain('Fragment 1 Updated');
        expect(fragment2).toContain('Fragment 2 Updated');

        // Nur 1 HTTP Request für beide Updates
        const requestCount = await page.evaluate(() => {
            return window.__requestCount || 0;
        });
        expect(requestCount).toBe(1);
    });

    test('should preserve component state during partial render', async ({ page }) => {
        // State setzen
        await page.fill('input#state-input', 'Test Value');
        await page.click('button#save-state');
        await page.waitForTimeout(200);

        // Fragment Update triggern
        await page.click('button#update-fragment');
        await page.waitForTimeout(500);

        // State wurde NICHT verloren
        const inputValue = await page.inputValue('input#state-input');
        expect(inputValue).toBe('Test Value');

        // State im Component noch vorhanden
        const stateValue = await page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');
            return component.state.get('savedValue');
        });
        expect(stateValue).toBe('Test Value');
    });

    test('should handle nested fragment updates', async ({ page }) => {
        await page.click('button#update-nested-fragment');
        await page.waitForTimeout(500);

        // Parent fragment aktualisiert
        const parentContent = await page.locator('#parent-fragment').textContent();
        expect(parentContent).toContain('Parent Updated');

        // Child fragment innerhalb von parent auch aktualisiert
        const childContent = await page.locator('#child-fragment').textContent();
        expect(childContent).toContain('Child Updated');

        // Sibling fragment NICHT aktualisiert
        const siblingTimestamp = await page.locator('#sibling-fragment').getAttribute('data-timestamp');
        const originalTimestamp = await page.evaluate(() => window.__originalSiblingTimestamp);
        expect(siblingTimestamp).toBe(originalTimestamp);
    });

    test('should apply morphing algorithm for minimal DOM changes', async ({ page }) => {
        // DOM Nodes vor Update zählen
        const nodesBefore = await page.evaluate(() => {
            const fragment = document.getElementById('morph-fragment');
            return fragment.querySelectorAll('*').length;
        });

        // Kleines Update im Fragment
        await page.click('button#small-update');
        await page.waitForTimeout(500);

        // DOM Nodes nach Update
        const nodesAfter = await page.evaluate(() => {
            const fragment = document.getElementById('morph-fragment');
            return fragment.querySelectorAll('*').length;
        });

        // Anzahl Nodes sollte gleich bleiben (nur Content geändert)
        expect(nodesAfter).toBe(nodesBefore);

        // Morphing Stats prüfen
        const morphStats = await page.evaluate(() => window.__morphingStats);
        expect(morphStats.nodesAdded).toBe(0);
        expect(morphStats.nodesRemoved).toBe(0);
        expect(morphStats.nodesUpdated).toBeGreaterThan(0);
    });

    test('should handle fragment-not-found gracefully', async ({ page }) => {
        // Action mit nicht-existierendem Fragment
        await page.evaluate(() => {
            window.LiveComponents.get('integration:test').call('updateFragment', {
                fragmentId: 'non-existent-fragment'
            });
        });

        await page.waitForTimeout(500);

        // Error handling
        const errorMessage = await page.locator('.fragment-error').textContent();
        expect(errorMessage).toContain('Fragment not found');

        // Component bleibt funktionsfähig
        await page.click('button#update-fragment');
        await page.waitForTimeout(500);

        const fragmentContent = await page.locator('#target-fragment').textContent();
        expect(fragmentContent).toContain('Updated');
    });
});

test.describe('Batch Operations', () => {
    test.beforeEach(async ({ page }) => {
        await page.goto('https://localhost/livecomponents/test/integration');
        await page.waitForFunction(() => window.LiveComponents !== undefined, { timeout: 5000 });
    });

    test('should execute multiple actions in single batch request', async ({ page }) => {
        const requestsBefore = await page.evaluate(() => window.__requestCount || 0);

        // Batch von 3 Actions triggern
        await page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');

            component.batch()
                .call('incrementCounter')
                .call('updateText', { text: 'Batch Updated' })
                .call('toggleFlag')
                .execute();
        });

        await page.waitForTimeout(500);

        // Nur 1 zusätzlicher Request für alle 3 Actions
        const requestsAfter = await page.evaluate(() => window.__requestCount || 0);
        expect(requestsAfter - requestsBefore).toBe(1);

        // Alle Actions wurden ausgeführt
        const counter = await page.locator('#counter-value').textContent();
        expect(parseInt(counter)).toBeGreaterThan(0);

        const text = await page.locator('#text-value').textContent();
        expect(text).toBe('Batch Updated');

        const flag = await page.locator('#flag-value').textContent();
        expect(flag).toBe('true');
    });

    test('should maintain action execution order in batch', async ({ page }) => {
        const executionLog = [];

        await page.exposeFunction('logExecution', (action) => {
            executionLog.push(action);
        });

        await page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');

            component.batch()
                .call('action1')
                .call('action2')
                .call('action3')
                .execute();
        });

        await page.waitForTimeout(1000);

        // Actions in korrekter Reihenfolge ausgeführt
        expect(executionLog).toEqual(['action1', 'action2', 'action3']);
    });

    test('should rollback batch on action failure', async ({ page }) => {
        const initialCounter = await page.locator('#counter-value').textContent();

        await page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');

            component.batch()
                .call('incrementCounter')  // Erfolgreich
                .call('failingAction')     // Fehlschlag
                .call('incrementCounter')  // Sollte nicht ausgeführt werden
                .execute();
        });

        await page.waitForTimeout(500);

        // Counter wurde NICHT inkrementiert (Rollback)
        const finalCounter = await page.locator('#counter-value').textContent();
        expect(finalCounter).toBe(initialCounter);

        // Error angezeigt
        const errorVisible = await page.locator('.batch-error').isVisible();
        expect(errorVisible).toBe(true);
    });

    test('should handle partial batch execution with continueOnError flag', async ({ page }) => {
        await page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');

            component.batch({ continueOnError: true })
                .call('incrementCounter')  // Erfolgreich
                .call('failingAction')     // Fehlschlag
                .call('incrementCounter')  // Sollte trotzdem ausgeführt werden
                .execute();
        });

        await page.waitForTimeout(500);

        // Counter wurde 2x inkrementiert (trotz Fehler in der Mitte)
        const counter = await page.locator('#counter-value').textContent();
        expect(parseInt(counter)).toBe(2);

        // Partial success angezeigt
        const partialSuccessVisible = await page.locator('.partial-success').isVisible();
        expect(partialSuccessVisible).toBe(true);
    });

    test('should support batch with mixed action types', async ({ page }) => {
        await page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');

            component.batch()
                .call('syncAction')        // Synchrone Action
                .call('asyncAction')       // Asynchrone Action
                .call('fragmentAction')    // Fragment Update Action
                .execute();
        });

        await page.waitForTimeout(1000);

        // Alle Action-Typen erfolgreich
        const syncResult = await page.locator('#sync-result').isVisible();
        const asyncResult = await page.locator('#async-result').isVisible();
        const fragmentResult = await page.locator('#fragment-result').isVisible();

        expect(syncResult).toBe(true);
        expect(asyncResult).toBe(true);
        expect(fragmentResult).toBe(true);
    });

    test('should batch state updates efficiently', async ({ page }) => {
        const stateUpdatesBefore = await page.evaluate(() => {
            return window.__stateUpdateCount || 0;
        });

        await page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');

            // 10 State-Updates in Batch
            const batch = component.batch();
            for (let i = 0; i < 10; i++) {
                batch.call('incrementCounter');
            }
            batch.execute();
        });

        await page.waitForTimeout(500);

        // Counter wurde 10x inkrementiert
        const counter = await page.locator('#counter-value').textContent();
        expect(parseInt(counter)).toBe(10);

        // Nur 1 State-Update Event gefeuert (optimiert)
        const stateUpdatesAfter = await page.evaluate(() => {
            return window.__stateUpdateCount || 0;
        });
        expect(stateUpdatesAfter - stateUpdatesBefore).toBe(1);
    });
});

test.describe('Server-Sent Events (SSE)', () => {
    test.beforeEach(async ({ page }) => {
        await page.goto('https://localhost/livecomponents/test/integration');
        await page.waitForFunction(() => window.LiveComponents !== undefined, { timeout: 5000 });
    });

    test('should establish SSE connection for real-time updates', async ({ page }) => {
        // SSE aktivieren
        await page.click('button#enable-sse');
        await page.waitForTimeout(1000);

        // Connection Status prüfen
        const connectionStatus = await page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');
            return component.sse?.readyState;
        });

        expect(connectionStatus).toBe(1); // OPEN

        // SSE-Indikator sichtbar
        const sseIndicator = await page.locator('.sse-connected').isVisible();
        expect(sseIndicator).toBe(true);
    });

    test('should receive and apply server-pushed updates', async ({ page }) => {
        await page.click('button#enable-sse');
        await page.waitForTimeout(1000);

        const initialValue = await page.locator('#live-value').textContent();

        // Server-Push triggern (z.B. durch andere User/Aktion)
        await page.evaluate(() => {
            // Simuliere Server-Push Event
            const event = new MessageEvent('message', {
                data: JSON.stringify({
                    type: 'update',
                    payload: { liveValue: 'Server Updated' }
                })
            });

            const component = window.LiveComponents.get('integration:test');
            component.sse?.dispatchEvent(event);
        });

        await page.waitForTimeout(500);

        // Wert wurde automatisch aktualisiert
        const updatedValue = await page.locator('#live-value').textContent();
        expect(updatedValue).toBe('Server Updated');
        expect(updatedValue).not.toBe(initialValue);
    });

    test('should handle SSE reconnection on connection loss', async ({ page }) => {
        await page.click('button#enable-sse');
        await page.waitForTimeout(1000);

        // Connection simuliert unterbrochen
        await page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');
            component.sse?.close();
        });

        await page.waitForTimeout(500);

        // Reconnection Indikator
        const reconnecting = await page.locator('.sse-reconnecting').isVisible();
        expect(reconnecting).toBe(true);

        // Nach Retry-Periode reconnected
        await page.waitForTimeout(3000);

        const reconnected = await page.locator('.sse-connected').isVisible();
        expect(reconnected).toBe(true);
    });

    test('should support multiple SSE event types', async ({ page }) => {
        await page.click('button#enable-sse');
        await page.waitForTimeout(1000);

        const events = [];

        await page.exposeFunction('logSSEEvent', (eventType) => {
            events.push(eventType);
        });

        // Verschiedene Event-Typen senden
        await page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');
            const sse = component.sse;

            sse.dispatchEvent(new MessageEvent('message', {
                data: JSON.stringify({ type: 'update', payload: {} })
            }));

            sse.dispatchEvent(new MessageEvent('message', {
                data: JSON.stringify({ type: 'notification', payload: {} })
            }));

            sse.dispatchEvent(new MessageEvent('message', {
                data: JSON.stringify({ type: 'sync', payload: {} })
            }));
        });

        await page.waitForTimeout(1000);

        // Alle Event-Typen wurden verarbeitet
        expect(events).toContain('update');
        expect(events).toContain('notification');
        expect(events).toContain('sync');
    });

    test('should batch SSE updates for performance', async ({ page }) => {
        await page.click('button#enable-sse');
        await page.waitForTimeout(1000);

        const renderCountBefore = await page.evaluate(() => window.__renderCount || 0);

        // Viele schnelle SSE Updates
        await page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');
            const sse = component.sse;

            for (let i = 0; i < 20; i++) {
                sse.dispatchEvent(new MessageEvent('message', {
                    data: JSON.stringify({
                        type: 'update',
                        payload: { counter: i }
                    })
                }));
            }
        });

        await page.waitForTimeout(1000);

        // Finale Wert korrekt
        const finalCounter = await page.locator('#sse-counter').textContent();
        expect(parseInt(finalCounter)).toBe(19);

        // Deutlich weniger Renders als Updates (Batching)
        const renderCountAfter = await page.evaluate(() => window.__renderCount || 0);
        const renderDiff = renderCountAfter - renderCountBefore;
        expect(renderDiff).toBeLessThan(20);
        expect(renderDiff).toBeGreaterThan(0);
    });

    test('should close SSE connection when component unmounts', async ({ page }) => {
        await page.click('button#enable-sse');
        await page.waitForTimeout(1000);

        // Connection aktiv
        let connectionState = await page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');
            return component.sse?.readyState;
        });
        expect(connectionState).toBe(1); // OPEN

        // Component unmounten
        await page.evaluate(() => {
            window.LiveComponents.get('integration:test').unmount();
        });

        await page.waitForTimeout(500);

        // Connection geschlossen
        connectionState = await page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');
            return component.sse?.readyState;
        });
        expect(connectionState).toBe(2); // CLOSED
    });

    test('should handle SSE authentication and authorization', async ({ page }) => {
        // SSE mit Auth Token
        await page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');
            component.enableSSE({ authToken: 'valid-token-123' });
        });

        await page.waitForTimeout(1000);

        // Connection erfolgreich mit Auth
        const authenticated = await page.locator('.sse-authenticated').isVisible();
        expect(authenticated).toBe(true);

        // SSE mit ungültigem Token
        await page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');
            component.sse?.close();
            component.enableSSE({ authToken: 'invalid-token' });
        });

        await page.waitForTimeout(1000);

        // Auth Fehler
        const authError = await page.locator('.sse-auth-error').isVisible();
        expect(authError).toBe(true);
    });

    test('should support SSE with custom event filters', async ({ page }) => {
        await page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');

            // Nur 'important' Events empfangen
            component.enableSSE({
                filter: (event) => event.priority === 'important'
            });
        });

        await page.waitForTimeout(1000);

        const receivedEvents = [];

        await page.exposeFunction('trackEvent', (event) => {
            receivedEvents.push(event);
        });

        // Verschiedene Events senden
        await page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');
            const sse = component.sse;

            sse.dispatchEvent(new MessageEvent('message', {
                data: JSON.stringify({
                    type: 'update',
                    priority: 'important',
                    payload: { id: 1 }
                })
            }));

            sse.dispatchEvent(new MessageEvent('message', {
                data: JSON.stringify({
                    type: 'update',
                    priority: 'low',
                    payload: { id: 2 }
                })
            }));

            sse.dispatchEvent(new MessageEvent('message', {
                data: JSON.stringify({
                    type: 'update',
                    priority: 'important',
                    payload: { id: 3 }
                })
            }));
        });

        await page.waitForTimeout(1000);

        // Nur 'important' Events wurden verarbeitet
        expect(receivedEvents.length).toBe(2);
        expect(receivedEvents.map(e => e.id)).toEqual([1, 3]);
    });
});

test.describe('Integration: Partial Rendering + Batch + SSE', () => {
    test.beforeEach(async ({ page }) => {
        await page.goto('https://localhost/livecomponents/test/integration');
        await page.waitForFunction(() => window.LiveComponents !== undefined, { timeout: 5000 });
    });

    test('should combine partial rendering with batch operations', async ({ page }) => {
        const requestsBefore = await page.evaluate(() => window.__requestCount || 0);

        // Batch mit Fragment-Updates
        await page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');

            component.batch()
                .call('updateFragment', { fragmentId: 'fragment-1' })
                .call('updateFragment', { fragmentId: 'fragment-2' })
                .call('incrementCounter')
                .execute();
        });

        await page.waitForTimeout(500);

        // Nur 1 Request für alle Updates
        const requestsAfter = await page.evaluate(() => window.__requestCount || 0);
        expect(requestsAfter - requestsBefore).toBe(1);

        // Beide Fragments aktualisiert
        const fragment1Updated = await page.locator('#fragment-1').textContent();
        const fragment2Updated = await page.locator('#fragment-2').textContent();
        expect(fragment1Updated).toContain('Updated');
        expect(fragment2Updated).toContain('Updated');

        // Counter auch aktualisiert
        const counter = await page.locator('#counter-value').textContent();
        expect(parseInt(counter)).toBeGreaterThan(0);
    });

    test('should push partial updates via SSE', async ({ page }) => {
        await page.click('button#enable-sse');
        await page.waitForTimeout(1000);

        // SSE Event mit Fragment Update
        await page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');

            component.sse.dispatchEvent(new MessageEvent('message', {
                data: JSON.stringify({
                    type: 'fragment-update',
                    fragmentId: 'live-fragment',
                    html: '<div id="live-fragment">SSE Updated</div>'
                })
            }));
        });

        await page.waitForTimeout(500);

        // Fragment via SSE aktualisiert
        const fragmentContent = await page.locator('#live-fragment').textContent();
        expect(fragmentContent).toBe('SSE Updated');

        // Kein Full Component Render
        const componentTimestamp = await page.locator('#component-timestamp').textContent();
        const originalTimestamp = await page.evaluate(() => window.__originalComponentTimestamp);
        expect(componentTimestamp).toBe(originalTimestamp);
    });

    test('should batch SSE-triggered actions efficiently', async ({ page }) => {
        await page.click('button#enable-sse');
        await page.waitForTimeout(1000);

        const actionsBefore = await page.evaluate(() => window.__actionCount || 0);

        // Mehrere SSE Events die Actions triggern
        await page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');
            const sse = component.sse;

            // 5 Events in kurzer Zeit
            for (let i = 0; i < 5; i++) {
                sse.dispatchEvent(new MessageEvent('message', {
                    data: JSON.stringify({
                        type: 'trigger-action',
                        action: 'incrementCounter'
                    })
                }));
            }
        });

        await page.waitForTimeout(1000);

        // Actions wurden gebatched
        const actionsAfter = await page.evaluate(() => window.__actionCount || 0);
        const actionDiff = actionsAfter - actionsBefore;

        // Weniger Action-Aufrufe als Events (durch Batching)
        expect(actionDiff).toBeLessThan(5);
        expect(actionDiff).toBeGreaterThan(0);

        // Finaler Counter-Wert korrekt
        const counter = await page.locator('#counter-value').textContent();
        expect(parseInt(counter)).toBe(5);
    });

    test('should maintain consistency across all integration features', async ({ page }) => {
        await page.click('button#enable-sse');
        await page.waitForTimeout(1000);

        // Komplexes Szenario: Batch + Partial + SSE gleichzeitig

        // 1. Batch mit Fragment-Updates starten
        const batchPromise = page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');
            return component.batch()
                .call('updateFragment', { fragmentId: 'fragment-1' })
                .call('incrementCounter')
                .execute();
        });

        // 2. Während Batch läuft: SSE Update
        await page.waitForTimeout(200);
        await page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');
            component.sse.dispatchEvent(new MessageEvent('message', {
                data: JSON.stringify({
                    type: 'update',
                    payload: { liveValue: 'SSE During Batch' }
                })
            }));
        });

        await batchPromise;
        await page.waitForTimeout(500);

        // Alle Updates korrekt angewendet
        const fragment1 = await page.locator('#fragment-1').textContent();
        expect(fragment1).toContain('Updated');

        const counter = await page.locator('#counter-value').textContent();
        expect(parseInt(counter)).toBeGreaterThan(0);

        const liveValue = await page.locator('#live-value').textContent();
        expect(liveValue).toBe('SSE During Batch');

        // State konsistent
        const stateConsistent = await page.evaluate(() => {
            const component = window.LiveComponents.get('integration:test');
            return component.validateStateConsistency();
        });
        expect(stateConsistent).toBe(true);
    });
});
