/**
 * LiveComponents E2E Tests - Chunked Upload with Progress
 *
 * Tests for large file upload with chunking and progress tracking:
 * - File chunking into smaller pieces
 * - Sequential chunk upload with retry
 * - Real-time progress updates
 * - Pause/resume functionality
 * - Error handling and recovery
 * - Upload cancellation
 *
 * @see src/Framework/LiveComponents/Services/ChunkedUploadHandler.php
 * @see resources/js/modules/LiveComponent.js (chunked upload)
 */

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

test.describe('LiveComponents - Chunked Upload', () => {
    test.beforeEach(async ({ page }) => {
        // Navigate to upload test page
        await page.goto('/livecomponents/upload-test');

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

    test('should chunk large file into multiple pieces', async ({ page }) => {
        const requests = [];
        page.on('request', request => {
            if (request.url().includes('/livecomponent/upload')) {
                requests.push(request);
            }
        });

        // Create test file (5MB - should be chunked)
        const testFile = await page.evaluate(() => {
            const size = 5 * 1024 * 1024; // 5MB
            const blob = new Blob([new ArrayBuffer(size)], { type: 'application/octet-stream' });
            return new File([blob], 'test-file.bin', { type: 'application/octet-stream' });
        });

        // Set file input
        const fileInput = page.locator('input[type="file"][data-lc-upload]');
        await fileInput.setInputFiles({
            name: 'test-file.bin',
            mimeType: 'application/octet-stream',
            buffer: Buffer.alloc(5 * 1024 * 1024) // 5MB
        });

        // Wait for chunked upload to complete
        await page.waitForSelector('.upload-complete', { timeout: 30000 });

        // With 1MB chunk size, should have ~5 chunks
        expect(requests.length).toBeGreaterThanOrEqual(5);
        expect(requests.length).toBeLessThanOrEqual(6);

        // Verify each chunk request has correct headers
        requests.forEach((request, index) => {
            const headers = request.headers();
            expect(headers['x-chunk-index']).toBeDefined();
            expect(headers['x-chunk-total']).toBeDefined();
            expect(headers['x-upload-id']).toBeDefined();
        });
    });

    test('should track upload progress in real-time', async ({ page }) => {
        const progressUpdates = [];

        // Monitor progress updates
        await page.exposeFunction('onProgressUpdate', (progress) => {
            progressUpdates.push(progress);
        });

        await page.evaluate(() => {
            window.addEventListener('livecomponent:upload-progress', (e) => {
                window.onProgressUpdate(e.detail);
            });
        });

        // Upload file
        const fileInput = page.locator('input[type="file"][data-lc-upload]');
        await fileInput.setInputFiles({
            name: 'test-file.bin',
            mimeType: 'application/octet-stream',
            buffer: Buffer.alloc(3 * 1024 * 1024) // 3MB
        });

        // Wait for upload to complete
        await page.waitForSelector('.upload-complete', { timeout: 30000 });

        // Should have received multiple progress updates
        expect(progressUpdates.length).toBeGreaterThan(0);

        // Progress should increase monotonically
        for (let i = 1; i < progressUpdates.length; i++) {
            expect(progressUpdates[i].percentage).toBeGreaterThanOrEqual(
                progressUpdates[i - 1].percentage
            );
        }

        // Final progress should be 100%
        const finalProgress = progressUpdates[progressUpdates.length - 1];
        expect(finalProgress.percentage).toBe(100);
    });

    test('should display progress bar during upload', async ({ page }) => {
        const fileInput = page.locator('input[type="file"][data-lc-upload]');
        await fileInput.setInputFiles({
            name: 'test-file.bin',
            mimeType: 'application/octet-stream',
            buffer: Buffer.alloc(2 * 1024 * 1024) // 2MB
        });

        // Progress bar should appear
        const progressBar = page.locator('.upload-progress-bar');
        await expect(progressBar).toBeVisible();

        // Progress percentage should be visible
        const progressText = page.locator('.upload-progress-text');
        await expect(progressText).toBeVisible();

        // Wait for progress to reach 100%
        await expect(progressText).toContainText('100%', { timeout: 30000 });

        // Progress bar should hide or show completion
        await expect(progressBar).toHaveClass(/complete|success/);
    });

    test('should handle pause and resume correctly', async ({ page }) => {
        const fileInput = page.locator('input[type="file"][data-lc-upload]');
        await fileInput.setInputFiles({
            name: 'test-file.bin',
            mimeType: 'application/octet-stream',
            buffer: Buffer.alloc(5 * 1024 * 1024) // 5MB
        });

        // Wait for upload to start
        await page.waitForSelector('.upload-progress-bar');

        // Wait for some progress
        await page.waitForTimeout(500);

        // Pause upload
        await page.click('[data-action="pause-upload"]');

        const progressTextBefore = await page.locator('.upload-progress-text').textContent();
        const percentageBefore = parseInt(progressTextBefore);

        // Wait to ensure upload is paused
        await page.waitForTimeout(1000);

        const progressTextAfter = await page.locator('.upload-progress-text').textContent();
        const percentageAfter = parseInt(progressTextAfter);

        // Progress should not have changed (paused)
        expect(percentageAfter).toBe(percentageBefore);

        // Resume upload
        await page.click('[data-action="resume-upload"]');

        // Wait for upload to complete
        await page.waitForSelector('.upload-complete', { timeout: 30000 });

        // Final progress should be 100%
        const finalProgress = await page.locator('.upload-progress-text').textContent();
        expect(finalProgress).toContain('100%');
    });

    test('should allow cancelling upload', async ({ page }) => {
        const fileInput = page.locator('input[type="file"][data-lc-upload]');
        await fileInput.setInputFiles({
            name: 'test-file.bin',
            mimeType: 'application/octet-stream',
            buffer: Buffer.alloc(5 * 1024 * 1024) // 5MB
        });

        // Wait for upload to start
        await page.waitForSelector('.upload-progress-bar');

        // Wait for some progress
        await page.waitForTimeout(500);

        // Cancel upload
        await page.click('[data-action="cancel-upload"]');

        // Progress bar should disappear or show cancelled state
        await expect(page.locator('.upload-progress-bar')).not.toBeVisible();

        // Cancelled notification should appear
        const notification = page.locator('.upload-notification');
        await expect(notification).toBeVisible();
        await expect(notification).toContainText(/cancel/i);
    });

    test('should retry failed chunks automatically', async ({ page }) => {
        let attemptCount = 0;

        // Mock server to fail first 2 chunk uploads
        await page.route('**/livecomponent/upload/**', async (route) => {
            attemptCount++;

            if (attemptCount <= 2) {
                // Fail first 2 attempts
                route.fulfill({
                    status: 500,
                    body: JSON.stringify({ error: 'Temporary server error' })
                });
            } else {
                // Succeed on retry
                route.continue();
            }
        });

        const fileInput = page.locator('input[type="file"][data-lc-upload]');
        await fileInput.setInputFiles({
            name: 'test-file.bin',
            mimeType: 'application/octet-stream',
            buffer: Buffer.alloc(1024 * 1024) // 1MB (single chunk)
        });

        // Should eventually succeed after retries
        await page.waitForSelector('.upload-complete', { timeout: 30000 });

        // Should have attempted at least 3 times (2 failures + 1 success)
        expect(attemptCount).toBeGreaterThanOrEqual(3);
    });

    test('should handle chunk upload failure with max retries', async ({ page }) => {
        // Mock server to always fail
        await page.route('**/livecomponent/upload/**', async (route) => {
            route.fulfill({
                status: 500,
                body: JSON.stringify({ error: 'Permanent server error' })
            });
        });

        const fileInput = page.locator('input[type="file"][data-lc-upload]');
        await fileInput.setInputFiles({
            name: 'test-file.bin',
            mimeType: 'application/octet-stream',
            buffer: Buffer.alloc(1024 * 1024) // 1MB
        });

        // Should show error after max retries
        const errorNotification = page.locator('.upload-error');
        await expect(errorNotification).toBeVisible({ timeout: 30000 });
        await expect(errorNotification).toContainText(/failed|error/i);

        // Progress bar should show error state
        const progressBar = page.locator('.upload-progress-bar');
        await expect(progressBar).toHaveClass(/error|failed/);
    });

    test('should upload multiple files sequentially', async ({ page }) => {
        const fileInput = page.locator('input[type="file"][data-lc-upload][multiple]');

        // Select multiple files
        await fileInput.setInputFiles([
            {
                name: 'file1.bin',
                mimeType: 'application/octet-stream',
                buffer: Buffer.alloc(2 * 1024 * 1024) // 2MB
            },
            {
                name: 'file2.bin',
                mimeType: 'application/octet-stream',
                buffer: Buffer.alloc(2 * 1024 * 1024) // 2MB
            },
            {
                name: 'file3.bin',
                mimeType: 'application/octet-stream',
                buffer: Buffer.alloc(2 * 1024 * 1024) // 2MB
            }
        ]);

        // Should show multiple progress bars or list
        const uploadItems = page.locator('.upload-item');
        await expect(uploadItems).toHaveCount(3);

        // Wait for all uploads to complete
        await page.waitForSelector('.all-uploads-complete', { timeout: 60000 });

        // All items should show completion
        const completedItems = page.locator('.upload-item.complete');
        await expect(completedItems).toHaveCount(3);
    });

    test('should preserve chunk order during upload', async ({ page }) => {
        const chunkOrder = [];

        // Monitor chunk requests
        page.on('request', request => {
            if (request.url().includes('/livecomponent/upload')) {
                const headers = request.headers();
                const chunkIndex = parseInt(headers['x-chunk-index']);
                chunkOrder.push(chunkIndex);
            }
        });

        const fileInput = page.locator('input[type="file"][data-lc-upload]');
        await fileInput.setInputFiles({
            name: 'test-file.bin',
            mimeType: 'application/octet-stream',
            buffer: Buffer.alloc(3 * 1024 * 1024) // 3MB
        });

        await page.waitForSelector('.upload-complete', { timeout: 30000 });

        // Chunks should be uploaded in order (0, 1, 2, ...)
        for (let i = 0; i < chunkOrder.length; i++) {
            expect(chunkOrder[i]).toBe(i);
        }
    });

    test('should include upload metadata in chunk requests', async ({ page }) => {
        const requests = [];

        page.on('request', request => {
            if (request.url().includes('/livecomponent/upload')) {
                requests.push(request);
            }
        });

        const fileInput = page.locator('input[type="file"][data-lc-upload]');
        await fileInput.setInputFiles({
            name: 'document.pdf',
            mimeType: 'application/pdf',
            buffer: Buffer.alloc(2 * 1024 * 1024) // 2MB
        });

        await page.waitForSelector('.upload-complete', { timeout: 30000 });

        // First chunk should have file metadata
        const firstRequest = requests[0];
        const headers = firstRequest.headers();

        expect(headers['x-file-name']).toBe('document.pdf');
        expect(headers['x-file-size']).toBeDefined();
        expect(headers['x-file-type']).toBe('application/pdf');
        expect(headers['x-upload-id']).toBeDefined();
    });

    test('should show upload speed and time remaining', async ({ page }) => {
        const fileInput = page.locator('input[type="file"][data-lc-upload]');
        await fileInput.setInputFiles({
            name: 'test-file.bin',
            mimeType: 'application/octet-stream',
            buffer: Buffer.alloc(5 * 1024 * 1024) // 5MB
        });

        // Wait for upload to start
        await page.waitForSelector('.upload-progress-bar');

        // Speed indicator should be visible
        const speedIndicator = page.locator('.upload-speed');
        await expect(speedIndicator).toBeVisible();
        await expect(speedIndicator).toContainText(/MB\/s|KB\/s/);

        // Time remaining should be visible
        const timeRemaining = page.locator('.upload-time-remaining');
        await expect(timeRemaining).toBeVisible();
        await expect(timeRemaining).toContainText(/second|minute/);
    });

    test('should handle file size validation before upload', async ({ page }) => {
        // Mock large file exceeding limit (e.g., 100MB limit)
        const fileInput = page.locator('input[type="file"][data-lc-upload]');

        // Set max file size via data attribute
        await page.evaluate(() => {
            const input = document.querySelector('input[type="file"][data-lc-upload]');
            input.dataset.maxFileSize = '10485760'; // 10MB limit
        });

        // Try to upload 20MB file
        await fileInput.setInputFiles({
            name: 'large-file.bin',
            mimeType: 'application/octet-stream',
            buffer: Buffer.alloc(20 * 1024 * 1024) // 20MB
        });

        // Should show validation error
        const errorNotification = page.locator('.upload-error');
        await expect(errorNotification).toBeVisible();
        await expect(errorNotification).toContainText(/too large|size limit/i);

        // Upload should not start
        const progressBar = page.locator('.upload-progress-bar');
        await expect(progressBar).not.toBeVisible();
    });

    test('should handle file type validation before upload', async ({ page }) => {
        // Set allowed file types
        await page.evaluate(() => {
            const input = document.querySelector('input[type="file"][data-lc-upload]');
            input.dataset.allowedTypes = 'image/jpeg,image/png,image/gif';
        });

        const fileInput = page.locator('input[type="file"][data-lc-upload]');

        // Try to upload disallowed file type
        await fileInput.setInputFiles({
            name: 'document.pdf',
            mimeType: 'application/pdf',
            buffer: Buffer.alloc(1024 * 1024) // 1MB
        });

        // Should show validation error
        const errorNotification = page.locator('.upload-error');
        await expect(errorNotification).toBeVisible();
        await expect(errorNotification).toContainText(/file type|not allowed/i);
    });

    test('should emit upload events for monitoring', async ({ page }) => {
        const events = {
            start: null,
            progress: [],
            complete: null
        };

        await page.exposeFunction('onUploadStart', (event) => {
            events.start = event;
        });

        await page.exposeFunction('onUploadProgress', (event) => {
            events.progress.push(event);
        });

        await page.exposeFunction('onUploadComplete', (event) => {
            events.complete = event;
        });

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

            window.addEventListener('livecomponent:upload-progress', (e) => {
                window.onUploadProgress(e.detail);
            });

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

        // Upload file
        const fileInput = page.locator('input[type="file"][data-lc-upload]');
        await fileInput.setInputFiles({
            name: 'test-file.bin',
            mimeType: 'application/octet-stream',
            buffer: Buffer.alloc(2 * 1024 * 1024) // 2MB
        });

        await page.waitForSelector('.upload-complete', { timeout: 30000 });

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

        // Verify start event
        expect(events.start).not.toBeNull();
        expect(events.start).toHaveProperty('fileName');
        expect(events.start).toHaveProperty('fileSize');

        // Verify progress events
        expect(events.progress.length).toBeGreaterThan(0);
        events.progress.forEach(event => {
            expect(event).toHaveProperty('percentage');
            expect(event).toHaveProperty('uploadedBytes');
        });

        // Verify complete event
        expect(events.complete).not.toBeNull();
        expect(events.complete).toHaveProperty('uploadId');
        expect(events.complete).toHaveProperty('duration');
    });

    test('should handle network interruption and resume', async ({ page }) => {
        let requestCount = 0;

        // Simulate network interruption
        await page.route('**/livecomponent/upload/**', async (route) => {
            requestCount++;

            if (requestCount === 2) {
                // Interrupt on 2nd chunk
                route.abort('failed');
            } else {
                route.continue();
            }
        });

        const fileInput = page.locator('input[type="file"][data-lc-upload]');
        await fileInput.setInputFiles({
            name: 'test-file.bin',
            mimeType: 'application/octet-stream',
            buffer: Buffer.alloc(3 * 1024 * 1024) // 3MB (3 chunks)
        });

        // Should retry and eventually complete
        await page.waitForSelector('.upload-complete', { timeout: 30000 });

        // Should have attempted more than 3 times due to retry
        expect(requestCount).toBeGreaterThan(3);
    });
});
