import {useCallback} from 'react'

import {useShopActions} from '../../internal/useShopActions'
import {fileToDataUri} from '../../utils'

import type {UploadTarget} from '@shopify/shop-minis-platform/actions'

export interface UploadImageParams {
  /**
   * The file to upload.
   */
  image: File
}

interface ProcessedImage {
  /**
   * The MIME type of the image.
   */
  mimeType: string
  /**
   * The size of the image in bytes.
   */
  fileSize: number
  /**
   * The file blob of the image.
   */
  fileBlob: Blob
}

export interface UploadedImage {
  /**
   * The ID of the uploaded image.
   */
  id: string
  /**
   * The URL of the uploaded image.
   */
  imageUrl?: string
  /**
   * The resource URL of the uploaded image.
   */
  resourceUrl?: string
}

interface UseImageUploadReturns {
  /**
   * Upload an image which will be attached to the current user.
   */
  uploadImage: (image: File) => Promise<UploadedImage[]>
}

// Fetch file data and detect file sizes if not provided
// Works with file://, data:, and http(s):// URIs
const processFileData = async (image: File): Promise<ProcessedImage> => {
  const uri = await fileToDataUri(image)

  const response = await fetch(uri)
  const blob = await response.blob()

  return {
    mimeType: image.type,
    fileSize: image.size ?? blob.size,
    fileBlob: blob,
  }
}

const uploadFileToGCS = async (image: ProcessedImage, target: UploadTarget) => {
  const formData = new FormData()
  target.parameters.forEach(({name, value}: {name: string; value: string}) => {
    formData.append(name, value)
  })

  formData.append('file', image.fileBlob)

  const uploadResponse = await fetch(target.url, {
    method: 'POST',
    body: formData,
  })

  if (!uploadResponse.ok) {
    console.error('Failed to upload image', {
      response: await uploadResponse.text(),
    })
    return {error: 'Failed to upload image'}
  }

  return {}
}

export const useImageUpload = (): UseImageUploadReturns => {
  const {createImageUploadLink, completeImageUpload} = useShopActions()

  const uploadImage = useCallback(
    async (image: File) => {
      const processedImageParams = await processFileData(image)

      const links = await createImageUploadLink({
        input: [
          {
            mimeType: processedImageParams.mimeType,
            fileSize: processedImageParams.fileSize,
          },
        ],
      })

      if (!links.ok) {
        throw new Error(links.error.message)
      }

      // Upload single file to GCS
      const {error: uploadError} = await uploadFileToGCS(
        processedImageParams,
        links?.data?.targets?.[0]!
      )

      if (uploadError) {
        throw new Error(uploadError)
      }

      // 10 second polling for image upload
      let count = 0
      while (count < 30) {
        const result = await completeImageUpload({
          resourceUrls:
            links?.data?.targets?.map(target => target.resourceUrl) || [],
        })

        if (!result.ok) {
          throw new Error(result.error.message)
        }

        if (result.data?.files?.[0]?.fileStatus === 'READY') {
          return [
            {
              id: result.data.files[0].id,
              imageUrl: result.data.files[0].image?.url,
              resourceUrl: links?.data?.targets?.[0]?.resourceUrl,
            },
          ]
        }

        await new Promise(resolve => setTimeout(resolve, 1000))
        count++
      }

      throw new Error('Image upload completion timed out')
    },
    [createImageUploadLink, completeImageUpload]
  )

  return {
    uploadImage,
  }
}
