import { describe, it, expect, vi, beforeEach } from 'vitest'
import {
  validateFile,
  uploadFile,
  deleteFile,
  formatFileSize,
  moveFilesFromTempToWorkout,
  cleanupTempFiles,
  UPLOAD_CONFIGS,
} from './storage'
import { supabase } from '@/lib/supabase'

// Mock Supabase
vi.mock('@/lib/supabase', () => ({
  supabase: {
    storage: {
      from: vi.fn(() => ({
        upload: vi.fn(),
        remove: vi.fn(),
        move: vi.fn(),
        list: vi.fn(),
        getPublicUrl: vi.fn(() => ({ data: { publicUrl: 'https://example.com/test.jpg' } })),
      })),
    },
  },
}))

describe('storage utils', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  describe('validateFile', () => {
    it('should validate correct file', () => {
      const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' })
      Object.defineProperty(file, 'size', { value: 1024 * 1024 }) // 1MB

      const result = validateFile(file, UPLOAD_CONFIGS.thumbnail)
      expect(result.valid).toBe(true)
    })

    it('should reject file too large', () => {
      const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' })
      Object.defineProperty(file, 'size', { value: 20 * 1024 * 1024 }) // 20MB > 10MB limit

      const result = validateFile(file, UPLOAD_CONFIGS.thumbnail)
      expect(result.valid).toBe(false)
      if (!result.valid) {
        expect(result.error.code).toBe('SIZE_EXCEEDED')
        expect(result.error.message).toContain('10 MB')
      }
    })

    it('should reject invalid file type', () => {
      const file = new File(['test'], 'test.gif', { type: 'image/gif' })
      Object.defineProperty(file, 'size', { value: 1024 })

      const result = validateFile(file, UPLOAD_CONFIGS.thumbnail)
      expect(result.valid).toBe(false)
      if (!result.valid) {
        expect(result.error.code).toBe('INVALID_TYPE')
      }
    })

    it('should validate video file', () => {
      const file = new File(['test'], 'test.mp4', { type: 'video/mp4' })
      Object.defineProperty(file, 'size', { value: 100 * 1024 * 1024 }) // 100MB < 500MB

      const result = validateFile(file, UPLOAD_CONFIGS.video)
      expect(result.valid).toBe(true)
    })

    it('should validate avatar file', () => {
      const file = new File(['test'], 'test.png', { type: 'image/png' })
      Object.defineProperty(file, 'size', { value: 2 * 1024 * 1024 }) // 2MB < 5MB

      const result = validateFile(file, UPLOAD_CONFIGS.avatar)
      expect(result.valid).toBe(true)
    })

    it('should reject avatar file too large', () => {
      const file = new File(['test'], 'test.png', { type: 'image/png' })
      Object.defineProperty(file, 'size', { value: 10 * 1024 * 1024 }) // 10MB > 5MB

      const result = validateFile(file, UPLOAD_CONFIGS.avatar)
      expect(result.valid).toBe(false)
      if (!result.valid) {
        expect(result.error.message).toContain('5 MB')
      }
    })
  })

  describe('formatFileSize', () => {
    it('should format bytes', () => {
      expect(formatFileSize(0)).toBe('0 Bytes')
      expect(formatFileSize(1024)).toBe('1 KB')
      expect(formatFileSize(1024 * 1024)).toBe('1 MB')
      expect(formatFileSize(1024 * 1024 * 1024)).toBe('1 GB')
    })

    it('should format with decimals', () => {
      expect(formatFileSize(1536)).toBe('1.5 KB')
      expect(formatFileSize(1572864)).toBe('1.5 MB')
    })

    it('should handle edge cases', () => {
      expect(formatFileSize(1)).toBe('1 Bytes')
      expect(formatFileSize(1023)).toBe('1023 Bytes')
      expect(formatFileSize(1025)).toBe('1 KB')
    })
  })

  describe('uploadFile', () => {
    it('should upload file successfully', async () => {
      const mockUpload = vi.fn().mockResolvedValue({ data: { path: 'workout-123/123.jpg' }, error: null })
      const mockGetPublicUrl = vi.fn().mockReturnValue({ data: { publicUrl: 'https://example.com/workout-123/123.jpg' } })

      vi.mocked(supabase.storage.from).mockReturnValue({
        upload: mockUpload,
        getPublicUrl: mockGetPublicUrl,
        remove: vi.fn(),
        move: vi.fn(),
        list: vi.fn(),
      } as any)

      const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' })
      Object.defineProperty(file, 'size', { value: 1024 })

      const result = await uploadFile(file, UPLOAD_CONFIGS.thumbnail, 'workout-123')

      expect(result.url).toBe('https://example.com/workout-123/123.jpg')
      expect(result.path).toContain('workout-123/')
      expect(mockUpload).toHaveBeenCalledWith(
        expect.stringContaining('workout-123/'),
        file,
        expect.objectContaining({ cacheControl: '3600', upsert: false })
      )
    })

    it('should throw error on upload failure', async () => {
      const mockUpload = vi.fn().mockResolvedValue({ data: null, error: { message: 'Upload failed' } })

      vi.mocked(supabase.storage.from).mockReturnValue({
        upload: mockUpload,
        getPublicUrl: vi.fn(),
        remove: vi.fn(),
        move: vi.fn(),
        list: vi.fn(),
      } as any)

      const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' })
      Object.defineProperty(file, 'size', { value: 1024 })

      await expect(uploadFile(file, UPLOAD_CONFIGS.thumbnail, 'workout-123')).rejects.toMatchObject({
        code: 'UPLOAD_FAILED',
        message: 'Upload failed',
      })
    })

    it('should validate file before upload', async () => {
      const file = new File(['test'], 'test.gif', { type: 'image/gif' })
      Object.defineProperty(file, 'size', { value: 1024 })

      await expect(uploadFile(file, UPLOAD_CONFIGS.thumbnail, 'workout-123')).rejects.toMatchObject({
        code: 'INVALID_TYPE',
      })
    })

    it('should validate file size before upload', async () => {
      const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' })
      Object.defineProperty(file, 'size', { value: 20 * 1024 * 1024 })

      await expect(uploadFile(file, UPLOAD_CONFIGS.thumbnail, 'workout-123')).rejects.toMatchObject({
        code: 'SIZE_EXCEEDED',
      })
    })

    it('should call onProgress callback', async () => {
      const mockUpload = vi.fn().mockResolvedValue({ data: { path: 'test.jpg' }, error: null })
      const mockGetPublicUrl = vi.fn().mockReturnValue({ data: { publicUrl: 'https://example.com/test.jpg' } })

      vi.mocked(supabase.storage.from).mockReturnValue({
        upload: mockUpload,
        getPublicUrl: mockGetPublicUrl,
        remove: vi.fn(),
        move: vi.fn(),
        list: vi.fn(),
      } as any)

      const onProgress = vi.fn()
      const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' })
      Object.defineProperty(file, 'size', { value: 1024 })

      await uploadFile(file, UPLOAD_CONFIGS.thumbnail, 'workout-123', onProgress)

      // Note: uploadFile doesn't currently use onProgress, but we test it's passed
      expect(mockUpload).toHaveBeenCalled()
    })
  })

  describe('deleteFile', () => {
    it('should delete file successfully', async () => {
      const mockRemove = vi.fn().mockResolvedValue({ error: null })

      vi.mocked(supabase.storage.from).mockReturnValue({
        remove: mockRemove,
        upload: vi.fn(),
        move: vi.fn(),
        list: vi.fn(),
        getPublicUrl: vi.fn(),
      } as any)

      await deleteFile('workout-videos', 'test/video.mp4')

      expect(mockRemove).toHaveBeenCalledWith(['test/video.mp4'])
    })

    it('should throw error on delete failure', async () => {
      const mockRemove = vi.fn().mockResolvedValue({ error: { message: 'Delete failed' } })

      vi.mocked(supabase.storage.from).mockReturnValue({
        remove: mockRemove,
        upload: vi.fn(),
        move: vi.fn(),
        list: vi.fn(),
        getPublicUrl: vi.fn(),
      } as any)

      await expect(deleteFile('workout-videos', 'test/video.mp4')).rejects.toThrow('Failed to delete file: Delete failed')
    })
  })

  describe('moveFilesFromTempToWorkout', () => {
    it('should move thumbnail from temp to workout folder', async () => {
      const mockMove = vi.fn().mockResolvedValue({ error: null })
      const mockGetPublicUrl = vi.fn().mockReturnValue({ data: { publicUrl: 'https://example.com/workout-123/thumb.jpg' } })

      vi.mocked(supabase.storage.from).mockReturnValue({
        move: mockMove,
        getPublicUrl: mockGetPublicUrl,
        remove: vi.fn(),
        list: vi.fn(),
        upload: vi.fn(),
      } as any)

      const tempUrl = 'https://supabase.1000co.fr/storage/v1/object/public/workout-thumbnails/temp/123.jpg'
      
      const result = await moveFilesFromTempToWorkout('temp', 'workout-123', tempUrl)

      expect(result.errors).toHaveLength(0)
      expect(result.thumbnail).toBeDefined()
      expect(result.thumbnail?.newUrl).toBe('https://example.com/workout-123/thumb.jpg')
      expect(mockMove).toHaveBeenCalledWith('temp/123.jpg', 'workout-123/123.jpg')
    })

    it('should move video from temp to workout folder', async () => {
      const mockMove = vi.fn().mockResolvedValue({ error: null })
      const mockGetPublicUrl = vi.fn().mockReturnValue({ data: { publicUrl: 'https://example.com/workout-123/video.mp4' } })

      vi.mocked(supabase.storage.from).mockReturnValue({
        move: mockMove,
        getPublicUrl: mockGetPublicUrl,
        remove: vi.fn(),
        list: vi.fn(),
        upload: vi.fn(),
      } as any)

      const tempUrl = 'https://supabase.1000co.fr/storage/v1/object/public/workout-videos/temp/video.mp4'
      
      const result = await moveFilesFromTempToWorkout('temp', 'workout-123', undefined, tempUrl)

      expect(result.errors).toHaveLength(0)
      expect(result.video).toBeDefined()
      expect(result.video?.newUrl).toBe('https://example.com/workout-123/video.mp4')
    })

    it('should handle move errors gracefully', async () => {
      const mockMove = vi.fn().mockResolvedValue({ error: { message: 'Move failed' } })

      vi.mocked(supabase.storage.from).mockReturnValue({
        move: mockMove,
        getPublicUrl: vi.fn(),
        remove: vi.fn(),
        list: vi.fn(),
        upload: vi.fn(),
      } as any)

      const tempUrl = 'https://supabase.1000co.fr/storage/v1/object/public/workout-thumbnails/temp/123.jpg'
      
      const result = await moveFilesFromTempToWorkout('temp', 'workout-123', tempUrl)

      expect(result.errors).toHaveLength(1)
      expect(result.errors[0]).toContain('Failed to move thumbnail')
      expect(result.thumbnail).toBeUndefined()
    })

    it('should skip files not in temp folder', async () => {
      const mockMove = vi.fn()

      vi.mocked(supabase.storage.from).mockReturnValue({
        move: mockMove,
        getPublicUrl: vi.fn(),
        remove: vi.fn(),
        list: vi.fn(),
        upload: vi.fn(),
      } as any)

      const nonTempUrl = 'https://supabase.1000co.fr/storage/v1/object/public/workout-thumbnails/other/123.jpg'
      
      const result = await moveFilesFromTempToWorkout('temp', 'workout-123', nonTempUrl)

      expect(mockMove).not.toHaveBeenCalled()
      expect(result.thumbnail).toBeUndefined()
      expect(result.errors).toHaveLength(0)
    })

    it('should move both thumbnail and video', async () => {
      const mockMove = vi.fn().mockResolvedValue({ error: null })
      const mockGetPublicUrl = vi.fn()
        .mockReturnValueOnce({ data: { publicUrl: 'https://example.com/thumb.jpg' } })
        .mockReturnValueOnce({ data: { publicUrl: 'https://example.com/video.mp4' } })

      vi.mocked(supabase.storage.from).mockReturnValue({
        move: mockMove,
        getPublicUrl: mockGetPublicUrl,
        remove: vi.fn(),
        list: vi.fn(),
        upload: vi.fn(),
      } as any)

      const thumbUrl = 'https://supabase.1000co.fr/storage/v1/object/public/workout-thumbnails/temp/thumb.jpg'
      const videoUrl = 'https://supabase.1000co.fr/storage/v1/object/public/workout-videos/temp/video.mp4'
      
      const result = await moveFilesFromTempToWorkout('temp', 'workout-123', thumbUrl, videoUrl)

      expect(result.thumbnail).toBeDefined()
      expect(result.video).toBeDefined()
      expect(mockMove).toHaveBeenCalledTimes(2)
    })

    it('should handle invalid URLs gracefully', async () => {
      const mockMove = vi.fn()

      vi.mocked(supabase.storage.from).mockReturnValue({
        move: mockMove,
        getPublicUrl: vi.fn(),
        remove: vi.fn(),
        list: vi.fn(),
        upload: vi.fn(),
      } as any)

      const invalidUrl = 'not-a-valid-url'
      
      const result = await moveFilesFromTempToWorkout('temp', 'workout-123', invalidUrl)

      expect(mockMove).not.toHaveBeenCalled()
      expect(result.thumbnail).toBeUndefined()
    })

    it('should handle empty URLs', async () => {
      const result = await moveFilesFromTempToWorkout('temp', 'workout-123')

      expect(result.thumbnail).toBeUndefined()
      expect(result.video).toBeUndefined()
      expect(result.errors).toHaveLength(0)
    })

    it('should handle exception during move', async () => {
      const mockMove = vi.fn().mockRejectedValue(new Error('Network error'))

      vi.mocked(supabase.storage.from).mockReturnValue({
        move: mockMove,
        getPublicUrl: vi.fn(),
        remove: vi.fn(),
        list: vi.fn(),
        upload: vi.fn(),
      } as any)

      const tempUrl = 'https://supabase.1000co.fr/storage/v1/object/public/workout-thumbnails/temp/123.jpg'
      
      const result = await moveFilesFromTempToWorkout('temp', 'workout-123', tempUrl)

      expect(result.errors).toHaveLength(1)
      expect(result.errors[0]).toContain('Failed to move thumbnail')
    })
  })

  describe('cleanupTempFiles', () => {
    it('should delete temp files from all buckets', async () => {
      const mockList = vi.fn()
        .mockResolvedValueOnce({ 
          data: [{ name: 'old1.jpg' }, { name: 'old2.jpg' }], 
          error: null 
        })
        .mockResolvedValueOnce({ 
          data: [{ name: 'video1.mp4' }], 
          error: null 
        })

      const mockRemove = vi.fn().mockResolvedValue({ error: null })

      vi.mocked(supabase.storage.from).mockReturnValue({
        list: mockList,
        remove: mockRemove,
        move: vi.fn(),
        upload: vi.fn(),
        getPublicUrl: vi.fn(),
      } as any)

      const result = await cleanupTempFiles()

      expect(result.deleted).toBe(3)
      expect(result.errors).toHaveLength(0)
      expect(mockRemove).toHaveBeenCalledWith(['temp/old1.jpg', 'temp/old2.jpg'])
      expect(mockRemove).toHaveBeenCalledWith(['temp/video1.mp4'])
    })

    it('should handle list errors', async () => {
      const mockList = vi.fn().mockResolvedValue({ 
        data: null, 
        error: { message: 'List failed' } 
      })

      vi.mocked(supabase.storage.from).mockReturnValue({
        list: mockList,
        remove: vi.fn(),
        move: vi.fn(),
        upload: vi.fn(),
        getPublicUrl: vi.fn(),
      } as any)

      const result = await cleanupTempFiles()

      expect(result.deleted).toBe(0)
      expect(result.errors.length).toBeGreaterThan(0)
    })

    it('should handle delete errors', async () => {
      const mockList = vi.fn()
        .mockResolvedValueOnce({ 
          data: [{ name: 'old1.jpg' }], 
          error: null 
        })
        .mockResolvedValueOnce({ 
          data: [], 
          error: null 
        })

      const mockRemove = vi.fn().mockResolvedValue({ error: { message: 'Delete failed' } })

      vi.mocked(supabase.storage.from).mockReturnValue({
        list: mockList,
        remove: mockRemove,
        move: vi.fn(),
        upload: vi.fn(),
        getPublicUrl: vi.fn(),
      } as any)

      const result = await cleanupTempFiles()

      expect(result.deleted).toBe(0)
      expect(result.errors.length).toBeGreaterThan(0)
    })

    it('should handle empty temp folders', async () => {
      const mockList = vi.fn().mockResolvedValue({ 
        data: [], 
        error: null 
      })

      vi.mocked(supabase.storage.from).mockReturnValue({
        list: mockList,
        remove: vi.fn(),
        move: vi.fn(),
        upload: vi.fn(),
        getPublicUrl: vi.fn(),
      } as any)

      const result = await cleanupTempFiles()

      expect(result.deleted).toBe(0)
      expect(result.errors).toHaveLength(0)
    })

    it('should handle exception during cleanup', async () => {
      const mockList = vi.fn().mockRejectedValue(new Error('Network error'))

      vi.mocked(supabase.storage.from).mockReturnValue({
        list: mockList,
        remove: vi.fn(),
        move: vi.fn(),
        upload: vi.fn(),
        getPublicUrl: vi.fn(),
      } as any)

      const result = await cleanupTempFiles()

      expect(result.deleted).toBe(0)
      expect(result.errors.length).toBeGreaterThan(0)
    })

    it('should process both buckets even if one fails', async () => {
      const mockList = vi.fn()
        .mockResolvedValueOnce({ 
          data: [{ name: 'file1.jpg' }], 
          error: null 
        })
        .mockResolvedValueOnce({ 
          data: null, 
          error: { message: 'Second bucket failed' } 
        })

      const mockRemove = vi.fn().mockResolvedValue({ error: null })

      vi.mocked(supabase.storage.from).mockReturnValue({
        list: mockList,
        remove: mockRemove,
        move: vi.fn(),
        upload: vi.fn(),
        getPublicUrl: vi.fn(),
      } as any)

      const result = await cleanupTempFiles()

      expect(result.deleted).toBe(1)
      expect(result.errors.length).toBeGreaterThan(0)
    })
  })
})
