import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen, waitFor, fireEvent } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { MediaUpload } from './media-upload'
import * as storage from '@/lib/storage'

// Mock storage module
vi.mock('@/lib/storage', () => ({
  uploadFile: vi.fn(),
  formatFileSize: vi.fn((bytes) => `${bytes} bytes`),
  UPLOAD_CONFIGS: {
    video: {
      bucket: 'workout-videos',
      maxSize: 500 * 1024 * 1024,
      allowedTypes: ['video/mp4', 'video/webm', 'video/quicktime'],
    },
    thumbnail: {
      bucket: 'workout-thumbnails',
      maxSize: 10 * 1024 * 1024,
      allowedTypes: ['image/jpeg', 'image/png', 'image/webp'],
    },
    avatar: {
      bucket: 'trainer-avatars',
      maxSize: 5 * 1024 * 1024,
      allowedTypes: ['image/jpeg', 'image/png', 'image/webp'],
    },
  },
}))

describe('MediaUpload', () => {
  const mockOnChange = vi.fn()

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

  describe('rendering', () => {
    it('should render upload area for thumbnail', () => {
      render(
        <MediaUpload
          type="thumbnail"
          onChange={mockOnChange}
        />
      )

      expect(screen.getByText(/click or drag file to upload/i)).toBeInTheDocument()
      expect(screen.getByText(/10 mb/i)).toBeInTheDocument()
    })

    it('should render upload area for video', () => {
      render(
        <MediaUpload
          type="video"
          onChange={mockOnChange}
        />
      )

      expect(screen.getByText(/click or drag file to upload/i)).toBeInTheDocument()
      expect(screen.getByText(/500 mb/i)).toBeInTheDocument()
    })

    it('should render upload area for avatar', () => {
      render(
        <MediaUpload
          type="avatar"
          onChange={mockOnChange}
        />
      )

      expect(screen.getByText(/click or drag file to upload/i)).toBeInTheDocument()
      expect(screen.getByText(/5 mb/i)).toBeInTheDocument()
    })

    it('should show image preview after successful upload', () => {
      render(
        <MediaUpload
          type="thumbnail"
          value="https://example.com/image.jpg"
          onChange={mockOnChange}
        />
      )

      const img = screen.getByRole('img')
      expect(img).toBeInTheDocument()
      expect(img).toHaveAttribute('src', 'https://example.com/image.jpg')
    })

    it('should show video preview after upload', () => {
      render(
        <MediaUpload
          type="video"
          value="https://example.com/video.mp4"
          onChange={mockOnChange}
        />
      )

      const video = document.querySelector('video')
      expect(video).toBeInTheDocument()
      expect(video).toHaveAttribute('src', 'https://example.com/video.mp4')
    })
  })

  describe('file upload', () => {
    it('should handle file selection via click', async () => {
      vi.mocked(storage.uploadFile).mockResolvedValue({
        url: 'https://example.com/image.jpg',
        path: 'temp/123.jpg',
      })

      render(
        <MediaUpload
          type="thumbnail"
          onChange={mockOnChange}
        />
      )

      const input = document.querySelector('input[type="file"]') as HTMLInputElement
      const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' })

      await userEvent.upload(input, file)

      await waitFor(() => {
        expect(storage.uploadFile).toHaveBeenCalled()
        expect(mockOnChange).toHaveBeenCalledWith('https://example.com/image.jpg')
      }, { timeout: 10000 })
    }, 15000)

    it('should show error for upload failure', async () => {
      vi.mocked(storage.uploadFile).mockRejectedValue({
        message: 'Upload failed',
        code: 'UPLOAD_FAILED',
      } as storage.UploadError)

      render(
        <MediaUpload
          type="thumbnail"
          onChange={mockOnChange}
        />
      )

      const input = document.querySelector('input[type="file"]') as HTMLInputElement
      const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' })

      await userEvent.upload(input, file)

      await waitFor(() => {
        // Error should be displayed (check for any error text in the error container)
        const errorContainer = document.querySelector('[class*="bg-red-500"]') || 
                               document.querySelector('[class*="text-red"]') ||
                               screen.queryByText(/failed/i) ||
                               screen.queryByText(/error/i)
        expect(errorContainer).toBeTruthy()
      }, { timeout: 5000 })
    }, 10000)

    it('should show error for file too large', async () => {
      vi.mocked(storage.uploadFile).mockRejectedValue({
        message: 'File too large. Maximum size is 10 MB.',
        code: 'SIZE_EXCEEDED',
      } as storage.UploadError)

      render(
        <MediaUpload
          type="thumbnail"
          onChange={mockOnChange}
        />
      )

      const input = document.querySelector('input[type="file"]') as HTMLInputElement
      const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' })

      await userEvent.upload(input, file)

      await waitFor(() => {
        const errorElement = screen.getByText(/file too large/i)
        expect(errorElement).toBeInTheDocument()
      }, { timeout: 5000 })
    }, 10000)

    it('should show upload progress', async () => {
      let resolveUpload: (value: { url: string; path: string }) => void
      const uploadPromise = new Promise<{ url: string; path: string }>((resolve) => {
        resolveUpload = resolve
      })
      
      vi.mocked(storage.uploadFile).mockReturnValue(uploadPromise)

      render(
        <MediaUpload
          type="thumbnail"
          onChange={mockOnChange}
        />
      )

      const input = document.querySelector('input[type="file"]') as HTMLInputElement
      const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' })

      await userEvent.upload(input, file)

      expect(screen.getByText(/uploading/i)).toBeInTheDocument()

      resolveUpload!({ url: 'https://example.com/image.jpg', path: 'temp/123.jpg' })

      await waitFor(() => {
        expect(mockOnChange).toHaveBeenCalledWith('https://example.com/image.jpg')
      }, { timeout: 5000 })
    }, 10000)

    it('should clear error when uploading new file', async () => {
      vi.mocked(storage.uploadFile)
        .mockRejectedValueOnce({
          message: 'First error',
          code: 'UPLOAD_FAILED',
        } as storage.UploadError)
        .mockResolvedValueOnce({
          url: 'https://example.com/image.jpg',
          path: 'temp/123.jpg',
        })

      render(
        <MediaUpload
          type="thumbnail"
          onChange={mockOnChange}
        />
      )

      const input = document.querySelector('input[type="file"]') as HTMLInputElement

      // First upload - error
      await userEvent.upload(input, new File(['test'], 'test1.jpg', { type: 'image/jpeg' }))
      await waitFor(() => {
        expect(screen.getByText(/first error/i)).toBeInTheDocument()
      })

      // Second upload - success, error should be cleared
      await userEvent.upload(input, new File(['test'], 'test2.jpg', { type: 'image/jpeg' }))
      await waitFor(() => {
        expect(screen.queryByText(/first error/i)).not.toBeInTheDocument()
      })
    }, 10000)
  })

  describe('drag and drop', () => {
    it('should highlight on drag over', () => {
      render(
        <MediaUpload
          type="thumbnail"
          onChange={mockOnChange}
        />
      )

      const dropZone = screen.getByText(/click or drag file to upload/i).closest('[class*="relative"]') as HTMLElement
      expect(dropZone).not.toHaveClass('border-orange-500')

      fireEvent.dragOver(dropZone)
      expect(dropZone).toHaveClass('border-orange-500')
    })

    it('should remove highlight on drag leave', () => {
      render(
        <MediaUpload
          type="thumbnail"
          onChange={mockOnChange}
        />
      )

      const dropZone = screen.getByText(/click or drag file to upload/i).closest('[class*="relative"]') as HTMLElement

      fireEvent.dragOver(dropZone)
      expect(dropZone).toHaveClass('border-orange-500')

      fireEvent.dragLeave(dropZone)
      expect(dropZone).not.toHaveClass('border-orange-500')
    })

    it('should handle file drop', async () => {
      vi.mocked(storage.uploadFile).mockResolvedValue({
        url: 'https://example.com/image.jpg',
        path: 'temp/123.jpg',
      })

      render(
        <MediaUpload
          type="thumbnail"
          onChange={mockOnChange}
        />
      )

      const dropZone = screen.getByText(/click or drag file to upload/i).closest('[class*="relative"]') as HTMLElement
      const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' })

      fireEvent.drop(dropZone, {
        dataTransfer: {
          files: [file],
        },
      })

      await waitFor(() => {
        expect(storage.uploadFile).toHaveBeenCalled()
      }, { timeout: 5000 })
    }, 10000)

    it('should not handle drop when disabled', () => {
      render(
        <MediaUpload
          type="thumbnail"
          onChange={mockOnChange}
          disabled
        />
      )

      const dropZone = screen.getByText(/click or drag file to upload/i).closest('[class*="relative"]') as HTMLElement
      const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' })

      fireEvent.drop(dropZone, {
        dataTransfer: {
          files: [file],
        },
      })

      expect(storage.uploadFile).not.toHaveBeenCalled()
    })

    it('should not highlight on drag over when disabled', () => {
      render(
        <MediaUpload
          type="thumbnail"
          onChange={mockOnChange}
          disabled
        />
      )

      const dropZone = screen.getByText(/click or drag file to upload/i).closest('[class*="relative"]') as HTMLElement

      fireEvent.dragOver(dropZone)
      expect(dropZone).not.toHaveClass('border-orange-500')
    })
  })

  describe('file removal', () => {
    it('should allow removing uploaded file', async () => {
      render(
        <MediaUpload
          type="thumbnail"
          value="https://example.com/image.jpg"
          onChange={mockOnChange}
        />
      )

      const removeButton = screen.getByRole('button', { name: /remove/i })
      await userEvent.click(removeButton)

      expect(mockOnChange).toHaveBeenCalledWith('')
    })

    it('should clear file input when removed', async () => {
      render(
        <MediaUpload
          type="thumbnail"
          value="https://example.com/image.jpg"
          onChange={mockOnChange}
        />
      )

      const removeButton = screen.getByRole('button', { name: /remove/i })
      await userEvent.click(removeButton)

      // Verify onChange was called with empty string
      expect(mockOnChange).toHaveBeenCalledWith('')
    })
  })

  describe('disabled state', () => {
    it('should be disabled when disabled prop is true', () => {
      render(
        <MediaUpload
          type="thumbnail"
          onChange={mockOnChange}
          disabled
        />
      )

      const dropZone = screen.getByText(/click or drag file to upload/i).closest('[class*="relative"]') as HTMLElement
      expect(dropZone).toHaveClass('cursor-not-allowed')
      expect(dropZone).toHaveClass('opacity-50')

      const input = document.querySelector('input[type="file"]') as HTMLInputElement
      expect(input).toBeDisabled()
    })

    it('should not show remove button when disabled with value', () => {
      render(
        <MediaUpload
          type="thumbnail"
          value="https://example.com/image.jpg"
          onChange={mockOnChange}
          disabled
        />
      )

      const removeButton = screen.getByRole('button', { name: /remove/i })
      expect(removeButton).toBeDisabled()
    })
  })

  describe('workoutId handling', () => {
    it('should use workoutId for file path', async () => {
      vi.mocked(storage.uploadFile).mockResolvedValue({
        url: 'https://example.com/image.jpg',
        path: 'workout-123/123.jpg',
      })

      render(
        <MediaUpload
          type="thumbnail"
          workoutId="workout-123"
          onChange={mockOnChange}
        />
      )

      const input = document.querySelector('input[type="file"]') as HTMLInputElement
      const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' })

      await userEvent.upload(input, file)

      await waitFor(() => {
        expect(storage.uploadFile).toHaveBeenCalled()
      }, { timeout: 10000 })
      
      // Check the third argument was 'workout-123'
      const call = vi.mocked(storage.uploadFile).mock.calls[0]
      expect(call[2]).toBe('workout-123')
    }, 15000)

    it('should use temp folder when no workoutId provided', async () => {
      vi.mocked(storage.uploadFile).mockResolvedValue({
        url: 'https://example.com/image.jpg',
        path: 'temp/123.jpg',
      })

      render(
        <MediaUpload
          type="thumbnail"
          onChange={mockOnChange}
        />
      )

      const input = document.querySelector('input[type="file"]') as HTMLInputElement
      const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' })

      await userEvent.upload(input, file)

      await waitFor(() => {
        expect(storage.uploadFile).toHaveBeenCalled()
      }, { timeout: 10000 })
      
      // Check the third argument was 'temp'
      const call = vi.mocked(storage.uploadFile).mock.calls[0]
      expect(call[2]).toBe('temp')
    }, 15000)
  })
})
