/**
 * @deprecated v2 upgrade pending. BackupService/NutstoreService are slated for replacement, and v2
 * can no longer perform real backups. As an interim measure, the transient sync status that used to
 * live in the Redux `backup` slice is now held in a session-local, non-reactive module object
 * (`backupSyncState` below) — just enough to keep the auto-sync scheduler internally consistent,
 * not a real implementation. Do not build on this.
 */
//TODO Data Refactor
// The code is messy, need to refactor all the backup related code

import { preferenceService } from '@data/PreferenceService'
import { loggerService } from '@logger'
import db from '@renderer/databases'
import { upgradeToV7, upgradeToV8 } from '@renderer/databases/upgrades'
import i18n from '@renderer/i18n'
import { uuid } from '@renderer/utils/uuid'
import type { S3Config, WebDavConfig } from '@shared/types/backup'
import dayjs from 'dayjs'

import { notificationService } from './notification'

const logger = loggerService.withContext('BackupService')

export interface RemoteSyncState {
  lastSyncTime: number | null
  syncing: boolean
  lastSyncError: string | null
}

// Session-local, non-reactive sync status. BackupService is slated for replacement and v2 can no
// longer perform real backups, so this only needs to stay internally consistent: the auto-sync
// scheduler writes timestamps here and reads them back; the settings UI reads it best-effort.
const backupSyncState: Record<'webdavSync' | 'localBackupSync' | 's3Sync', RemoteSyncState> = {
  webdavSync: { lastSyncTime: null, syncing: false, lastSyncError: null },
  localBackupSync: { lastSyncTime: null, syncing: false, lastSyncError: null },
  s3Sync: { lastSyncTime: null, syncing: false, lastSyncError: null }
}

export const getBackupSyncState = () => backupSyncState

const setWebDAVSyncState = (patch: Partial<RemoteSyncState>) => {
  Object.assign(backupSyncState.webdavSync, patch)
}

const setS3SyncState = (patch: Partial<RemoteSyncState>) => {
  Object.assign(backupSyncState.s3Sync, patch)
}

const setLocalBackupSyncState = (patch: Partial<RemoteSyncState>) => {
  Object.assign(backupSyncState.localBackupSync, patch)
}

// 重试删除S3文件的辅助函数
async function deleteS3FileWithRetry(fileName: string, s3Config: S3Config, maxRetries = 3) {
  let lastError: Error | null = null

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      await window.api.backup.deleteS3File(fileName, s3Config)
      logger.verbose(`Successfully deleted old backup file: ${fileName} (attempt ${attempt})`)
      return true
    } catch (error: any) {
      lastError = error
      logger.warn(`Delete attempt ${attempt}/${maxRetries} failed for ${fileName}:`, error.message)

      // 如果不是最后一次尝试，等待一段时间再重试
      if (attempt < maxRetries) {
        const delay = attempt * 1000 + Math.random() * 1000 // 1-2秒的随机延迟
        await new Promise((resolve) => setTimeout(resolve, delay))
      }
    }
  }

  logger.error(`Failed to delete old backup file after ${maxRetries} attempts: ${fileName}`, lastError)
  return false
}

// 重试删除WebDAV文件的辅助函数
async function deleteWebdavFileWithRetry(fileName: string, webdavConfig: WebDavConfig, maxRetries = 3) {
  let lastError: Error | null = null

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      await window.api.backup.deleteWebdavFile(fileName, webdavConfig)
      logger.verbose(`Successfully deleted old backup file: ${fileName} (attempt ${attempt})`)
      return true
    } catch (error: any) {
      lastError = error
      logger.warn(`Delete attempt ${attempt}/${maxRetries} failed for ${fileName}:`, error.message)

      // 如果不是最后一次尝试，等待一段时间再重试
      if (attempt < maxRetries) {
        const delay = attempt * 1000 + Math.random() * 1000 // 1-2秒的随机延迟
        await new Promise((resolve) => setTimeout(resolve, delay))
      }
    }
  }

  logger.error(`Failed to delete old backup file after ${maxRetries} attempts: ${fileName}`, lastError)
  return false
}

export async function backup(skipBackupFile: boolean) {
  const filename = `cherry-studio.${dayjs().format('YYYYMMDDHHmm')}.zip`
  const selectFolder = await window.api.file.selectFolder()
  if (selectFolder) {
    // Use direct backup method - copy IndexedDB/LocalStorage directories directly
    await window.api.backup.backup(filename, selectFolder, skipBackupFile)
    window.toast.success(i18n.t('message.backup.success'))
  }
}

export async function backupToLanTransfer() {
  // Let user select save location first
  const savePath = await window.api.file.selectFolder()

  if (!savePath) {
    return
  }

  // Create backup directly in the selected location
  const backupData = await getBackupData()
  await window.api.backup.createLanTransferBackup(backupData, savePath)

  window.toast.success(i18n.t('settings.data.export_to_phone.file.export_success'))
}

