/**
 * E2E Integration Tests: Video Upload (Frontend)
 *
 * Epic: OQI-002 - Módulo Educativo
 * Full integration test: VideoUploadForm + video-upload.service
 *
 * Tests validate:
 * - Complete user flow: select → metadata → upload → complete
 * - API integration (mocked)
 * - Progress callbacks
 * - Error scenarios
 * - Retry mechanisms
 */

import { describe, it, expect, beforeEach, vi } from 'vitest';
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import VideoUploadForm from '../../modules/education/components/VideoUploadForm';
import { apiClient } from '../../lib/apiClient';

// Mock API client
vi.mock('../../lib/apiClient', () => ({
  apiClient: {
    post: vi.fn(),
    get: vi.fn(),
    delete: vi.fn(),
  },
}));

// Mock fetch for S3 uploads
global.fetch = vi.fn() as any;

describe('E2E Integration: Video Upload (Frontend)', () => {
  const mockOnComplete = vi.fn();
  const mockOnCancel = vi.fn();

  beforeEach(() => {
    vi.clearAllMocks();
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  // ==========================================================================
  // Complete Happy Path Flow
  // ==========================================================================

  describe('Happy Path: Complete Upload Flow', () => {
    it('should complete full upload: select → metadata → upload → callback', async () => {
      // Mock API responses
      (apiClient.post as vi.Mock)
        // Init upload response
        .mockResolvedValueOnce({
          data: {
            success: true,
            data: {
              videoId: 'video-123',
              uploadId: 'upload-456',
              storageKey: 'videos/test.mp4',
              presignedUrls: [
                'https://s3.example.com/part1',
                'https://s3.example.com/part2',
              ],
            },
          },
        })
        // Complete upload response
        .mockResolvedValueOnce({
          data: {
            success: true,
            data: {
              id: 'video-123',
              status: 'uploaded',
              cdnUrl: 'https://cdn.example.com/video-123.mp4',
              title: 'Integration Test Video',
              description: 'Test description',
            },
            message: 'Upload completed successfully',
          },
        });

      // Mock S3 uploads
      (global.fetch as vi.Mock).mockResolvedValue({
        ok: true,
        headers: new Map([['ETag', '"etag-abc123"']]),
      });

      render(
        <VideoUploadForm
          courseId="course-123"
          lessonId="lesson-456"
          onUploadComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      // Step 1: Select file
      const file = new File(
        [new ArrayBuffer(10 * 1024 * 1024)], // 10MB
        'integration-test.mp4',
        { type: 'video/mp4' }
      );

      const fileInput = screen.getByLabelText(/select file/i) as HTMLInputElement;
      fireEvent.change(fileInput, { target: { files: [file] } });

      await waitFor(() => {
        expect(screen.getByDisplayValue(/integration test/i)).toBeInTheDocument();
      });

      // Move to step 2
      fireEvent.click(screen.getByText(/next|continue/i));

      // Step 2: Fill metadata
      const titleInput = screen.getByLabelText(/title/i);
      const descInput = screen.getByLabelText(/description/i);

      fireEvent.change(titleInput, { target: { value: 'Integration Test Video' } });
      fireEvent.change(descInput, { target: { value: 'Test description for integration' } });

      // Add tags
      const tagInput = screen.getByPlaceholderText(/add tag/i);
      const addTagButton = screen.getByText(/add tag/i).closest('button');

      fireEvent.change(tagInput, { target: { value: 'e2e' } });
      fireEvent.click(addTagButton!);

      fireEvent.change(tagInput, { target: { value: 'testing' } });
      fireEvent.click(addTagButton!);

      // Move to step 3
      fireEvent.click(screen.getByText(/next|continue/i));

      // Step 3: Start upload
      const uploadButton = screen.getByText(/upload|start/i);
      fireEvent.click(uploadButton);

      // Wait for completion
      await waitFor(
        () => {
          expect(mockOnComplete).toHaveBeenCalledWith(
            'video-123',
            expect.objectContaining({
              title: 'Integration Test Video',
              description: 'Test description for integration',
              tags: expect.arrayContaining(['e2e', 'testing']),
            })
          );
        },
        { timeout: 5000 }
      );

      // Verify API calls
      expect(apiClient.post).toHaveBeenCalledTimes(2); // init + complete
      expect(global.fetch).toHaveBeenCalledTimes(2); // 2 parts uploaded

      // Verify init call
      expect(apiClient.post).toHaveBeenNthCalledWith(
        1,
        '/api/v1/education/videos/upload-init',
        expect.objectContaining({
          courseId: 'course-123',
          lessonId: 'lesson-456',
          filename: 'integration-test.mp4',
          fileSize: file.size,
          contentType: 'video/mp4',
          metadata: expect.objectContaining({
            title: 'Integration Test Video',
            description: 'Test description for integration',
            tags: expect.arrayContaining(['e2e', 'testing']),
          }),
        })
      );

      // Verify complete call
      expect(apiClient.post).toHaveBeenNthCalledWith(
        2,
        '/api/v1/education/videos/video-123/complete',
        expect.objectContaining({
          parts: expect.arrayContaining([
            { partNumber: 1, etag: 'etag-abc123' },
            { partNumber: 2, etag: 'etag-abc123' },
          ]),
        })
      );
    });

    it('should show progress updates during upload', async () => {
      (apiClient.post as vi.Mock)
        .mockResolvedValueOnce({
          data: {
            success: true,
            data: {
              videoId: 'video-456',
              uploadId: 'upload-789',
              storageKey: 'videos/progress.mp4',
              presignedUrls: ['https://s3.example.com/part1'],
            },
          },
        })
        .mockResolvedValueOnce({
          data: {
            success: true,
            data: { id: 'video-456', status: 'uploaded' },
          },
        });

      (global.fetch as vi.Mock).mockImplementation(async () => {
        // Simulate delay
        await new Promise(resolve => setTimeout(resolve, 100));
        return {
          ok: true,
          headers: new Map([['ETag', '"etag"']]),
        };
      });

      render(
        <VideoUploadForm
          courseId="course-123"
          onUploadComplete={mockOnComplete}
        />
      );

      // Quick setup
      const file = new File([new ArrayBuffer(5 * 1024 * 1024)], 'test.mp4', {
        type: 'video/mp4',
      });

      const fileInput = screen.getByLabelText(/select file/i) as HTMLInputElement;
      fireEvent.change(fileInput, { target: { files: [file] } });

      await waitFor(() => screen.getByDisplayValue(/test/i));

      fireEvent.click(screen.getByText(/next|continue/i));

      const titleInput = screen.getByLabelText(/title/i);
      const descInput = screen.getByLabelText(/description/i);
      fireEvent.change(titleInput, { target: { value: 'Test' } });
      fireEvent.change(descInput, { target: { value: 'Description' } });

      fireEvent.click(screen.getByText(/next|continue/i));

      // Start upload
      fireEvent.click(screen.getByText(/upload|start/i));

      // Should show uploading status
      await waitFor(() => {
        expect(screen.getByText(/uploading|progress/i)).toBeInTheDocument();
      });

      // Should eventually complete
      await waitFor(
        () => {
          expect(screen.getByText(/complete|success/i)).toBeInTheDocument();
        },
        { timeout: 5000 }
      );
    });
  });

  // ==========================================================================
  // Error Scenarios
  // ==========================================================================

  describe('Error Handling', () => {
    it('should handle init upload failure', async () => {
      (apiClient.post as vi.Mock).mockRejectedValueOnce(
        new Error('Failed to initialize upload')
      );

      render(
        <VideoUploadForm
          courseId="course-123"
          onUploadComplete={mockOnComplete}
        />
      );

      // Setup
      const file = new File([new ArrayBuffer(5 * 1024 * 1024)], 'test.mp4', {
        type: 'video/mp4',
      });

      const fileInput = screen.getByLabelText(/select file/i) as HTMLInputElement;
      fireEvent.change(fileInput, { target: { files: [file] } });

      await waitFor(() => screen.getByDisplayValue(/test/i));

      fireEvent.click(screen.getByText(/next|continue/i));

      const titleInput = screen.getByLabelText(/title/i);
      const descInput = screen.getByLabelText(/description/i);
      fireEvent.change(titleInput, { target: { value: 'Test' } });
      fireEvent.change(descInput, { target: { value: 'Description' } });

      fireEvent.click(screen.getByText(/next|continue/i));

      // Start upload
      fireEvent.click(screen.getByText(/upload|start/i));

      // Should show error
      await waitFor(() => {
        expect(screen.getByText(/error|failed/i)).toBeInTheDocument();
      });

      // Should NOT call onComplete
      expect(mockOnComplete).not.toHaveBeenCalled();
    });

    it('should handle S3 upload failure', async () => {
      (apiClient.post as vi.Mock).mockResolvedValueOnce({
        data: {
          success: true,
          data: {
            videoId: 'video-789',
            uploadId: 'upload-012',
            storageKey: 'videos/fail.mp4',
            presignedUrls: ['https://s3.example.com/part1'],
          },
        },
      });

      // Mock S3 failure
      (global.fetch as vi.Mock).mockResolvedValue({
        ok: false,
        statusText: 'Forbidden',
      });

      render(
        <VideoUploadForm
          courseId="course-123"
          onUploadComplete={mockOnComplete}
        />
      );

      // Setup
      const file = new File([new ArrayBuffer(5 * 1024 * 1024)], 'test.mp4', {
        type: 'video/mp4',
      });

      const fileInput = screen.getByLabelText(/select file/i) as HTMLInputElement;
      fireEvent.change(fileInput, { target: { files: [file] } });

      await waitFor(() => screen.getByDisplayValue(/test/i));

      fireEvent.click(screen.getByText(/next|continue/i));

      const titleInput = screen.getByLabelText(/title/i);
      const descInput = screen.getByLabelText(/description/i);
      fireEvent.change(titleInput, { target: { value: 'Test' } });
      fireEvent.change(descInput, { target: { value: 'Description' } });

      fireEvent.click(screen.getByText(/next|continue/i));

      // Start upload
      fireEvent.click(screen.getByText(/upload|start/i));

      // Should show error
      await waitFor(() => {
        expect(screen.getByText(/error|failed|forbidden/i)).toBeInTheDocument();
      });

      expect(mockOnComplete).not.toHaveBeenCalled();
    });

    it('should handle complete upload failure', async () => {
      (apiClient.post as vi.Mock)
        .mockResolvedValueOnce({
          data: {
            success: true,
            data: {
              videoId: 'video-complete-fail',
              uploadId: 'upload-fail',
              storageKey: 'videos/complete-fail.mp4',
              presignedUrls: ['https://s3.example.com/part1'],
            },
          },
        })
        // Fail on complete
        .mockRejectedValueOnce(new Error('Failed to complete upload'));

      (global.fetch as vi.Mock).mockResolvedValue({
        ok: true,
        headers: new Map([['ETag', '"etag"']]),
      });

      render(
        <VideoUploadForm
          courseId="course-123"
          onUploadComplete={mockOnComplete}
        />
      );

      // Setup
      const file = new File([new ArrayBuffer(5 * 1024 * 1024)], 'test.mp4', {
        type: 'video/mp4',
      });

      const fileInput = screen.getByLabelText(/select file/i) as HTMLInputElement;
      fireEvent.change(fileInput, { target: { files: [file] } });

      await waitFor(() => screen.getByDisplayValue(/test/i));

      fireEvent.click(screen.getByText(/next|continue/i));

      const titleInput = screen.getByLabelText(/title/i);
      const descInput = screen.getByLabelText(/description/i);
      fireEvent.change(titleInput, { target: { value: 'Test' } });
      fireEvent.change(descInput, { target: { value: 'Description' } });

      fireEvent.click(screen.getByText(/next|continue/i));

      // Start upload
      fireEvent.click(screen.getByText(/upload|start/i));

      // Should show error
      await waitFor(() => {
        expect(screen.getByText(/error|failed/i)).toBeInTheDocument();
      });

      expect(mockOnComplete).not.toHaveBeenCalled();
    });
  });

  // ==========================================================================
  // Retry Mechanism
  // ==========================================================================

  describe('Retry Mechanism', () => {
    it('should allow retry after failure', async () => {
      // First attempt fails
      (apiClient.post as vi.Mock)
        .mockRejectedValueOnce(new Error('Network error'))
        // Retry succeeds
        .mockResolvedValueOnce({
          data: {
            success: true,
            data: {
              videoId: 'video-retry',
              uploadId: 'upload-retry',
              storageKey: 'videos/retry.mp4',
              presignedUrls: ['https://s3.example.com/part1'],
            },
          },
        })
        .mockResolvedValueOnce({
          data: {
            success: true,
            data: { id: 'video-retry', status: 'uploaded' },
          },
        });

      (global.fetch as vi.Mock).mockResolvedValue({
        ok: true,
        headers: new Map([['ETag', '"etag"']]),
      });

      render(
        <VideoUploadForm
          courseId="course-123"
          onUploadComplete={mockOnComplete}
        />
      );

      // Setup
      const file = new File([new ArrayBuffer(5 * 1024 * 1024)], 'test.mp4', {
        type: 'video/mp4',
      });

      const fileInput = screen.getByLabelText(/select file/i) as HTMLInputElement;
      fireEvent.change(fileInput, { target: { files: [file] } });

      await waitFor(() => screen.getByDisplayValue(/test/i));

      fireEvent.click(screen.getByText(/next|continue/i));

      const titleInput = screen.getByLabelText(/title/i);
      const descInput = screen.getByLabelText(/description/i);
      fireEvent.change(titleInput, { target: { value: 'Test' } });
      fireEvent.change(descInput, { target: { value: 'Description' } });

      fireEvent.click(screen.getByText(/next|continue/i));

      // First attempt
      fireEvent.click(screen.getByText(/upload|start/i));

      // Wait for error
      await waitFor(() => {
        expect(screen.getByText(/error|failed/i)).toBeInTheDocument();
      });

      // Retry
      const retryButton = screen.getByText(/retry|try again/i);
      fireEvent.click(retryButton);

      // Should succeed on retry
      await waitFor(
        () => {
          expect(mockOnComplete).toHaveBeenCalledWith('video-retry', expect.any(Object));
        },
        { timeout: 5000 }
      );
    });
  });

  // ==========================================================================
  // Cancel Flow
  // ==========================================================================

  describe('Cancel Flow', () => {
    it('should invoke onCancel callback when cancelled', () => {
      render(
        <VideoUploadForm
          courseId="course-123"
          onUploadComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      const cancelButton = screen.getByText(/cancel/i);
      fireEvent.click(cancelButton);

      expect(mockOnCancel).toHaveBeenCalled();
    });

    it('should cleanup resources on cancel during upload', async () => {
      (apiClient.post as vi.Mock)
        .mockResolvedValueOnce({
          data: {
            success: true,
            data: {
              videoId: 'video-cancel',
              uploadId: 'upload-cancel',
              storageKey: 'videos/cancel.mp4',
              presignedUrls: ['https://s3.example.com/part1'],
            },
          },
        })
        // Abort endpoint
        .mockResolvedValueOnce({ data: { success: true } });

      (global.fetch as vi.Mock).mockImplementation(
        () => new Promise(() => {}) // Never resolves (simulates long upload)
      );

      render(
        <VideoUploadForm
          courseId="course-123"
          onUploadComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      // Setup
      const file = new File([new ArrayBuffer(5 * 1024 * 1024)], 'test.mp4', {
        type: 'video/mp4',
      });

      const fileInput = screen.getByLabelText(/select file/i) as HTMLInputElement;
      fireEvent.change(fileInput, { target: { files: [file] } });

      await waitFor(() => screen.getByDisplayValue(/test/i));

      fireEvent.click(screen.getByText(/next|continue/i));

      const titleInput = screen.getByLabelText(/title/i);
      const descInput = screen.getByLabelText(/description/i);
      fireEvent.change(titleInput, { target: { value: 'Test' } });
      fireEvent.change(descInput, { target: { value: 'Description' } });

      fireEvent.click(screen.getByText(/next|continue/i));

      // Start upload
      fireEvent.click(screen.getByText(/upload|start/i));

      await waitFor(() => {
        expect(screen.getByText(/uploading/i)).toBeInTheDocument();
      });

      // Cancel during upload
      const cancelButton = screen.getByText(/cancel/i);
      fireEvent.click(cancelButton);

      expect(mockOnCancel).toHaveBeenCalled();
    });
  });

  // ==========================================================================
  // Validation
  // ==========================================================================

  describe('Validation', () => {
    it('should not allow upload without file', () => {
      render(
        <VideoUploadForm
          courseId="course-123"
          onUploadComplete={mockOnComplete}
        />
      );

      // Try to proceed without selecting file
      const nextButton = screen.queryByText(/next|continue/i);
      expect(nextButton).toBeDisabled();
    });

    it('should not allow upload without required metadata', async () => {
      render(
        <VideoUploadForm
          courseId="course-123"
          onUploadComplete={mockOnComplete}
        />
      );

      // Select file
      const file = new File([new ArrayBuffer(5 * 1024 * 1024)], 'test.mp4', {
        type: 'video/mp4',
      });

      const fileInput = screen.getByLabelText(/select file/i) as HTMLInputElement;
      fireEvent.change(fileInput, { target: { files: [file] } });

      await waitFor(() => screen.getByDisplayValue(/test/i));

      fireEvent.click(screen.getByText(/next|continue/i));

      // Clear required fields
      const titleInput = screen.getByLabelText(/title/i);
      const descInput = screen.getByLabelText(/description/i);

      fireEvent.change(titleInput, { target: { value: '' } });
      fireEvent.change(descInput, { target: { value: '' } });

      // Try to proceed
      fireEvent.click(screen.getByText(/next|continue/i));

      // Should show validation errors
      await waitFor(() => {
        expect(screen.getByText(/title is required/i)).toBeInTheDocument();
        expect(screen.getByText(/description is required/i)).toBeInTheDocument();
      });

      // Should not proceed to step 3
      expect(screen.queryByText(/upload|start/i)).not.toBeInTheDocument();
    });
  });
});
