import type { CosyError } from './types'
import { http, useAxios } from '@uozi-admin/request'
import JSEncrypt from 'jsencrypt'
import { storeToRefs } from 'pinia'
import use2FAModal from '@/components/TwoFA/use2FAModal'
import { useNProgress } from '@/lib/nprogress/nprogress'
import { useSettingsStore, useUserStore } from '@/pinia'
import router from '@/routes'
import { handleApiError, useMessageDedupe } from './error'

const { setRequestInterceptor, setResponseInterceptor } = useAxios()

const nprogress = useNProgress()
const dedupe = useMessageDedupe()

// Helper function for encrypting JSON data
// eslint-disable-next-line ts/no-explicit-any
async function encryptJsonData(data: any): Promise<string> {
  const cryptoParams = await http.get('/crypto/public_key')
  const { public_key } = await cryptoParams

  // Encrypt data with RSA public key
  const encrypt = new JSEncrypt()
  encrypt.setPublicKey(public_key)

  return JSON.stringify({
    encrypted_params: encrypt.encrypt(JSON.stringify(data)),
  })
}

// Helper function for handling encrypted form data
async function handleEncryptedFormData(formData: FormData): Promise<FormData> {
  const cryptoParams = await http.get('/crypto/public_key')
  const { public_key } = await cryptoParams

  // Extract form parameters that are not files
  // eslint-disable-next-line ts/no-explicit-any
  const formParams: Record<string, any> = {}
  const newFormData = new FormData()

  // Copy all files to new FormData
  for (const [key, value] of formData.entries()) {
    // Check if value is a File or Blob
    // eslint-disable-next-line ts/no-explicit-any
    if (typeof value !== 'string' && ((value as any) instanceof File || (value as any) instanceof Blob)) {
      newFormData.append(key, value)
    }
    else {
      // Collect non-file fields to encrypt
      formParams[key] = value
    }
  }

  // Encrypt the form parameters
  const encrypt = new JSEncrypt()
  encrypt.setPublicKey(public_key)

  // Add encrypted params to form data
  const encryptedData = encrypt.encrypt(JSON.stringify(formParams))
  if (encryptedData) {
    newFormData.append('encrypted_params', encryptedData)
  }

  return newFormData
}

// Setup request interceptor
export function setupRequestInterceptor() {
  // Setup stores and refs
  const user = useUserStore()
  const settings = useSettingsStore()
  const { token, secureSessionId } = storeToRefs(user)
  setRequestInterceptor(
    async config => {
      nprogress.start()
      if (token.value) {
        config.headers.Authorization = token.value
      }

      if (settings.environment.id) {
        config.headers['X-Node-ID'] = settings.environment.id
      }

      if (secureSessionId.value) {
        config.headers['X-Secure-Session-ID'] = secureSessionId.value
      }

      // Handle JSON encryption
      if (config.headers?.['Content-Type'] !== 'multipart/form-data;charset=UTF-8') {
        config.headers['Content-Type'] = 'application/json'

        if (config.crypto) {
          config.data = await encryptJsonData(config.data)
        }
      }
      // Handle form data with encryption
      else if (config.crypto && config.data instanceof FormData) {
        config.data = await handleEncryptedFormData(config.data)
      }

      return config
    },
    err => {
      return Promise.reject(err)
    },
  )
}

// Setup response interceptor
export function setupResponseInterceptor() {
  setResponseInterceptor(
    response => {
      nprogress.done()

      // Check if full response is requested in config
      if (response?.config?.returnFullResponse) {
        return Promise.resolve(response)
      }
      return Promise.resolve(response.data)
    },

    async error => {
      // Setup stores and refs
      const user = useUserStore()
      const { secureSessionId } = storeToRefs(user)
      nprogress.done()
      const otpModal = use2FAModal()

      // Handle authentication errors
      if (error?.response) {
        switch (error.response.status) {
          case 401:
            secureSessionId.value = ''
            await otpModal.open()
            break
          case 403:
            user.logout()
            await router.push('/login')
            return
        }
      }

      // Handle JSON error that comes back as Blob for blob request type
      if (error?.response?.data instanceof Blob && error?.response?.data?.type === 'application/json') {
        try {
          const text = await error.response.data.text()
          error.response.data = JSON.parse(text)
        }
        catch (e) {
          // If parsing fails, we'll continue with the original error.response.data
          console.error('Failed to parse blob error response as JSON', e)
        }
      }

      const err = error.response?.data as CosyError
      await handleApiError(err, dedupe)

      return Promise.reject(error.response?.data)
    },
  )
}

export function setupInterceptors() {
  setupRequestInterceptor()
  setupResponseInterceptor()
}