export async function restore() {
  // notificationService is imported as a module-level singleton
  const file = await window.api.file.open({ filters: [{ name: '备份文件', extensions: ['bak', 'zip'] }] })

  if (file) {
    try {
      // zip backup file
      if (file?.fileName.endsWith('.zip')) {
        const restoreData = await window.api.backup.restore(file.filePath)

        // Direct backup format returns void (app needs to relaunch)
        // Legacy format returns JSON string that needs to be processed
        if (restoreData !== undefined && restoreData !== null) {
          const data = JSON.parse(restoreData)
          await handleData(data)
        } else {
          // Direct backup was restored, app will relaunch
          void notificationService.send({
            id: uuid(),
            type: 'success',
            title: i18n.t('common.success'),
            message: i18n.t('message.restore.success'),
            silent: false,
            timestamp: Date.now(),
            source: 'backup'
          })
          // App will relaunch automatically
          return
        }
      } else {
        // Legacy .bak format
        const data = JSON.parse(await window.api.zip.decompress(file.content))
        await handleData(data)
      }

      void notificationService.send({
        id: uuid(),
        type: 'success',
        title: i18n.t('common.success'),
        message: i18n.t('message.restore.success'),
        silent: false,
        timestamp: Date.now(),
        source: 'backup'
      })
    } catch (error) {
      logger.error('restore: Error restoring backup file:', error as Error)
      window.modal.error({
        title: i18n.t('error.backup.file_format'),
        content: (error as Error).message,
        centered: true
      })
    }
  }
}

export async function reset() {
  window.modal.confirm({
    title: i18n.t('common.warning'),
    content: i18n.t('message.reset.confirm.content'),
    centered: true,
    okText: i18n.t('common.confirm'),
    cancelText: i18n.t('common.cancel'),
    okButtonProps: {
      danger: true
    },
    onOk: async () => {
      window.modal.confirm({
        title: i18n.t('message.reset.double.confirm.title'),
        content: i18n.t('message.reset.double.confirm.content'),
        centered: true,
        okText: i18n.t('common.confirm'),
        cancelText: i18n.t('common.cancel'),
        onOk: async () => {
          localStorage.clear()
          await clearDatabase()
          await window.api.resetData()
          window.toast.success(i18n.t('message.reset.success'))
          setTimeout(() => window.api.application.relaunch(), 1000)
        }
      })
    }
  })
}

// 备份到 webdav
/**
 * @param showMessage
 * @param customFileName
 * @param autoBackupProcess
 * if call in auto backup process, not show any message, any error will be thrown
 */
