import mime from 'mime'
import { upload } from '@/service/base'
import {
  downloadFile,
  fileIsUploaded,
  fileUpload,
  getFileAppearanceType,
  getFileExtension,
  getFileNameFromUrl,
  getFilesInLogs,
  getProcessedFiles,
  getProcessedFilesFromResponse,
  getSupportFileExtensionList,
  getSupportFileType,
  isAllowedFileExtension,
} from './utils'
import { FileAppearanceTypeEnum } from './types'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
import { TransferMethod } from '@/types/app'
import { FILE_EXTS } from '../prompt-editor/constants'

jest.mock('mime', () => ({
  __esModule: true,
  default: {
    getAllExtensions: jest.fn(),
  },
}))

jest.mock('@/service/base', () => ({
  upload: jest.fn(),
}))

describe('file-uploader utils', () => {
  beforeEach(() => {
    jest.clearAllMocks()
  })

  describe('fileUpload', () => {
    it('should handle successful file upload', async () => {
      const mockFile = new File(['test'], 'test.txt')
      const mockCallbacks = {
        onProgressCallback: jest.fn(),
        onSuccessCallback: jest.fn(),
        onErrorCallback: jest.fn(),
      }

      jest.mocked(upload).mockResolvedValue({ id: '123' })

      await fileUpload({
        file: mockFile,
        ...mockCallbacks,
      })

      expect(upload).toHaveBeenCalled()
      expect(mockCallbacks.onSuccessCallback).toHaveBeenCalledWith({ id: '123' })
    })
  })

  describe('getFileExtension', () => {
    it('should get extension from mimetype', () => {
      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pdf']))
      expect(getFileExtension('file', 'application/pdf')).toBe('pdf')
    })

    it('should get extension from mimetype and file name 1', () => {
      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pdf']))
      expect(getFileExtension('file.pdf', 'application/pdf')).toBe('pdf')
    })

    it('should get extension from mimetype with multiple ext candidates with filename hint', () => {
      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['der', 'crt', 'pem']))
      expect(getFileExtension('file.pem', 'application/x-x509-ca-cert')).toBe('pem')
    })

    it('should get extension from mimetype with multiple ext candidates without filename hint', () => {
      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['der', 'crt', 'pem']))
      expect(getFileExtension('file', 'application/x-x509-ca-cert')).toBe('der')
    })

    it('should get extension from filename if mimetype fails', () => {
      jest.mocked(mime.getAllExtensions).mockReturnValue(null)
      expect(getFileExtension('file.txt', '')).toBe('txt')
      expect(getFileExtension('file.txt.docx', '')).toBe('docx')
      expect(getFileExtension('file', '')).toBe('')
    })

    it('should return empty string for remote files', () => {
      expect(getFileExtension('file.txt', '', true)).toBe('')
    })
  })

  describe('getFileAppearanceType', () => {
    it('should identify gif files', () => {
      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['gif']))
      expect(getFileAppearanceType('image.gif', 'image/gif'))
        .toBe(FileAppearanceTypeEnum.gif)
    })

    it('should identify image files', () => {
      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['jpg']))
      expect(getFileAppearanceType('image.jpg', 'image/jpeg'))
        .toBe(FileAppearanceTypeEnum.image)

      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['jpeg']))
      expect(getFileAppearanceType('image.jpeg', 'image/jpeg'))
        .toBe(FileAppearanceTypeEnum.image)

      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['png']))
      expect(getFileAppearanceType('image.png', 'image/png'))
        .toBe(FileAppearanceTypeEnum.image)

      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['webp']))
      expect(getFileAppearanceType('image.webp', 'image/webp'))
        .toBe(FileAppearanceTypeEnum.image)

      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['svg']))
      expect(getFileAppearanceType('image.svg', 'image/svgxml'))
        .toBe(FileAppearanceTypeEnum.image)
    })

    it('should identify video files', () => {
      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mp4']))
      expect(getFileAppearanceType('video.mp4', 'video/mp4'))
        .toBe(FileAppearanceTypeEnum.video)

      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mov']))
      expect(getFileAppearanceType('video.mov', 'video/quicktime'))
        .toBe(FileAppearanceTypeEnum.video)

      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mpeg']))
      expect(getFileAppearanceType('video.mpeg', 'video/mpeg'))
        .toBe(FileAppearanceTypeEnum.video)

      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['webm']))
      expect(getFileAppearanceType('video.web', 'video/webm'))
        .toBe(FileAppearanceTypeEnum.video)
    })

    it('should identify audio files', () => {
      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mp3']))
      expect(getFileAppearanceType('audio.mp3', 'audio/mpeg'))
        .toBe(FileAppearanceTypeEnum.audio)

      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['m4a']))
      expect(getFileAppearanceType('audio.m4a', 'audio/mp4'))
        .toBe(FileAppearanceTypeEnum.audio)

      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['wav']))
      expect(getFileAppearanceType('audio.wav', 'audio/vnd.wav'))
        .toBe(FileAppearanceTypeEnum.audio)

      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['amr']))
      expect(getFileAppearanceType('audio.amr', 'audio/AMR'))
        .toBe(FileAppearanceTypeEnum.audio)

      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mpga']))
      expect(getFileAppearanceType('audio.mpga', 'audio/mpeg'))
        .toBe(FileAppearanceTypeEnum.audio)
    })

    it('should identify code files', () => {
      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['html']))
      expect(getFileAppearanceType('index.html', 'text/html'))
        .toBe(FileAppearanceTypeEnum.code)
    })

    it('should identify PDF files', () => {
      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pdf']))
      expect(getFileAppearanceType('doc.pdf', 'application/pdf'))
        .toBe(FileAppearanceTypeEnum.pdf)
    })

    it('should identify markdown files', () => {
      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['md']))
      expect(getFileAppearanceType('file.md', 'text/markdown'))
        .toBe(FileAppearanceTypeEnum.markdown)

      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['markdown']))
      expect(getFileAppearanceType('file.markdown', 'text/markdown'))
        .toBe(FileAppearanceTypeEnum.markdown)

      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['mdx']))
      expect(getFileAppearanceType('file.mdx', 'text/mdx'))
        .toBe(FileAppearanceTypeEnum.markdown)
    })

    it('should identify excel files', () => {
      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['xlsx']))
      expect(getFileAppearanceType('doc.xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'))
        .toBe(FileAppearanceTypeEnum.excel)

      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['xls']))
      expect(getFileAppearanceType('doc.xls', 'application/vnd.ms-excel'))
        .toBe(FileAppearanceTypeEnum.excel)
    })

    it('should identify word files', () => {
      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['doc']))
      expect(getFileAppearanceType('doc.doc', 'application/msword'))
        .toBe(FileAppearanceTypeEnum.word)

      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['docx']))
      expect(getFileAppearanceType('doc.docx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'))
        .toBe(FileAppearanceTypeEnum.word)
    })

    it('should identify word files', () => {
      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['ppt']))
      expect(getFileAppearanceType('doc.ppt', 'application/vnd.ms-powerpoint'))
        .toBe(FileAppearanceTypeEnum.ppt)

      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pptx']))
      expect(getFileAppearanceType('doc.pptx', 'application/vnd.openxmlformats-officedocument.presentationml.presentation'))
        .toBe(FileAppearanceTypeEnum.ppt)
    })

    it('should identify document files', () => {
      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['txt']))
      expect(getFileAppearanceType('file.txt', 'text/plain'))
        .toBe(FileAppearanceTypeEnum.document)

      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['csv']))
      expect(getFileAppearanceType('file.csv', 'text/csv'))
        .toBe(FileAppearanceTypeEnum.document)

      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['msg']))
      expect(getFileAppearanceType('file.msg', 'application/vnd.ms-outlook'))
        .toBe(FileAppearanceTypeEnum.document)

      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['eml']))
      expect(getFileAppearanceType('file.eml', 'message/rfc822'))
        .toBe(FileAppearanceTypeEnum.document)

      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['xml']))
      expect(getFileAppearanceType('file.xml', 'application/rssxml'))
        .toBe(FileAppearanceTypeEnum.document)

      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['epub']))
      expect(getFileAppearanceType('file.epub', 'application/epubzip'))
        .toBe(FileAppearanceTypeEnum.document)
    })

    it('should handle null mime extension', () => {
      jest.mocked(mime.getAllExtensions).mockReturnValue(null)
      expect(getFileAppearanceType('file.txt', 'text/plain'))
        .toBe(FileAppearanceTypeEnum.document)
    })
  })

  describe('getSupportFileType', () => {
    it('should return custom type when isCustom is true', () => {
      expect(getSupportFileType('file.txt', '', true))
        .toBe(SupportUploadFileTypes.custom)
    })

    it('should return file type when isCustom is false', () => {
      expect(getSupportFileType('file.txt', 'text/plain'))
        .toBe(SupportUploadFileTypes.document)
    })
  })

  describe('getProcessedFiles', () => {
    it('should process files correctly', () => {
      const files = [{
        id: '123',
        name: 'test.txt',
        size: 1024,
        type: 'text/plain',
        progress: 100,
        supportFileType: 'document',
        transferMethod: TransferMethod.remote_url,
        url: 'http://example.com',
        uploadedId: '123',
      }]

      const result = getProcessedFiles(files)
      expect(result[0]).toEqual({
        type: 'document',
        transfer_method: TransferMethod.remote_url,
        url: 'http://example.com',
        upload_file_id: '123',
      })
    })
  })

  describe('getProcessedFilesFromResponse', () => {
    it('should process files correctly', () => {
      const files = [{
        related_id: '2a38e2ca-1295-415d-a51d-65d4ff9912d9',
        extension: '.jpeg',
        filename: 'test.jpeg',
        size: 2881761,
        mime_type: 'image/jpeg',
        transfer_method: TransferMethod.local_file,
        type: 'image',
        url: 'https://upload.dify.dev/files/xxx/file-preview',
      }]

      const result = getProcessedFilesFromResponse(files)
      expect(result[0]).toEqual({
        id: '2a38e2ca-1295-415d-a51d-65d4ff9912d9',
        name: 'test.jpeg',
        size: 2881761,
        type: 'image/jpeg',
        progress: 100,
        transferMethod: TransferMethod.local_file,
        supportFileType: 'image',
        uploadedId: '2a38e2ca-1295-415d-a51d-65d4ff9912d9',
        url: 'https://upload.dify.dev/files/xxx/file-preview',
      })
    })
  })

  describe('getFileNameFromUrl', () => {
    it('should extract filename from URL', () => {
      expect(getFileNameFromUrl('http://example.com/path/file.txt'))
        .toBe('file.txt')
    })
  })

  describe('getSupportFileExtensionList', () => {
    it('should handle custom file types', () => {
      const result = getSupportFileExtensionList(
        [SupportUploadFileTypes.custom],
        ['.pdf', '.txt', '.doc'],
      )
      expect(result).toEqual(['PDF', 'TXT', 'DOC'])
    })

    it('should handle standard file types', () => {
      const mockFileExts = {
        image: ['JPG', 'PNG'],
        document: ['PDF', 'TXT'],
        video: ['MP4', 'MOV'],
      }

      // Temporarily mock FILE_EXTS
      const originalFileExts = { ...FILE_EXTS }
      Object.assign(FILE_EXTS, mockFileExts)

      const result = getSupportFileExtensionList(
        ['image', 'document'],
        [],
      )
      expect(result).toEqual(['JPG', 'PNG', 'PDF', 'TXT'])

      // Restore original FILE_EXTS
      Object.assign(FILE_EXTS, originalFileExts)
    })

    it('should return empty array for empty inputs', () => {
      const result = getSupportFileExtensionList([], [])
      expect(result).toEqual([])
    })

    it('should prioritize custom types over standard types', () => {
      const mockFileExts = {
        image: ['JPG', 'PNG'],
      }

      // Temporarily mock FILE_EXTS
      const originalFileExts = { ...FILE_EXTS }
      Object.assign(FILE_EXTS, mockFileExts)

      const result = getSupportFileExtensionList(
        [SupportUploadFileTypes.custom, 'image'],
        ['.csv', '.xml'],
      )
      expect(result).toEqual(['CSV', 'XML'])

      // Restore original FILE_EXTS
      Object.assign(FILE_EXTS, originalFileExts)
    })
  })

  describe('isAllowedFileExtension', () => {
    it('should validate allowed file extensions', () => {
      jest.mocked(mime.getAllExtensions).mockReturnValue(new Set(['pdf']))
      expect(isAllowedFileExtension(
        'test.pdf',
        'application/pdf',
        ['document'],
        ['.pdf'],
      )).toBe(true)
    })
  })

  describe('getFilesInLogs', () => {
    const mockFileData = {
      dify_model_identity: '__dify__file__',
      related_id: '123',
      filename: 'test.pdf',
      size: 1024,
      mime_type: 'application/pdf',
      transfer_method: 'local_file',
      type: 'document',
      url: 'http://example.com/test.pdf',
    }

    it('should handle empty or null input', () => {
      expect(getFilesInLogs(null)).toEqual([])
      expect(getFilesInLogs({})).toEqual([])
      expect(getFilesInLogs(undefined)).toEqual([])
    })

    it('should process single file object', () => {
      const input = {
        file1: mockFileData,
      }

      const expected = [{
        varName: 'file1',
        list: [{
          id: '123',
          name: 'test.pdf',
          size: 1024,
          type: 'application/pdf',
          progress: 100,
          transferMethod: 'local_file',
          supportFileType: 'document',
          uploadedId: '123',
          url: 'http://example.com/test.pdf',
        }],
      }]

      expect(getFilesInLogs(input)).toEqual(expected)
    })

    it('should process array of files', () => {
      const input = {
        files: [mockFileData, mockFileData],
      }

      const expected = [{
        varName: 'files',
        list: [
          {
            id: '123',
            name: 'test.pdf',
            size: 1024,
            type: 'application/pdf',
            progress: 100,
            transferMethod: 'local_file',
            supportFileType: 'document',
            uploadedId: '123',
            url: 'http://example.com/test.pdf',
          },
          {
            id: '123',
            name: 'test.pdf',
            size: 1024,
            type: 'application/pdf',
            progress: 100,
            transferMethod: 'local_file',
            supportFileType: 'document',
            uploadedId: '123',
            url: 'http://example.com/test.pdf',
          },
        ],
      }]

      expect(getFilesInLogs(input)).toEqual(expected)
    })

    it('should ignore non-file objects and arrays', () => {
      const input = {
        regularString: 'not a file',
        regularNumber: 123,
        regularArray: [1, 2, 3],
        regularObject: { key: 'value' },
        file: mockFileData,
      }

      const expected = [{
        varName: 'file',
        list: [{
          id: '123',
          name: 'test.pdf',
          size: 1024,
          type: 'application/pdf',
          progress: 100,
          transferMethod: 'local_file',
          supportFileType: 'document',
          uploadedId: '123',
          url: 'http://example.com/test.pdf',
        }],
      }]

      expect(getFilesInLogs(input)).toEqual(expected)
    })

    it('should handle mixed file types in array', () => {
      const input = {
        mixedFiles: [
          mockFileData,
          { notAFile: true },
          mockFileData,
        ],
      }

      const expected = [{
        varName: 'mixedFiles',
        list: [
          {
            id: '123',
            name: 'test.pdf',
            size: 1024,
            type: 'application/pdf',
            progress: 100,
            transferMethod: 'local_file',
            supportFileType: 'document',
            uploadedId: '123',
            url: 'http://example.com/test.pdf',
          },
          {
            id: undefined,
            name: undefined,
            progress: 100,
            size: 0,
            supportFileType: undefined,
            transferMethod: undefined,
            type: undefined,
            uploadedId: undefined,
            url: undefined,
          },
          {
            id: '123',
            name: 'test.pdf',
            size: 1024,
            type: 'application/pdf',
            progress: 100,
            transferMethod: 'local_file',
            supportFileType: 'document',
            uploadedId: '123',
            url: 'http://example.com/test.pdf',
          },
        ],
      }]

      expect(getFilesInLogs(input)).toEqual(expected)
    })
  })

  describe('fileIsUploaded', () => {
    it('should identify uploaded files', () => {
      expect(fileIsUploaded({
        uploadedId: '123',
        progress: 100,
      } as any)).toBe(true)
    })

    it('should identify remote files as uploaded', () => {
      expect(fileIsUploaded({
        transferMethod: TransferMethod.remote_url,
        progress: 100,
      } as any)).toBe(true)
    })
  })

  describe('downloadFile', () => {
    let mockAnchor: HTMLAnchorElement
    let createElementMock: jest.SpyInstance
    let appendChildMock: jest.SpyInstance
    let removeChildMock: jest.SpyInstance

    beforeEach(() => {
      // Mock createElement and appendChild
      mockAnchor = {
        href: '',
        download: '',
        style: { display: '' },
        target: '',
        title: '',
        click: jest.fn(),
      } as unknown as HTMLAnchorElement

      createElementMock = jest.spyOn(document, 'createElement').mockReturnValue(mockAnchor as any)
      appendChildMock = jest.spyOn(document.body, 'appendChild').mockImplementation((node: Node) => {
        return node
      })
      removeChildMock = jest.spyOn(document.body, 'removeChild').mockImplementation((node: Node) => {
        return node
      })
    })

    afterEach(() => {
      jest.resetAllMocks()
    })

    it('should create and trigger download with correct attributes', () => {
      const url = 'https://example.com/test.pdf'
      const filename = 'test.pdf'

      downloadFile(url, filename)

      // Verify anchor element was created with correct properties
      expect(createElementMock).toHaveBeenCalledWith('a')
      expect(mockAnchor.href).toBe(url)
      expect(mockAnchor.download).toBe(filename)
      expect(mockAnchor.style.display).toBe('none')
      expect(mockAnchor.target).toBe('_blank')
      expect(mockAnchor.title).toBe(filename)

      // Verify DOM operations
      expect(appendChildMock).toHaveBeenCalledWith(mockAnchor)
      expect(mockAnchor.click).toHaveBeenCalled()
      expect(removeChildMock).toHaveBeenCalledWith(mockAnchor)
    })

    it('should handle empty filename', () => {
      const url = 'https://example.com/test.pdf'
      const filename = ''

      downloadFile(url, filename)

      expect(mockAnchor.download).toBe('')
      expect(mockAnchor.title).toBe('')
    })

    it('should handle empty url', () => {
      const url = ''
      const filename = 'test.pdf'

      downloadFile(url, filename)

      expect(mockAnchor.href).toBe('')
    })
  })
})
