/**
 * E2E Tests: Video Upload Form (Frontend)
 *
 * Epic: OQI-002 - Módulo Educativo
 * Component: VideoUploadForm (3-step wizard)
 *
 * Tests validate:
 * - Step 1: File selection (drag & drop, validation)
 * - Step 2: Metadata entry (title, description, tags)
 * - Step 3: Upload flow (progress, callbacks)
 * - CRITICAL: NO File blob stored unnecessarily in component state
 */

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';

// Mock video upload service
vi.mock('../../services/video-upload.service', () => ({
  videoUploadService: {
    uploadVideo: vi.fn(),
  },
}));

import { videoUploadService } from '../../services/video-upload.service';

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

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

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

  // ==========================================================================
  // STEP 1: File Selection & Validation
  // ==========================================================================

  describe('Step 1: File Selection', () => {
    it('should render file upload area with drag & drop support', () => {
      render(
        <VideoUploadForm
          courseId="course-123"
          onUploadComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      expect(screen.getByText(/drag.*drop/i)).toBeInTheDocument();
      expect(screen.getByText(/select file/i)).toBeInTheDocument();
    });

    it('should accept valid video file (mp4)', async () => {
      render(
        <VideoUploadForm
          courseId="course-123"
          onUploadComplete={mockOnComplete}
        />
      );

      const file = new File(['video content'], 'test-video.mp4', { type: 'video/mp4' });
      const input = screen.getByLabelText(/select file/i) as HTMLInputElement;

      fireEvent.change(input, { target: { files: [file] } });

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

      // Should show file name without extension
      expect(screen.getByDisplayValue(/test video/i)).toBeInTheDocument();
    });

    it('should accept valid video file (webm)', async () => {
      render(
        <VideoUploadForm
          courseId="course-123"
          onUploadComplete={mockOnComplete}
        />
      );

      const file = new File(['video content'], 'test.webm', { type: 'video/webm' });
      const input = screen.getByLabelText(/select file/i) as HTMLInputElement;

      fireEvent.change(input, { target: { files: [file] } });

      await waitFor(() => {
        expect(screen.queryByText(/invalid format/i)).not.toBeInTheDocument();
      });
    });

    it('should reject invalid file format (avi)', async () => {
      render(
        <VideoUploadForm
          courseId="course-123"
          onUploadComplete={mockOnComplete}
        />
      );

      const file = new File(['video content'], 'test.avi', { type: 'video/x-msvideo' });
      const input = screen.getByLabelText(/select file/i) as HTMLInputElement;

      fireEvent.change(input, { target: { files: [file] } });

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

    it('should reject file exceeding size limit (500MB default)', async () => {
      render(
        <VideoUploadForm
          courseId="course-123"
          maxFileSizeMB={100}
          onUploadComplete={mockOnComplete}
        />
      );

      // Create file larger than 100MB
      const largeFile = new File(
        [new ArrayBuffer(101 * 1024 * 1024)],
        'large-video.mp4',
        { type: 'video/mp4' }
      );

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

      await waitFor(() => {
        expect(screen.getByText(/file too large/i)).toBeInTheDocument();
        expect(screen.getByText(/maximum.*100mb/i)).toBeInTheDocument();
      });
    });

    it('should support drag and drop', async () => {
      render(
        <VideoUploadForm
          courseId="course-123"
          onUploadComplete={mockOnComplete}
        />
      );

      const file = new File(['video content'], 'dropped-video.mp4', { type: 'video/mp4' });
      const dropZone = screen.getByText(/drag.*drop/i).closest('div');

      const dropEvent = {
        preventDefault: vi.fn(),
        dataTransfer: {
          files: [file],
        },
      };

      fireEvent.drop(dropZone!, dropEvent as any);

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

    it('should extract video duration on file select', async () => {
      render(
        <VideoUploadForm
          courseId="course-123"
          onUploadComplete={mockOnComplete}
        />
      );

      const file = new File(['video content'], 'video.mp4', { type: 'video/mp4' });
      const input = screen.getByLabelText(/select file/i) as HTMLInputElement;

      fireEvent.change(input, { target: { files: [file] } });

      await waitFor(() => {
        // Duration should be displayed (2 minutes = 120 seconds)
        expect(screen.getByText(/2:00|120/i)).toBeInTheDocument();
      });
    });

    it('should NOT store video blob in component state', async () => {
      const { container } = render(
        <VideoUploadForm
          courseId="course-123"
          onUploadComplete={mockOnComplete}
        />
      );

      const file = new File(['video content'], 'video.mp4', { type: 'video/mp4' });
      const input = screen.getByLabelText(/select file/i) as HTMLInputElement;

      fireEvent.change(input, { target: { files: [file] } });

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

      // CRITICAL: Verify NO data URL or base64 encoded video in DOM
      const html = container.innerHTML;
      expect(html).not.toContain('data:video/');
      expect(html).not.toContain('base64');

      // Only blob URLs allowed (for preview)
      expect(global.URL.createObjectURL).toHaveBeenCalled();
    });
  });

  // ==========================================================================
  // STEP 2: Metadata Entry
  // ==========================================================================

  describe('Step 2: Metadata Entry', () => {
    const setupWithFile = async () => {
      const { container, rerender } = render(
        <VideoUploadForm
          courseId="course-123"
          onUploadComplete={mockOnComplete}
        />
      );

      const file = new File(['video content'], 'test-video.mp4', { type: 'video/mp4' });
      const input = screen.getByLabelText(/select file/i) as HTMLInputElement;
      fireEvent.change(input, { target: { files: [file] } });

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

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

      return { container, rerender };
    };

    it('should require title (max 100 chars)', async () => {
      await setupWithFile();

      const titleInput = screen.getByLabelText(/title/i) as HTMLInputElement;

      // Clear auto-filled title
      fireEvent.change(titleInput, { target: { value: '' } });

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

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

    it('should reject title exceeding 100 characters', async () => {
      await setupWithFile();

      const longTitle = 'a'.repeat(101);
      const titleInput = screen.getByLabelText(/title/i) as HTMLInputElement;

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

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

      await waitFor(() => {
        expect(screen.getByText(/title must be less than 100/i)).toBeInTheDocument();
      });
    });

    it('should require description (max 5000 chars)', async () => {
      await setupWithFile();

      const descInput = screen.getByLabelText(/description/i) as HTMLTextAreaElement;

      // Leave empty
      fireEvent.change(descInput, { target: { value: '' } });

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

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

    it('should reject description exceeding 5000 characters', async () => {
      await setupWithFile();

      const longDesc = 'a'.repeat(5001);
      const descInput = screen.getByLabelText(/description/i) as HTMLTextAreaElement;

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

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

      await waitFor(() => {
        expect(screen.getByText(/description must be less than 5000/i)).toBeInTheDocument();
      });
    });

    it('should support tag management (max 10 tags)', async () => {
      await setupWithFile();

      const tagInput = screen.getByPlaceholderText(/add tag/i) as HTMLInputElement;
      const addButton = screen.getByText(/add tag/i).closest('button');

      // Add first tag
      fireEvent.change(tagInput, { target: { value: 'javascript' } });
      fireEvent.click(addButton!);

      await waitFor(() => {
        expect(screen.getByText('javascript')).toBeInTheDocument();
      });

      // Add second tag
      fireEvent.change(tagInput, { target: { value: 'react' } });
      fireEvent.click(addButton!);

      expect(screen.getByText('react')).toBeInTheDocument();

      // Remove first tag
      const removeButtons = screen.getAllByLabelText(/remove tag/i);
      fireEvent.click(removeButtons[0]);

      await waitFor(() => {
        expect(screen.queryByText('javascript')).not.toBeInTheDocument();
      });
    });

    it('should limit to 10 tags maximum', async () => {
      await setupWithFile();

      const tagInput = screen.getByPlaceholderText(/add tag/i) as HTMLInputElement;
      const addButton = screen.getByText(/add tag/i).closest('button');

      // Add 10 tags
      for (let i = 1; i <= 10; i++) {
        fireEvent.change(tagInput, { target: { value: `tag${i}` } });
        fireEvent.click(addButton!);
      }

      // Try to add 11th tag
      fireEvent.change(tagInput, { target: { value: 'tag11' } });
      fireEvent.click(addButton!);

      await waitFor(() => {
        expect(screen.queryByText('tag11')).not.toBeInTheDocument();
      });

      // Should still have 10 tags
      const tags = screen.getAllByText(/tag\d+/);
      expect(tags).toHaveLength(10);
    });

    it('should support thumbnail upload (optional)', async () => {
      await setupWithFile();

      const thumbnailInput = screen.getByLabelText(/thumbnail/i) as HTMLInputElement;
      const thumbnailFile = new File(['image'], 'thumb.jpg', { type: 'image/jpeg' });

      fireEvent.change(thumbnailInput, { target: { files: [thumbnailFile] } });

      await waitFor(() => {
        expect(global.URL.createObjectURL).toHaveBeenCalledWith(thumbnailFile);
      });
    });

    it('should reject non-image files for thumbnail', async () => {
      await setupWithFile();

      const thumbnailInput = screen.getByLabelText(/thumbnail/i) as HTMLInputElement;
      const invalidFile = new File(['text'], 'file.txt', { type: 'text/plain' });

      fireEvent.change(thumbnailInput, { target: { files: [invalidFile] } });

      await waitFor(() => {
        expect(screen.getByText(/please select an image/i)).toBeInTheDocument();
      });
    });
  });

  // ==========================================================================
  // STEP 3: Upload Flow
  // ==========================================================================

  describe('Step 3: Upload Flow', () => {
    const setupReadyToUpload = async () => {
      render(
        <VideoUploadForm
          courseId="course-123"
          lessonId="lesson-456"
          onUploadComplete={mockOnComplete}
        />
      );

      // Step 1: Select file
      const file = new File(['video content'], '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(/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: 'Test Video Title' } });
      fireEvent.change(descInput, { target: { value: 'Test video description' } });

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

    it('should show progress during upload (0% to 100%)', async () => {
      await setupReadyToUpload();

      // Mock upload service with progress callbacks
      (videoUploadService.uploadVideo as vi.Mock).mockImplementation(
        async (file, options, progressCallback) => {
          // Simulate progress
          progressCallback(0, 'uploading', 'Starting upload...');
          await new Promise(resolve => setTimeout(resolve, 100));
          progressCallback(50, 'uploading', 'Uploading...');
          await new Promise(resolve => setTimeout(resolve, 100));
          progressCallback(100, 'completed', 'Upload complete!');

          return { id: 'video-123', url: 'https://cdn.example.com/video-123' };
        }
      );

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

      // Check progress updates
      await waitFor(() => {
        expect(screen.getByText(/starting upload/i)).toBeInTheDocument();
      });

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

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

    it('should invoke callback on successful upload', async () => {
      await setupReadyToUpload();

      (videoUploadService.uploadVideo as vi.Mock).mockResolvedValue({
        id: 'video-123',
        url: 'https://cdn.example.com/video-123',
      });

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

      await waitFor(() => {
        expect(mockOnComplete).toHaveBeenCalledWith(
          'video-123',
          expect.objectContaining({
            title: 'Test Video Title',
            description: 'Test video description',
          })
        );
      });
    });

    it('should handle upload errors gracefully', async () => {
      await setupReadyToUpload();

      const error = new Error('Network error');
      (videoUploadService.uploadVideo as vi.Mock).mockRejectedValue(error);

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

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

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

    it('should show retry option on upload failure', async () => {
      await setupReadyToUpload();

      (videoUploadService.uploadVideo as vi.Mock).mockRejectedValueOnce(
        new Error('Network error')
      );

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

      await waitFor(() => {
        expect(screen.getByText(/retry|try again/i)).toBeInTheDocument();
      });

      // Mock successful retry
      (videoUploadService.uploadVideo as vi.Mock).mockResolvedValueOnce({
        id: 'video-123',
        url: 'https://cdn.example.com/video-123',
      });

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

      await waitFor(() => {
        expect(mockOnComplete).toHaveBeenCalled();
      });
    });

    it('should disable form during upload', async () => {
      await setupReadyToUpload();

      (videoUploadService.uploadVideo as vi.Mock).mockImplementation(
        () => new Promise(() => {}) // Never resolves
      );

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

      await waitFor(() => {
        expect(uploadButton).toBeDisabled();
      });
    });
  });
});