export async function backupToWebdav({
  showMessage = false,
  customFileName = '',
  autoBackupProcess = false
}: {
  showMessage?: boolean
  customFileName?: string
  autoBackupProcess?: boolean
} = {}) {
  // notificationService is imported as a module-level singleton
  if (isManualBackupRunning) {
    logger.verbose('Manual backup already in progress')
    return
  }
  // force set showMessage to false when auto backup process
  if (autoBackupProcess) {
    showMessage = false
  }

  isManualBackupRunning = true

  setWebDAVSyncState({ syncing: true, lastSyncError: null })

  const {
    webdavHost,
    webdavUser,
    webdavPass,
    webdavPath,
    webdavMaxBackups,
    webdavSkipBackupFile,
    webdavDisableStream
  } = await preferenceService.getMultiple({
    webdavHost: 'data.backup.webdav.host',
    webdavUser: 'data.backup.webdav.user',
    webdavPass: 'data.backup.webdav.pass',
    webdavPath: 'data.backup.webdav.path',
    webdavMaxBackups: 'data.backup.webdav.max_backups',
    webdavSkipBackupFile: 'data.backup.webdav.skip_backup_file',
    webdavDisableStream: 'data.backup.webdav.disable_stream'
  })

  let deviceType = 'unknown'
  let hostname = 'unknown'
  try {
    deviceType = (await window.api.system.getDeviceType()) || 'unknown'
    hostname = (await window.api.system.getHostname()) || 'unknown'
  } catch (error) {
    logger.error('Failed to get device type or hostname:', error as Error)
  }
  const timestamp = dayjs().format('YYYYMMDDHHmmss')
  const backupFileName = customFileName || `cherry-studio.${timestamp}.${hostname}.${deviceType}.zip`
  const finalFileName = backupFileName.endsWith('.zip') ? backupFileName : `${backupFileName}.zip`

  // 上传文件 - Use direct backup method (copy IndexedDB/LocalStorage directories)
  try {
    const success = await window.api.backup.backupToWebdav({
      webdavHost,
      webdavUser,
      webdavPass,
      webdavPath,
      fileName: finalFileName,
      skipBackupFile: webdavSkipBackupFile,
      disableStream: webdavDisableStream
    })
    if (success) {
      setWebDAVSyncState({
        lastSyncError: null
      })
      void notificationService.send({
        id: uuid(),
        type: 'success',
        title: i18n.t('common.success'),
        message: i18n.t('message.backup.success'),
        silent: false,
        timestamp: Date.now(),
        source: 'backup'
      })
      showMessage && window.toast.success(i18n.t('message.backup.success'))

      // 清理旧备份文件
      if (webdavMaxBackups > 0) {
        try {
          // 获取所有备份文件
          const files = await window.api.backup.listWebdavFiles({
            webdavHost,
            webdavUser,
            webdavPass,
            webdavPath
          })

          // 筛选当前设备的备份文件
          const currentDeviceFiles = files.filter((file) => {
            // 检查文件名是否包含当前设备的标识信息
            return file.fileName.includes(deviceType) && file.fileName.includes(hostname)
          })

          // 如果当前设备的备份文件数量超过最大保留数量，删除最旧的文件
          if (currentDeviceFiles.length > webdavMaxBackups) {
            // 文件已按修改时间降序排序，所以最旧的文件在末尾
            const filesToDelete = currentDeviceFiles.slice(webdavMaxBackups)

            logger.verbose(`Cleaning up ${filesToDelete.length} old backup files`)

            // 串行删除文件，避免并发请求导致的问题
            for (let i = 0; i < filesToDelete.length; i++) {
              const file = filesToDelete[i]
              await deleteWebdavFileWithRetry(file.fileName, {
                webdavHost,
                webdavUser,
                webdavPass,
                webdavPath
              })

              // 在删除操作之间添加短暂延迟，避免请求过于频繁
              if (i < filesToDelete.length - 1) {
                await new Promise((resolve) => setTimeout(resolve, 500))
              }
            }
          }
        } catch (error) {
          logger.error('Failed to clean up old backup files:', error as Error)
        }
      }
    } else {
      // if auto backup process, throw error
      if (autoBackupProcess) {
        throw new Error(i18n.t('message.backup.failed'))
      }

      setWebDAVSyncState({ lastSyncError: 'Backup failed' })
      showMessage && window.toast.error(i18n.t('message.backup.failed'))
    }
  } catch (error: any) {
    // if auto backup process, throw error
    if (autoBackupProcess) {
      throw error
    }
    void notificationService.send({
      id: uuid(),
      type: 'error',
      title: i18n.t('message.backup.failed'),
      message: error.message,
      silent: false,
      timestamp: Date.now(),
      source: 'backup'
    })
    setWebDAVSyncState({ lastSyncError: error.message })
    showMessage && window.toast.error(i18n.t('message.backup.failed'))
    logger.error('[Backup] backupToWebdav: Error uploading file to WebDAV:', error)
    throw error
  } finally {
    if (!autoBackupProcess) {
      setWebDAVSyncState({
        lastSyncTime: Date.now(),
        syncing: false
      })
    }
    isManualBackupRunning = false
  }
}

// 从 webdav 恢复
export async function restoreFromWebdav(fileName?: string) {
  const { webdavHost, webdavUser, webdavPass, webdavPath } = await preferenceService.getMultiple({
    webdavHost: 'data.backup.webdav.host',
    webdavUser: 'data.backup.webdav.user',
    webdavPass: 'data.backup.webdav.pass',
    webdavPath: 'data.backup.webdav.path'
  })
  let data = ''

  try {
    data = await window.api.backup.restoreFromWebdav({ webdavHost, webdavUser, webdavPass, webdavPath, fileName })
  } catch (error: any) {
    logger.error('[Backup] restoreFromWebdav: Error downloading file from WebDAV:', error)
    window.modal.error({
      title: i18n.t('message.restore.failed'),
      content: error.message
    })
    return
  }

  // Direct backup format (version 6+) returns undefined - app needs to relaunch
  if (!data) {
    logger.info('[WebDAVBackup] Direct backup restored, app will restart')
    return
  }

  // Legacy backup format (version <= 5) returns JSON string
  try {
    await handleData(JSON.parse(data))
  } catch (error) {
    logger.error('[Backup] Error downloading file from WebDAV:', error as Error)
    window.toast.error(i18n.t('error.backup.file_format'))
  }
}

