/**
 * LiveComponents E2E Tests - Request Batching
 *
 * Tests for automatic request batching functionality:
 * - Multiple actions batched into single HTTP request
 * - Batch payload structure and response handling
 * - Error handling for failed actions in batch
 * - Batch size limits and automatic debouncing
 * - Network efficiency validation
 *
 * @see src/Framework/LiveComponents/Services/BatchRequestHandler.php
 * @see resources/js/modules/LiveComponent.js (batch handling)
 */

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

test.describe('LiveComponents - Request Batching', () => {
    test.beforeEach(async ({ page }) => {
        // Navigate to test page with LiveComponent
        await page.goto('/livecomponents/test');

        // Wait for LiveComponent to be initialized
        await page.waitForFunction(() => window.LiveComponent !== undefined);
    });

    test('should batch multiple rapid actions into single request', async ({ page }) => {
        // Track network requests
        const requests = [];
        page.on('request', request => {
            if (request.url().includes('/livecomponent')) {
                requests.push(request);
            }
        });

        // Trigger multiple actions rapidly (should be batched)
        await page.click('[data-lc-action="increment"]');
        await page.click('[data-lc-action="increment"]');
        await page.click('[data-lc-action="increment"]');

        // Wait for batch to be sent (debounced)
        await page.waitForTimeout(100);

        // Should only have 1 request (batched)
        expect(requests.length).toBe(1);

        // Verify batch request structure
        const request = requests[0];
        const postData = request.postData();
        expect(postData).toBeTruthy();

        const payload = JSON.parse(postData);
        expect(payload).toHaveProperty('batch');
        expect(payload.batch).toBeInstanceOf(Array);
        expect(payload.batch.length).toBe(3);
    });

    test('should handle batch response with multiple action results', async ({ page }) => {
        // Trigger batched actions
        await page.click('[data-lc-action="increment"]');
        await page.click('[data-lc-action="updateTitle"]');
        await page.click('[data-lc-action="toggleFlag"]');

        // Wait for batch response
        const response = await page.waitForResponse(response =>
            response.url().includes('/livecomponent') &&
            response.status() === 200
        );

        const responseData = await response.json();

        // Verify batch response structure
        expect(responseData).toHaveProperty('batch_results');
        expect(responseData.batch_results).toBeInstanceOf(Array);
        expect(responseData.batch_results.length).toBe(3);

        // Verify each result has required fields
        responseData.batch_results.forEach(result => {
            expect(result).toHaveProperty('success');
            expect(result).toHaveProperty('html');
            expect(result).toHaveProperty('action_index');
        });

        // Verify component state was updated correctly
        const counter = await page.locator('.counter-value').textContent();
        expect(parseInt(counter)).toBeGreaterThan(0);
    });

    test('should handle partial batch failure gracefully', async ({ page }) => {
        // Trigger batch with one failing action
        await page.click('[data-lc-action="increment"]');
        await page.click('[data-lc-action="failingAction"]'); // This will fail
        await page.click('[data-lc-action="updateTitle"]');

        await page.waitForTimeout(100);

        const response = await page.waitForResponse(response =>
            response.url().includes('/livecomponent')
        );

        const responseData = await response.json();

        // First action should succeed
        expect(responseData.batch_results[0].success).toBe(true);

        // Second action should fail
        expect(responseData.batch_results[1].success).toBe(false);
        expect(responseData.batch_results[1]).toHaveProperty('error');

        // Third action should still execute
        expect(responseData.batch_results[2].success).toBe(true);

        // Verify error notification was shown
        const errorNotification = page.locator('.livecomponent-error');
        await expect(errorNotification).toBeVisible();
    });

    test('should respect batch size limits', async ({ page }) => {
        const requests = [];
        page.on('request', request => {
            if (request.url().includes('/livecomponent')) {
                requests.push(request);
            }
        });

        // Trigger more actions than batch limit (default: 10)
        for (let i = 0; i < 15; i++) {
            await page.click('[data-lc-action="increment"]');
        }

        await page.waitForTimeout(200);

        // Should have 2 requests (batch size limit: 10 + 5)
        expect(requests.length).toBeGreaterThanOrEqual(2);

        // First batch should have 10 actions
        const firstBatchData = JSON.parse(requests[0].postData());
        expect(firstBatchData.batch.length).toBeLessThanOrEqual(10);

        // Second batch should have remaining actions
        const secondBatchData = JSON.parse(requests[1].postData());
        expect(secondBatchData.batch.length).toBeLessThanOrEqual(5);
    });

    test('should debounce rapid actions before batching', async ({ page }) => {
        const requests = [];
        page.on('request', request => {
            if (request.url().includes('/livecomponent')) {
                requests.push(request);
            }
        });

        // Click rapidly within debounce window (default: 50ms)
        await page.click('[data-lc-action="increment"]');
        await page.waitForTimeout(10);
        await page.click('[data-lc-action="increment"]');
        await page.waitForTimeout(10);
        await page.click('[data-lc-action="increment"]');

        // Wait for debounce + network
        await page.waitForTimeout(150);

        // Should only have 1 batched request
        expect(requests.length).toBe(1);

        // Click after debounce window expires
        await page.waitForTimeout(100);
        await page.click('[data-lc-action="increment"]');

        await page.waitForTimeout(150);

        // Should have 2 requests total
        expect(requests.length).toBe(2);
    });

    test('should send immediate request when batch is manually flushed', async ({ page }) => {
        const requests = [];
        page.on('request', request => {
            if (request.url().includes('/livecomponent')) {
                requests.push(request);
            }
        });

        // Trigger action
        await page.click('[data-lc-action="increment"]');

        // Manually flush batch before debounce expires
        await page.evaluate(() => {
            window.LiveComponent.flushBatch();
        });

        // Wait for immediate request
        await page.waitForTimeout(50);

        // Should have sent request immediately
        expect(requests.length).toBe(1);
    });

    test('should validate network efficiency with batching', async ({ page }) => {
        // Count requests WITHOUT batching
        await page.evaluate(() => {
            window.LiveComponent.disableBatching();
        });

        const unbatchedRequests = [];
        page.on('request', request => {
            if (request.url().includes('/livecomponent')) {
                unbatchedRequests.push(request);
            }
        });

        // Trigger 5 actions
        for (let i = 0; i < 5; i++) {
            await page.click('[data-lc-action="increment"]');
            await page.waitForTimeout(20);
        }

        await page.waitForTimeout(200);

        const unbatchedCount = unbatchedRequests.length;
        expect(unbatchedCount).toBe(5); // Should be 5 separate requests

        // Reload and test WITH batching
        await page.reload();
        await page.waitForFunction(() => window.LiveComponent !== undefined);

        const batchedRequests = [];
        page.on('request', request => {
            if (request.url().includes('/livecomponent')) {
                batchedRequests.push(request);
            }
        });

        // Trigger same 5 actions
        for (let i = 0; i < 5; i++) {
            await page.click('[data-lc-action="increment"]');
        }

        await page.waitForTimeout(200);

        const batchedCount = batchedRequests.length;
        expect(batchedCount).toBe(1); // Should be 1 batched request

        // Network efficiency: 80% reduction in requests
        const efficiency = ((unbatchedCount - batchedCount) / unbatchedCount) * 100;
        expect(efficiency).toBeGreaterThanOrEqual(80);
    });

    test('should preserve action order in batch', async ({ page }) => {
        // Trigger actions in specific order
        await page.click('[data-lc-action="setValueA"]');
        await page.click('[data-lc-action="setValueB"]');
        await page.click('[data-lc-action="setValueC"]');

        const response = await page.waitForResponse(response =>
            response.url().includes('/livecomponent') &&
            response.status() === 200
        );

        const responseData = await response.json();

        // Verify action_index preserves order
        expect(responseData.batch_results[0].action_index).toBe(0);
        expect(responseData.batch_results[1].action_index).toBe(1);
        expect(responseData.batch_results[2].action_index).toBe(2);

        // Verify final state reflects correct order
        const finalValue = await page.locator('.value-display').textContent();
        expect(finalValue).toBe('C'); // Last action wins
    });

    test('should include component state in batch request', async ({ page }) => {
        const requests = [];
        page.on('request', request => {
            if (request.url().includes('/livecomponent')) {
                requests.push(request);
            }
        });

        // Update state and trigger batch
        await page.fill('[data-lc-model="searchTerm"]', 'test query');
        await page.click('[data-lc-action="search"]');
        await page.click('[data-lc-action="filter"]');

        await page.waitForTimeout(100);

        const request = requests[0];
        const payload = JSON.parse(request.postData());

        // Verify state is included
        expect(payload).toHaveProperty('state');
        expect(payload.state.searchTerm).toBe('test query');

        // Verify batch actions
        expect(payload.batch.length).toBe(2);
        expect(payload.batch[0].action).toBe('search');
        expect(payload.batch[1].action).toBe('filter');
    });

    test('should handle batch with different action parameters', async ({ page }) => {
        // Trigger actions with different params
        await page.evaluate(() => {
            const componentId = document.querySelector('[data-component-id]').dataset.componentId;

            window.LiveComponent.executeAction(componentId, 'updateField', { field: 'name', value: 'John' });
            window.LiveComponent.executeAction(componentId, 'updateField', { field: 'email', value: 'john@example.com' });
            window.LiveComponent.executeAction(componentId, 'updateField', { field: 'age', value: 30 });
        });

        const response = await page.waitForResponse(response =>
            response.url().includes('/livecomponent') &&
            response.status() === 200
        );

        const responseData = await response.json();

        // Verify all 3 actions were batched
        expect(responseData.batch_results.length).toBe(3);

        // Verify each action maintained its parameters
        expect(responseData.batch_results[0].success).toBe(true);
        expect(responseData.batch_results[1].success).toBe(true);
        expect(responseData.batch_results[2].success).toBe(true);

        // Verify final component state
        const nameField = await page.locator('[data-field="name"]').textContent();
        const emailField = await page.locator('[data-field="email"]').textContent();
        const ageField = await page.locator('[data-field="age"]').textContent();

        expect(nameField).toBe('John');
        expect(emailField).toBe('john@example.com');
        expect(ageField).toBe('30');
    });

    test('should emit batch events for monitoring', async ({ page }) => {
        let batchStartEvent = null;
        let batchCompleteEvent = null;

        // Listen for batch events
        await page.exposeFunction('onBatchStart', (event) => {
            batchStartEvent = event;
        });

        await page.exposeFunction('onBatchComplete', (event) => {
            batchCompleteEvent = event;
        });

        await page.evaluate(() => {
            window.addEventListener('livecomponent:batch-start', (e) => {
                window.onBatchStart(e.detail);
            });

            window.addEventListener('livecomponent:batch-complete', (e) => {
                window.onBatchComplete(e.detail);
            });
        });

        // Trigger batch
        await page.click('[data-lc-action="increment"]');
        await page.click('[data-lc-action="increment"]');
        await page.click('[data-lc-action="increment"]');

        await page.waitForResponse(response =>
            response.url().includes('/livecomponent') &&
            response.status() === 200
        );

        // Wait for events
        await page.waitForTimeout(100);

        // Verify batch-start event
        expect(batchStartEvent).not.toBeNull();
        expect(batchStartEvent).toHaveProperty('actionCount');
        expect(batchStartEvent.actionCount).toBe(3);

        // Verify batch-complete event
        expect(batchCompleteEvent).not.toBeNull();
        expect(batchCompleteEvent).toHaveProperty('results');
        expect(batchCompleteEvent.results.length).toBe(3);
        expect(batchCompleteEvent).toHaveProperty('duration');
    });

    test('should handle concurrent batches correctly', async ({ page }) => {
        const requests = [];
        page.on('request', request => {
            if (request.url().includes('/livecomponent')) {
                requests.push(request);
            }
        });

        // Trigger first batch
        await page.click('[data-lc-action="increment"]');
        await page.click('[data-lc-action="increment"]');

        // Wait for partial debounce
        await page.waitForTimeout(30);

        // Trigger second batch (while first is still debouncing)
        await page.click('[data-lc-action="updateTitle"]');
        await page.click('[data-lc-action="updateTitle"]');

        // Wait for all batches to complete
        await page.waitForTimeout(200);

        // Should have sent batches separately
        expect(requests.length).toBeGreaterThanOrEqual(1);

        // Verify final state is consistent
        const counter = await page.locator('.counter-value').textContent();
        expect(parseInt(counter)).toBe(2); // Both increments applied
    });

    test('should handle batch timeout gracefully', async ({ page }) => {
        // Mock slow server response
        await page.route('**/livecomponent/**', async route => {
            await new Promise(resolve => setTimeout(resolve, 5000)); // 5 second delay
            route.continue();
        });

        // Trigger batch
        await page.click('[data-lc-action="increment"]');

        // Should show loading state
        const loadingIndicator = page.locator('.livecomponent-loading');
        await expect(loadingIndicator).toBeVisible();

        // Wait for timeout (should be less than 5 seconds)
        await page.waitForTimeout(3000);

        // Should show timeout error
        const errorNotification = page.locator('.livecomponent-error');
        await expect(errorNotification).toBeVisible();
        await expect(errorNotification).toContainText('timeout');
    });

    test('should allow disabling batching per action', async ({ page }) => {
        const requests = [];
        page.on('request', request => {
            if (request.url().includes('/livecomponent')) {
                requests.push(request);
            }
        });

        // Trigger batchable actions
        await page.click('[data-lc-action="increment"]');
        await page.click('[data-lc-action="increment"]');

        // Trigger non-batchable action (critical/immediate)
        await page.click('[data-lc-action="criticalAction"]');

        await page.waitForTimeout(200);

        // Should have 2 requests:
        // 1. Immediate critical action
        // 2. Batched increments
        expect(requests.length).toBeGreaterThanOrEqual(2);

        // First request should be the critical action (immediate)
        const firstRequest = JSON.parse(requests[0].postData());
        expect(firstRequest.action).toBe('criticalAction');
        expect(firstRequest).not.toHaveProperty('batch');

        // Second request should be the batched increments
        const secondRequest = JSON.parse(requests[1].postData());
        expect(secondRequest).toHaveProperty('batch');
        expect(secondRequest.batch.length).toBe(2);
    });
});
