import type { FileEntity } from '../types'
import type { FileUploadConfigResponse } from '@/models/common'
import { describe, expect, it } from 'vitest'
import {
  DEFAULT_IMAGE_FILE_BATCH_LIMIT,
  DEFAULT_IMAGE_FILE_SIZE_LIMIT,
  DEFAULT_SINGLE_CHUNK_ATTACHMENT_LIMIT,
} from '../constants'
import { fileIsUploaded, getFileType, getFileUploadConfig, traverseFileEntry } from '../utils'

describe('image-uploader utils', () => {
  describe('getFileType', () => {
    it('should return file extension for a simple filename', () => {
      const file = { name: 'image.png' } as File
      expect(getFileType(file)).toBe('png')
    })

    it('should return file extension for filename with multiple dots', () => {
      const file = { name: 'my.photo.image.jpg' } as File
      expect(getFileType(file)).toBe('jpg')
    })

    it('should return empty string for null/undefined file', () => {
      expect(getFileType(null as unknown as File)).toBe('')
      expect(getFileType(undefined as unknown as File)).toBe('')
    })

    it('should return filename for file without extension', () => {
      const file = { name: 'README' } as File
      expect(getFileType(file)).toBe('README')
    })

    it('should handle various file extensions', () => {
      expect(getFileType({ name: 'doc.pdf' } as File)).toBe('pdf')
      expect(getFileType({ name: 'image.jpeg' } as File)).toBe('jpeg')
      expect(getFileType({ name: 'video.mp4' } as File)).toBe('mp4')
      expect(getFileType({ name: 'archive.tar.gz' } as File)).toBe('gz')
    })
  })

  describe('fileIsUploaded', () => {
    it('should return true when uploadedId is set', () => {
      const file = { uploadedId: 'some-id', progress: 50 } as Partial<FileEntity>
      expect(fileIsUploaded(file as FileEntity)).toBe(true)
    })

    it('should return true when progress is 100', () => {
      const file = { progress: 100 } as Partial<FileEntity>
      expect(fileIsUploaded(file as FileEntity)).toBe(true)
    })

    it('should return undefined when neither uploadedId nor 100 progress', () => {
      const file = { progress: 50 } as Partial<FileEntity>
      expect(fileIsUploaded(file as FileEntity)).toBeUndefined()
    })

    it('should return undefined when progress is 0', () => {
      const file = { progress: 0 } as Partial<FileEntity>
      expect(fileIsUploaded(file as FileEntity)).toBeUndefined()
    })

    it('should return true when uploadedId is empty string and progress is 100', () => {
      const file = { uploadedId: '', progress: 100 } as Partial<FileEntity>
      expect(fileIsUploaded(file as FileEntity)).toBe(true)
    })
  })

  describe('getFileUploadConfig', () => {
    it('should return default values when response is undefined', () => {
      const result = getFileUploadConfig(undefined)
      expect(result).toEqual({
        imageFileSizeLimit: DEFAULT_IMAGE_FILE_SIZE_LIMIT,
        imageFileBatchLimit: DEFAULT_IMAGE_FILE_BATCH_LIMIT,
        singleChunkAttachmentLimit: DEFAULT_SINGLE_CHUNK_ATTACHMENT_LIMIT,
      })
    })

    it('should return values from response when valid', () => {
      const response: Partial<FileUploadConfigResponse> = {
        image_file_batch_limit: 20,
        single_chunk_attachment_limit: 10,
        attachment_image_file_size_limit: 5,
      }

      const result = getFileUploadConfig(response as FileUploadConfigResponse)
      expect(result).toEqual({
        imageFileSizeLimit: 5,
        imageFileBatchLimit: 20,
        singleChunkAttachmentLimit: 10,
      })
    })

    it('should use default values when response values are 0', () => {
      const response: Partial<FileUploadConfigResponse> = {
        image_file_batch_limit: 0,
        single_chunk_attachment_limit: 0,
        attachment_image_file_size_limit: 0,
      }

      const result = getFileUploadConfig(response as FileUploadConfigResponse)
      expect(result).toEqual({
        imageFileSizeLimit: DEFAULT_IMAGE_FILE_SIZE_LIMIT,
        imageFileBatchLimit: DEFAULT_IMAGE_FILE_BATCH_LIMIT,
        singleChunkAttachmentLimit: DEFAULT_SINGLE_CHUNK_ATTACHMENT_LIMIT,
      })
    })

    it('should use default values when response values are negative', () => {
      const response: Partial<FileUploadConfigResponse> = {
        image_file_batch_limit: -5,
        single_chunk_attachment_limit: -10,
        attachment_image_file_size_limit: -1,
      }

      const result = getFileUploadConfig(response as FileUploadConfigResponse)
      expect(result).toEqual({
        imageFileSizeLimit: DEFAULT_IMAGE_FILE_SIZE_LIMIT,
        imageFileBatchLimit: DEFAULT_IMAGE_FILE_BATCH_LIMIT,
        singleChunkAttachmentLimit: DEFAULT_SINGLE_CHUNK_ATTACHMENT_LIMIT,
      })
    })

    it('should handle string values in response', () => {
      const response = {
        image_file_batch_limit: '15',
        single_chunk_attachment_limit: '8',
        attachment_image_file_size_limit: '3',
      } as unknown as FileUploadConfigResponse

      const result = getFileUploadConfig(response)
      expect(result).toEqual({
        imageFileSizeLimit: 3,
        imageFileBatchLimit: 15,
        singleChunkAttachmentLimit: 8,
      })
    })

    it('should handle null values in response', () => {
      const response = {
        image_file_batch_limit: null,
        single_chunk_attachment_limit: null,
        attachment_image_file_size_limit: null,
      } as unknown as FileUploadConfigResponse

      const result = getFileUploadConfig(response)
      expect(result).toEqual({
        imageFileSizeLimit: DEFAULT_IMAGE_FILE_SIZE_LIMIT,
        imageFileBatchLimit: DEFAULT_IMAGE_FILE_BATCH_LIMIT,
        singleChunkAttachmentLimit: DEFAULT_SINGLE_CHUNK_ATTACHMENT_LIMIT,
      })
    })

    it('should handle undefined values in response', () => {
      const response = {
        image_file_batch_limit: undefined,
        single_chunk_attachment_limit: undefined,
        attachment_image_file_size_limit: undefined,
      } as unknown as FileUploadConfigResponse

      const result = getFileUploadConfig(response)
      expect(result).toEqual({
        imageFileSizeLimit: DEFAULT_IMAGE_FILE_SIZE_LIMIT,
        imageFileBatchLimit: DEFAULT_IMAGE_FILE_BATCH_LIMIT,
        singleChunkAttachmentLimit: DEFAULT_SINGLE_CHUNK_ATTACHMENT_LIMIT,
      })
    })

    it('should handle partial response', () => {
      const response: Partial<FileUploadConfigResponse> = {
        image_file_batch_limit: 25,
      }

      const result = getFileUploadConfig(response as FileUploadConfigResponse)
      expect(result.imageFileBatchLimit).toBe(25)
      expect(result.imageFileSizeLimit).toBe(DEFAULT_IMAGE_FILE_SIZE_LIMIT)
      expect(result.singleChunkAttachmentLimit).toBe(DEFAULT_SINGLE_CHUNK_ATTACHMENT_LIMIT)
    })

    it('should handle non-number non-string values (object, boolean, etc) with default fallback', () => {
      // This tests the getNumberValue function's final return 0 case
      // When value is neither number nor string (e.g., object, boolean, array)
      const response = {
        image_file_batch_limit: { invalid: 'object' }, // Object - not number or string
        single_chunk_attachment_limit: true, // Boolean - not number or string
        attachment_image_file_size_limit: ['array'], // Array - not number or string
      } as unknown as FileUploadConfigResponse

      const result = getFileUploadConfig(response)
      // All should fall back to defaults since getNumberValue returns 0 for these types
      expect(result).toEqual({
        imageFileSizeLimit: DEFAULT_IMAGE_FILE_SIZE_LIMIT,
        imageFileBatchLimit: DEFAULT_IMAGE_FILE_BATCH_LIMIT,
        singleChunkAttachmentLimit: DEFAULT_SINGLE_CHUNK_ATTACHMENT_LIMIT,
      })
    })

    it('should handle NaN string values', () => {
      const response = {
        image_file_batch_limit: 'not-a-number',
        single_chunk_attachment_limit: '',
        attachment_image_file_size_limit: 'abc',
      } as unknown as FileUploadConfigResponse

      const result = getFileUploadConfig(response)
      // NaN values should result in defaults (since NaN > 0 is false)
      expect(result).toEqual({
        imageFileSizeLimit: DEFAULT_IMAGE_FILE_SIZE_LIMIT,
        imageFileBatchLimit: DEFAULT_IMAGE_FILE_BATCH_LIMIT,
        singleChunkAttachmentLimit: DEFAULT_SINGLE_CHUNK_ATTACHMENT_LIMIT,
      })
    })
  })

  describe('traverseFileEntry', () => {
    type MockFile = { name: string, relativePath?: string }
    type FileCallback = (file: MockFile) => void
    type EntriesCallback = (entries: FileSystemEntry[]) => void

    // Helper to create mock FileSystemEntry with required properties
    const createMockEntry = (props: {
      isFile: boolean
      isDirectory: boolean
      name?: string
      file?: (callback: FileCallback) => void
      createReader?: () => { readEntries: (callback: EntriesCallback) => void }
    }): FileSystemEntry => props as unknown as FileSystemEntry

    it('should resolve with file array for file entry', async () => {
      const mockFile: MockFile = { name: 'test.png' }
      const mockEntry = createMockEntry({
        isFile: true,
        isDirectory: false,
        file: (callback: FileCallback) => callback(mockFile),
      })

      const result = await traverseFileEntry(mockEntry)
      expect(result).toHaveLength(1)
      expect(result[0]!.name).toBe('test.png')
      expect(result[0]!.relativePath).toBe('test.png')
    })

    it('should resolve with file array with prefix for nested file', async () => {
      const mockFile: MockFile = { name: 'test.png' }
      const mockEntry = createMockEntry({
        isFile: true,
        isDirectory: false,
        file: (callback: FileCallback) => callback(mockFile),
      })

      const result = await traverseFileEntry(mockEntry, 'folder/')
      expect(result).toHaveLength(1)
      expect(result[0]!.relativePath).toBe('folder/test.png')
    })

    it('should resolve empty array for unknown entry type', async () => {
      const mockEntry = createMockEntry({
        isFile: false,
        isDirectory: false,
      })

      const result = await traverseFileEntry(mockEntry)
      expect(result).toEqual([])
    })

    it('should handle directory with no files', async () => {
      const mockEntry = createMockEntry({
        isFile: false,
        isDirectory: true,
        name: 'empty-folder',
        createReader: () => ({
          readEntries: (callback: EntriesCallback) => callback([]),
        }),
      })

      const result = await traverseFileEntry(mockEntry)
      expect(result).toEqual([])
    })

    it('should handle directory with files', async () => {
      const mockFile1: MockFile = { name: 'file1.png' }
      const mockFile2: MockFile = { name: 'file2.png' }

      const mockFileEntry1 = createMockEntry({
        isFile: true,
        isDirectory: false,
        file: (callback: FileCallback) => callback(mockFile1),
      })

      const mockFileEntry2 = createMockEntry({
        isFile: true,
        isDirectory: false,
        file: (callback: FileCallback) => callback(mockFile2),
      })

      let readCount = 0
      const mockEntry = createMockEntry({
        isFile: false,
        isDirectory: true,
        name: 'folder',
        createReader: () => ({
          readEntries: (callback: EntriesCallback) => {
            if (readCount === 0) {
              readCount++
              callback([mockFileEntry1, mockFileEntry2])
            }
            else {
              callback([])
            }
          },
        }),
      })

      const result = await traverseFileEntry(mockEntry)
      expect(result).toHaveLength(2)
      expect(result[0]!.relativePath).toBe('folder/file1.png')
      expect(result[1]!.relativePath).toBe('folder/file2.png')
    })
  })
})