export async function backupToS3({
  showMessage = false,
  customFileName = '',
  autoBackupProcess = false
}: {
  showMessage?: boolean
  customFileName?: string
  autoBackupProcess?: boolean
} = {}) {
  // notificationService is imported as a module-level singleton
  if (isManualBackupRunning) {
    logger.verbose('Manual backup already in progress')
    return
  }

  if (autoBackupProcess) {
    showMessage = false
  }

  isManualBackupRunning = true

  setS3SyncState({ syncing: true, lastSyncError: null })

  const s3Config = await preferenceService.getMultiple({
    autoSync: 'data.backup.s3.auto_sync',
    accessKeyId: 'data.backup.s3.access_key_id',
    secretAccessKey: 'data.backup.s3.secret_access_key',
    endpoint: 'data.backup.s3.endpoint',
    bucket: 'data.backup.s3.bucket',
    region: 'data.backup.s3.region',
    root: 'data.backup.s3.root',
    maxBackups: 'data.backup.s3.max_backups',
    skipBackupFile: 'data.backup.s3.skip_backup_file',
    syncInterval: 'data.backup.s3.sync_interval'
  })
  let deviceType = 'unknown'
  let hostname = 'unknown'
  try {
    deviceType = (await window.api.system.getDeviceType()) || 'unknown'
    hostname = (await window.api.system.getHostname()) || 'unknown'
  } catch (error) {
    logger.error('Failed to get device type or hostname:', error as Error)
  }
  const timestamp = dayjs().format('YYYYMMDDHHmmss')
  const backupFileName = customFileName || `cherry-studio.${timestamp}.${hostname}.${deviceType}.zip`
  const finalFileName = backupFileName.endsWith('.zip') ? backupFileName : `${backupFileName}.zip`

  try {
    // Use direct backup method (copy IndexedDB/LocalStorage directories)
    const success = await window.api.backup.backupToS3({
      ...s3Config,
      fileName: finalFileName
    })

    if (success) {
      setS3SyncState({
        lastSyncError: null,
        syncing: false,
        lastSyncTime: Date.now()
      })
      void notificationService.send({
        id: uuid(),
        type: 'success',
        title: i18n.t('common.success'),
        message: i18n.t('message.backup.success'),
        silent: false,
        timestamp: Date.now(),
        source: 'backup'
      })
      showMessage && window.toast.success(i18n.t('message.backup.success'))

      // 清理旧备份文件
      if (s3Config.maxBackups > 0) {
        try {
          // 获取所有备份文件
          const files = await window.api.backup.listS3Files(s3Config)

          // 筛选当前设备的备份文件
          const currentDeviceFiles = files.filter((file) => {
            return file.fileName.includes(deviceType) && file.fileName.includes(hostname)
          })

          // 如果当前设备的备份文件数量超过最大保留数量，删除最旧的文件
          if (currentDeviceFiles.length > s3Config.maxBackups) {
            const filesToDelete = currentDeviceFiles.slice(s3Config.maxBackups)

            logger.verbose(`Cleaning up ${filesToDelete.length} old backup files`)

            for (let i = 0; i < filesToDelete.length; i++) {
              const file = filesToDelete[i]
              await deleteS3FileWithRetry(file.fileName, s3Config)

              if (i < filesToDelete.length - 1) {
                await new Promise((resolve) => setTimeout(resolve, 500))
              }
            }
          }
        } catch (error) {
          logger.error('Failed to clean up old backup files:', error as Error)
        }
      }
    } else {
      if (autoBackupProcess) {
        throw new Error(i18n.t('message.backup.failed'))
      }

      setS3SyncState({ lastSyncError: 'Backup failed' })
      showMessage && window.toast.error(i18n.t('message.backup.failed'))
    }
  } catch (error: any) {
    if (autoBackupProcess) {
      throw error
    }
    void notificationService.send({
      id: uuid(),
      type: 'error',
      title: i18n.t('message.backup.failed'),
      message: error.message,
      silent: false,
      timestamp: Date.now(),
      source: 'backup'
    })
    setS3SyncState({ lastSyncError: error.message })
    logger.error('backupToS3: Error uploading file to S3:', error)
    showMessage && window.toast.error(i18n.t('message.backup.failed'))
    throw error
  } finally {
    if (!autoBackupProcess) {
      setS3SyncState({
        lastSyncTime: Date.now(),
        syncing: false
      })
    }
    isManualBackupRunning = false
  }
}

// 从 S3 恢复
export async function restoreFromS3(fileName?: string) {
  const s3Config = await preferenceService.getMultiple({
    autoSync: 'data.backup.s3.auto_sync',
    accessKeyId: 'data.backup.s3.access_key_id',
    secretAccessKey: 'data.backup.s3.secret_access_key',
    endpoint: 'data.backup.s3.endpoint',
    bucket: 'data.backup.s3.bucket',
    region: 'data.backup.s3.region',
    root: 'data.backup.s3.root',
    maxBackups: 'data.backup.s3.max_backups',
    skipBackupFile: 'data.backup.s3.skip_backup_file',
    syncInterval: 'data.backup.s3.sync_interval'
  })

  if (!fileName) {
    const files = await window.api.backup.listS3Files(s3Config)
    if (files.length > 0) {
      fileName = files[0].fileName
    }
  }

  if (fileName) {
    const restoreData = await window.api.backup.restoreFromS3({
      ...s3Config,
      fileName
    })

    // Direct backup format (version 6+) returns undefined - app needs to relaunch
    if (!restoreData) {
      logger.info('[S3Backup] Direct backup restored, app will restart')
      return
    }

    // Legacy backup format (version <= 5) returns JSON string
    const data = JSON.parse(restoreData)
    await handleData(data)
  }
}

