import { cacheService } from '@data/CacheService'
import { loggerService } from '@logger'
import db from '@renderer/databases'
import i18n from '@renderer/i18n'
import type { FileMetadata } from '@renderer/types/file'
import { getFileDirectory } from '@renderer/utils/file'
import dayjs from 'dayjs'

const logger = loggerService.withContext('FileManager')

/**
 * @deprecated Slated for v2 redesign — do not extend.
 *
 * This class predates the v2 file module and bundles three unrelated
 * responsibilities that should be split apart:
 *
 *   1. **Dexie `db.files` CRUD + reference counting** — superseded by the
 *      v2 `file_entry` + `file_ref` tables. Renderer never writes those
 *      directly; it goes through File IPC (`createInternalEntry`,
 *      `ensureExternalEntry`, `permanentDelete`, …).
 *   2. **Thin IPC wrappers** (`selectFiles`, `readBinaryImage`, …) — should
 *      either be inlined at call sites or moved into a focused IPC client.
 *   3. **Pure utility helpers** (`getFilePath`, `getSafePath`, `getFileUrl`,
 *      `isDangerFile`, `formatFileName`) — belong in `@renderer/utils/file`.
 *
 * Phase 2 Batch 0 attempted to migrate `addFile` / `uploadFile` / `deleteFile`
 * to v2 IPC in place, but doing so broke the v1 contract: the v1 `addFile`
 * was a Dexie-only reference-count operation that assumed the physical file
 * was already on disk (written upstream by `saveBase64Image` / `download` /
 * etc.), whereas the v2 path-based `createInternalEntry` re-copies the file
 * and mints a new uuid. Production callers (`imageCallbacks`, paintings
 * pages) discard the return value, leaving a duplicate physical copy plus
 * an unreferenced `file_entry`, while the business object still points at
 * the original (now entry-less) uuid.
 *
 * The cutover is being reverted here and the class re-marked as legacy
 * pending its v2-shaped replacement. The replacement work is tracked
 * separately; until it lands, **do not add new call sites** — write straight
 * to File IPC (`window.api.file.createInternalEntry` etc.) from new code.
 */
class FileManager {
  static async selectFiles(options?: Electron.OpenDialogOptions): Promise<FileMetadata[] | null> {
    return await window.api.file.select(options)
  }

  static async addFile(file: FileMetadata): Promise<FileMetadata> {
    const fileRecord = await db.files.get(file.id)

    if (fileRecord) {
      await db.files.update(fileRecord.id, { ...fileRecord, count: fileRecord.count + 1 })
      return fileRecord
    }

    await db.files.add(file)

    return file
  }

  static async addFiles(files: FileMetadata[]): Promise<FileMetadata[]> {
    return Promise.all(files.map((file) => this.addFile(file)))
  }

  static async readBinaryImage(file: FileMetadata): Promise<Buffer> {
    const fileData = await window.api.file.binaryImage(file.id + file.ext)
    return fileData.data
  }

  static async readBase64File(file: FileMetadata): Promise<string> {
    const fileData = await window.api.file.base64File(file.id + file.ext)
    return fileData.data
  }

  static async addBase64File(file: FileMetadata): Promise<FileMetadata> {
    logger.info(`Adding base64 file: ${JSON.stringify(file)}`)

    const base64File = await window.api.file.base64File(file.id + file.ext)
    const fileRecord = await db.files.get(base64File.id)

    if (fileRecord) {
      await db.files.update(fileRecord.id, { ...fileRecord, count: fileRecord.count + 1 })
      return fileRecord
    }

    await db.files.add(base64File)

    return base64File
  }

  static async uploadFile(file: FileMetadata): Promise<FileMetadata> {
    logger.info(`Uploading file: ${JSON.stringify(file)}`)

    const uploadFile = await window.api.file.upload(file)
    logger.info('Uploaded file:', uploadFile)
    const fileRecord = await db.files.get(uploadFile.id)

    if (fileRecord) {
      await db.files.update(fileRecord.id, { ...fileRecord, count: fileRecord.count + 1 })
      return fileRecord
    }

    await db.files.add(uploadFile)

    return uploadFile
  }

  static async uploadFiles(files: FileMetadata[]): Promise<FileMetadata[]> {
    return Promise.all(files.map((file) => this.uploadFile(file)))
  }

  static async getFile(id: string): Promise<FileMetadata | undefined> {
    const file = await db.files.get(id)

    if (file) {
      const filesPath = cacheService.get('app.path.files') ?? ''
      file.path = filesPath + '/' + file.id + file.ext
    }

    return file
  }

  static getFilePath(file: FileMetadata) {
    const filesPath = cacheService.get('app.path.files') ?? ''
    return filesPath + '/' + file.id + file.ext
  }

  static async deleteFile(id: string, force: boolean = false): Promise<void> {
    const file = await this.getFile(id)

    logger.info('Deleting file:', file)

    if (!file) {
      return
    }

    if (!force) {
      if (file.count > 1) {
        await db.files.update(id, { ...file, count: file.count - 1 })
        return
      }
    }

    await db.files.delete(id)

    try {
      await window.api.file.delete(id + file.ext)
    } catch (error) {
      logger.error('Failed to delete file:', error as Error)
    }
  }

  static async deleteFiles(files: FileMetadata[]): Promise<void> {
    if (!files || files.length === 0) return

    const results = await Promise.allSettled(files.map((file) => this.deleteFile(file.id)))

    const failed = results.filter((r) => r.status === 'rejected')
    if (failed.length > 0) {
      logger.warn(`File deletions completed with ${failed.length} files failed to delete:`, failed)
    }
  }

  static async allFiles(): Promise<FileMetadata[]> {
    return db.files.toArray()
  }

  static isDangerFile(file: FileMetadata) {
    return ['.sh', '.bat', '.cmd', '.ps1', '.vbs', 'reg'].includes(file.ext)
  }

  static getSafePath(file: FileMetadata) {
    // use the path from the file metadata instead
    // this function is used to get path for files which are not in the filestorage
    return this.isDangerFile(file) ? getFileDirectory(file.path) : file.path
  }

  static getFileUrl(file: FileMetadata) {
    const filesPath = cacheService.get('app.path.files') ?? ''
    return 'file://' + filesPath + '/' + file.name
  }

  static async updateFile(file: FileMetadata) {
    if (!file.origin_name.includes(file.ext)) {
      file.origin_name = file.origin_name + file.ext
    }

    await db.files.update(file.id, file)
  }

  static formatFileName(file: FileMetadata) {
    if (!file || !file.origin_name) {
      return ''
    }

    const date = dayjs(file.created_at).format('YYYY-MM-DD')

    if (file.origin_name.includes('pasted_text')) {
      return date + ' ' + i18n.t('message.attachments.pasted_text') + file.ext
    }

    if (file.origin_name.startsWith('temp_file') && file.origin_name.includes('image')) {
      return date + ' ' + i18n.t('message.attachments.pasted_image') + file.ext
    }

    return file.origin_name
  }
}

export default FileManager