let isManualBackupRunning = false

// 为每种备份类型维护独立的状态
let webdavAutoSyncStarted = false
let webdavSyncTimeout: NodeJS.Timeout | null = null
let isWebdavAutoBackupRunning = false

let s3AutoSyncStarted = false
let s3SyncTimeout: NodeJS.Timeout | null = null
let isS3AutoBackupRunning = false

let localAutoSyncStarted = false
let localSyncTimeout: NodeJS.Timeout | null = null
let isLocalAutoBackupRunning = false

type BackupType = 'webdav' | 's3' | 'local'

export async function startAutoSync(immediate = false, type?: BackupType) {
  // 如果没有指定类型，启动所有配置的自动同步
  if (!type) {
    const { webdavAutoSync, webdavHost, localBackupAutoSync, localBackupDir } = await preferenceService.getMultiple({
      webdavAutoSync: 'data.backup.webdav.auto_sync',
      webdavHost: 'data.backup.webdav.host',
      localBackupAutoSync: 'data.backup.local.auto_sync',
      localBackupDir: 'data.backup.local.dir'
    })
    const s3Settings = await preferenceService.getMultiple({
      autoSync: 'data.backup.s3.auto_sync',
      endpoint: 'data.backup.s3.endpoint',
      bucket: 'data.backup.s3.bucket',
      region: 'data.backup.s3.region',
      root: 'data.backup.s3.root'
    })

    if (webdavAutoSync && webdavHost) {
      void startAutoSync(immediate, 'webdav')
    }
    if (s3Settings?.autoSync && s3Settings?.endpoint) {
      void startAutoSync(immediate, 's3')
    }
    if (localBackupAutoSync && localBackupDir) {
      void startAutoSync(immediate, 'local')
    }
    return
  }

  // 根据类型启动特定的自动同步
  if (type === 'webdav') {
    if (webdavAutoSyncStarted) {
      return
    }

    const { webdavAutoSync, webdavHost } = await preferenceService.getMultiple({
      webdavAutoSync: 'data.backup.webdav.auto_sync',
      webdavHost: 'data.backup.webdav.host'
    })

    if (!webdavAutoSync || !webdavHost) {
      logger.info('[WebdavAutoSync] Invalid sync settings, auto sync disabled')
      return
    }

    webdavAutoSyncStarted = true
    stopAutoSync('webdav')
    void scheduleNextBackup(immediate ? 'immediate' : 'fromLastSyncTime', 'webdav')
  } else if (type === 's3') {
    if (s3AutoSyncStarted) {
      return
    }

    const s3Settings = await preferenceService.getMultiple({
      autoSync: 'data.backup.s3.auto_sync',
      endpoint: 'data.backup.s3.endpoint'
    })

    if (!s3Settings?.autoSync || !s3Settings?.endpoint) {
      logger.verbose('Invalid sync settings, auto sync disabled')
      return
    }

    s3AutoSyncStarted = true
    stopAutoSync('s3')
    void scheduleNextBackup(immediate ? 'immediate' : 'fromLastSyncTime', 's3')
  } else if (type === 'local') {
    if (localAutoSyncStarted) {
      return
    }

    const { localBackupAutoSync, localBackupDir } = await preferenceService.getMultiple({
      localBackupAutoSync: 'data.backup.local.auto_sync',
      localBackupDir: 'data.backup.local.dir'
    })

    if (!localBackupAutoSync || !localBackupDir) {
      logger.verbose('Invalid sync settings, auto sync disabled')
      return
    }

    localAutoSyncStarted = true
    stopAutoSync('local')
    void scheduleNextBackup(immediate ? 'immediate' : 'fromLastSyncTime', 'local')
  }

  async function scheduleNextBackup(
    scheduleType: 'immediate' | 'fromLastSyncTime' | 'fromNow',
    backupType: BackupType
  ) {
    let syncInterval: number
    let lastSyncTime: number | undefined
    let logPrefix: string

    // 根据备份类型获取相应的配置和状态
    const backup = getBackupSyncState()

    if (backupType === 'webdav') {
      if (webdavSyncTimeout) {
        clearTimeout(webdavSyncTimeout)
        webdavSyncTimeout = null
      }
      syncInterval = await preferenceService.get('data.backup.webdav.sync_interval')
      lastSyncTime = backup.webdavSync?.lastSyncTime || undefined
      logPrefix = '[WebdavAutoSync]'
    } else if (backupType === 's3') {
      if (s3SyncTimeout) {
        clearTimeout(s3SyncTimeout)
        s3SyncTimeout = null
      }
      syncInterval = await preferenceService.get('data.backup.s3.sync_interval')
      lastSyncTime = backup.s3Sync?.lastSyncTime || undefined
      logPrefix = '[S3AutoSync]'
    } else if (backupType === 'local') {
      if (localSyncTimeout) {
        clearTimeout(localSyncTimeout)
        localSyncTimeout = null
      }
      syncInterval = await preferenceService.get('data.backup.local.sync_interval')
      lastSyncTime = backup.localBackupSync?.lastSyncTime || undefined
      logPrefix = '[LocalAutoSync]'
    } else {
      return
    }

    if (!syncInterval || syncInterval <= 0) {
      logger.verbose(`${logPrefix} Invalid sync interval, auto sync disabled`)
      stopAutoSync(backupType)
      return
    }

    const requiredInterval = syncInterval * 60 * 1000
    let timeUntilNextSync = 1000

    switch (scheduleType) {
      case 'fromLastSyncTime':
        timeUntilNextSync = Math.max(1000, (lastSyncTime || 0) + requiredInterval - Date.now())
        break
      case 'fromNow':
        timeUntilNextSync = requiredInterval
        break
    }

    const timeout = setTimeout(() => performAutoBackup(backupType), timeUntilNextSync)

    // 保存对应类型的 timeout
    if (backupType === 'webdav') {
      webdavSyncTimeout = timeout
    } else if (backupType === 's3') {
      s3SyncTimeout = timeout
    } else if (backupType === 'local') {
      localSyncTimeout = timeout
    }

    logger.verbose(
      `${logPrefix} Next sync scheduled in ${Math.floor(timeUntilNextSync / 1000 / 60)} minutes ${Math.floor(
        (timeUntilNextSync / 1000) % 60
      )} seconds`
    )
  }

  async function performAutoBackup(backupType: BackupType) {
    let isRunning: boolean
    let logPrefix: string

    if (backupType === 'webdav') {
      isRunning = isWebdavAutoBackupRunning
      logPrefix = '[WebdavAutoSync]'
    } else if (backupType === 's3') {
      isRunning = isS3AutoBackupRunning
      logPrefix = '[S3AutoSync]'
    } else if (backupType === 'local') {
      isRunning = isLocalAutoBackupRunning
      logPrefix = '[LocalAutoSync]'
    } else {
      return
    }

    if (isRunning || isManualBackupRunning) {
      logger.verbose(`${logPrefix} Backup already in progress, rescheduling`)
      void scheduleNextBackup('fromNow', backupType)
      return
    }

    // 设置运行状态
    if (backupType === 'webdav') {
      isWebdavAutoBackupRunning = true
    } else if (backupType === 's3') {
      isS3AutoBackupRunning = true
    } else if (backupType === 'local') {
      isLocalAutoBackupRunning = true
    }

    const maxRetries = 4
    let retryCount = 0

    while (retryCount < maxRetries) {
      try {
        logger.verbose(`${logPrefix} Starting auto backup... (attempt ${retryCount + 1}/${maxRetries})`)

        if (backupType === 'webdav') {
          await backupToWebdav({ autoBackupProcess: true })
          setWebDAVSyncState({
            lastSyncError: null,
            lastSyncTime: Date.now(),
            syncing: false
          })
        } else if (backupType === 's3') {
          await backupToS3({ autoBackupProcess: true })
          setS3SyncState({
            lastSyncError: null,
            lastSyncTime: Date.now(),
            syncing: false
          })
        } else if (backupType === 'local') {
          await backupToLocal({ autoBackupProcess: true })
          setLocalBackupSyncState({
            lastSyncError: null,
            lastSyncTime: Date.now(),
            syncing: false
          })
        }

        // 重置运行状态
        if (backupType === 'webdav') {
          isWebdavAutoBackupRunning = false
        } else if (backupType === 's3') {
          isS3AutoBackupRunning = false
        } else if (backupType === 'local') {
          isLocalAutoBackupRunning = false
        }

        void scheduleNextBackup('fromNow', backupType)
        break
      } catch (error: any) {
        retryCount++
        if (retryCount === maxRetries) {
          logger.error(`${logPrefix} Auto backup failed after all retries:`, error)

          if (backupType === 'webdav') {
            setWebDAVSyncState({
              lastSyncError: 'Auto backup failed',
              lastSyncTime: Date.now(),
              syncing: false
            })
          } else if (backupType === 's3') {
            setS3SyncState({
              lastSyncError: 'Auto backup failed',
              lastSyncTime: Date.now(),
              syncing: false
            })
          } else if (backupType === 'local') {
            setLocalBackupSyncState({
              lastSyncError: 'Auto backup failed',
              lastSyncTime: Date.now(),
              syncing: false
            })
          }

          await window.modal.error({
            title: i18n.t('message.backup.failed'),
            content: `${logPrefix} ${new Date().toLocaleString()} ` + error.message
          })

          void scheduleNextBackup('fromNow', backupType)

          // 重置运行状态
          if (backupType === 'webdav') {
            isWebdavAutoBackupRunning = false
          } else if (backupType === 's3') {
            isS3AutoBackupRunning = false
          } else if (backupType === 'local') {
            isLocalAutoBackupRunning = false
          }
        } else {
          const backoffDelay = Math.pow(2, retryCount - 1) * 10000 - 3000
          logger.warn(`${logPrefix} Failed, retry ${retryCount}/${maxRetries} after ${backoffDelay / 1000}s`)

          await new Promise((resolve) => setTimeout(resolve, backoffDelay))

          // 检查是否被用户停止
          let currentRunning: boolean
          if (backupType === 'webdav') {
            currentRunning = isWebdavAutoBackupRunning
          } else if (backupType === 's3') {
            currentRunning = isS3AutoBackupRunning
          } else {
            currentRunning = isLocalAutoBackupRunning
          }

          if (!currentRunning) {
            logger.info(`${logPrefix} retry cancelled by user, exit`)
            break
          }
        }
      }
    }
  }
}

export function stopAutoSync(type?: BackupType) {
  // 如果没有指定类型，停止所有自动同步
  if (!type) {
    stopAutoSync('webdav')
    stopAutoSync('s3')
    stopAutoSync('local')
    return
  }

  if (type === 'webdav') {
    if (webdavSyncTimeout) {
      logger.info('[WebdavAutoSync] Stopping auto sync')
      clearTimeout(webdavSyncTimeout)
      webdavSyncTimeout = null
    }
    isWebdavAutoBackupRunning = false
    webdavAutoSyncStarted = false
  } else if (type === 's3') {
    if (s3SyncTimeout) {
      logger.info('[S3AutoSync] Stopping auto sync')
      clearTimeout(s3SyncTimeout)
      s3SyncTimeout = null
    }
    isS3AutoBackupRunning = false
    s3AutoSyncStarted = false
  } else if (type === 'local') {
    if (localSyncTimeout) {
      logger.info('[LocalAutoSync] Stopping auto sync')
      clearTimeout(localSyncTimeout)
      localSyncTimeout = null
    }
    isLocalAutoBackupRunning = false
    localAutoSyncStarted = false
  }
}

export async function getBackupData() {
  return JSON.stringify({
    time: new Date().getTime(),
    version: 5,
    localStorage,
    indexedDB: await backupDatabase()
  })
}

/************************************* Backup Utils ************************************** */
export async function handleData(data: Record<string, any>) {
  if (data.version === 1) {
    await clearDatabase()

    for (const { key, value } of data.indexedDB) {
      if (key.startsWith('topic:')) {
        await db.table('topics').add({ id: value.id, messages: value.messages })
      }
      if (key === 'image://avatar') {
        await db.table('settings').add({ id: key, value })
      }
    }

    localStorage.setItem('persist:cherry-studio', data.localStorage['persist:cherry-studio'])
    window.toast.success(i18n.t('message.restore.success'))
    setTimeout(() => window.api.application.relaunch(), 1000)
    return
  }

  if (data.version >= 2) {
    localStorage.setItem('persist:cherry-studio', data.localStorage['persist:cherry-studio'])

    // remove notes_tree from indexedDB
    if (data.indexedDB['notes_tree']) {
      delete data.indexedDB['notes_tree']
    }

    await restoreDatabase(data.indexedDB)

    if (data.version === 3) {
      await db.transaction('rw', db.tables, async (tx) => {
        await db.table('message_blocks').clear()
        await upgradeToV7(tx)
      })
    }

    if (data.version === 4) {
      await db.transaction('rw', db.tables, async (tx) => {
        await upgradeToV8(tx)
      })
    }

    window.toast.success(i18n.t('message.restore.success'))
    setTimeout(() => window.api.application.relaunch(), 1000)
    return
  }

  window.toast.error(i18n.t('error.backup.file_format'))
}

async function backupDatabase() {
  const tables = db.tables
  const backup = {}

  for (const table of tables) {
    backup[table.name] = await table.toArray()
  }

  return backup
}

async function restoreDatabase(backup: Record<string, any>) {
  await db.transaction('rw', db.tables, async () => {
    for (const tableName in backup) {
      await db.table(tableName).clear()
      await db.table(tableName).bulkAdd(backup[tableName])
    }
  })
}

async function clearDatabase() {
  const storeNames = db.tables.map((table) => table.name)

  await db.transaction('rw', db.tables, async () => {
    for (const storeName of storeNames) {
      await db[storeName].clear()
    }
  })
}

/**
 * Backup to local directory
 */
export async function backupToLocal({
  showMessage = false,
  customFileName = '',
  autoBackupProcess = false
}: {
  showMessage?: boolean
  customFileName?: string
  autoBackupProcess?: boolean
} = {}) {
  // notificationService is imported as a module-level singleton
  if (isManualBackupRunning) {
    logger.verbose('Manual backup already in progress')
    return
  }
  // force set showMessage to false when auto backup process
  if (autoBackupProcess) {
    showMessage = false
  }

  isManualBackupRunning = true

  setLocalBackupSyncState({ syncing: true, lastSyncError: null })

  const { localBackupDirSetting, localBackupMaxBackups, localBackupSkipBackupFile } =
    await preferenceService.getMultiple({
      localBackupDirSetting: 'data.backup.local.dir',
      localBackupMaxBackups: 'data.backup.local.max_backups',
      localBackupSkipBackupFile: 'data.backup.local.skip_backup_file'
    })
  const localBackupDir = await window.api.resolvePath(localBackupDirSetting)
  let deviceType = 'unknown'
  let hostname = 'unknown'
  try {
    deviceType = (await window.api.system.getDeviceType()) || 'unknown'
    hostname = (await window.api.system.getHostname()) || 'unknown'
  } catch (error) {
    logger.error('Failed to get device type or hostname:', error as Error)
  }
  const timestamp = dayjs().format('YYYYMMDDHHmmss')
  const backupFileName = customFileName || `cherry-studio.${timestamp}.${hostname}.${deviceType}.zip`
  const finalFileName = backupFileName.endsWith('.zip') ? backupFileName : `${backupFileName}.zip`

  try {
    // Use direct backup method (copy IndexedDB/LocalStorage directories)
    const result = await window.api.backup.backupToLocalDir(finalFileName, {
      localBackupDir,
      skipBackupFile: localBackupSkipBackupFile
    })

    if (result) {
      setLocalBackupSyncState({
        lastSyncError: null
      })

      if (showMessage) {
        void notificationService.send({
          id: uuid(),
          type: 'success',
          title: i18n.t('common.success'),
          message: i18n.t('message.backup.success'),
          silent: false,
          timestamp: Date.now(),
          source: 'backup'
        })
      }

      // Clean up old backups if maxBackups is set
      if (localBackupMaxBackups > 0) {
        try {
          // Get all backup files
          const files = await window.api.backup.listLocalBackupFiles(localBackupDir)

          // Filter backups for current device
          const currentDeviceFiles = files.filter((file) => {
            return file.fileName.includes(deviceType) && file.fileName.includes(hostname)
          })

          if (currentDeviceFiles.length > localBackupMaxBackups) {
            // Sort by modified time (oldest first)
            const filesToDelete = currentDeviceFiles
              .sort((a, b) => new Date(a.modifiedTime).getTime() - new Date(b.modifiedTime).getTime())
              .slice(0, currentDeviceFiles.length - localBackupMaxBackups)

            // Delete older backups
            for (const file of filesToDelete) {
              logger.verbose(`[LocalBackup] Deleting old backup: ${file.fileName}`)
              await window.api.backup.deleteLocalBackupFile(file.fileName, localBackupDir)
            }
          }
        } catch (error) {
          logger.error('[LocalBackup] Failed to clean up old backups:', error as Error)
        }
      }
    } else {
      if (autoBackupProcess) {
        throw new Error(i18n.t('message.backup.failed'))
      }

      setLocalBackupSyncState({
        lastSyncError: 'Backup failed'
      })

      if (showMessage) {
        window.modal.error({
          title: i18n.t('message.backup.failed'),
          content: 'Backup failed'
        })
      }
    }

    return result
  } catch (error: any) {
    if (autoBackupProcess) {
      throw error
    }

    logger.error('[LocalBackup] Backup failed:', error)

    setLocalBackupSyncState({
      lastSyncError: error.message || 'Unknown error'
    })

    if (showMessage) {
      window.modal.error({
        title: i18n.t('message.backup.failed'),
        content: error.message || 'Unknown error'
      })
    }

    throw error
  } finally {
    if (!autoBackupProcess) {
      setLocalBackupSyncState({
        lastSyncTime: Date.now(),
        syncing: false
      })
    }
    isManualBackupRunning = false
  }
}

export async function restoreFromLocal(fileName: string) {
  try {
    const localBackupDirSetting = await preferenceService.get('data.backup.local.dir')
    const localBackupDir = await window.api.resolvePath(localBackupDirSetting)
    const restoreData = await window.api.backup.restoreFromLocalBackup(fileName, localBackupDir)

    // Direct backup format (version 6+) returns undefined - app needs to relaunch
    if (!restoreData) {
      logger.info('[LocalBackup] Direct backup restored, app will restart')
      return true
    }

    // Legacy backup format (version <= 5) returns JSON string
    const data = JSON.parse(restoreData)
    await handleData(data)

    return true
  } catch (error) {
    logger.error('[LocalBackup] Restore failed:', error as Error)
    window.toast.error(i18n.t('error.backup.file_format'))
    throw error
  }
}
