import { MARK_TYPE_CONFIG } from '../components/MarkTypeStrategy/MarkStrategy.tsx'
import { NostrController } from '../controllers/NostrController.ts'
import store from '../store/store.ts'
import { Meta } from '../types'
import { PdfPage } from '../types/drawing.ts'
import { MOST_COMMON_MEDIA_TYPES } from './const.ts'
import { extractMarksFromSignedMeta } from './mark.ts'
import { hexToNpub } from './nostr.ts'
import {
  addMarks,
  groupMarksByFileNamePage,
  isPdf,
  pdfToImages
} from './pdf.ts'
import JSZip from 'jszip'

export const getZipWithFiles = async (
  meta: Meta,
  files: { [filename: string]: SigitFile }
): Promise<JSZip> => {
  const zip = new JSZip()
  const marks = extractMarksFromSignedMeta(meta)
  const marksByFileNamePage = groupMarksByFileNamePage(marks)

  for (const [fileName, file] of Object.entries(files)) {
    // Handle PDF Files, add marks
    if (file.isPdf && fileName in marksByFileNamePage) {
      const marksToAdd = marksByFileNamePage[fileName]
      if (meta.keys) {
        for (let i = 0; i < marks.length; i++) {
          const m = marks[i]
          const { sender, keys } = meta.keys
          const usersPubkey = store.getState().auth.usersPubkey!
          const usersNpub = hexToNpub(usersPubkey)
          if (usersNpub in keys) {
            const encryptionKey = await NostrController.getInstance()
              .nip04Decrypt(sender, keys[usersNpub])
              .catch((err) => {
                console.log(
                  'An error occurred in decrypting encryption key',
                  err
                )
                return null
              })

            try {
              const { fetchAndDecrypt } = MARK_TYPE_CONFIG[m.type] || {}
              if (
                typeof fetchAndDecrypt === 'function' &&
                m.value &&
                encryptionKey
              ) {
                // Fetch and decrypt the original file
                const link = m.value.split('/')
                const decrypted = await fetchAndDecrypt(m.value, encryptionKey)

                // Save decrypted
                zip.file(
                  `signatures/${link[link.length - 1]}.json`,
                  new Blob([decrypted])
                )
                marks[i].value = decrypted
              }
            } catch (error) {
              console.error(`Error during mark fetchAndDecrypt phase`, error)
            }
          }
        }
      }
      const blob = await addMarks(file, marksToAdd)
      zip.file(`marked/${fileName}`, blob)
    }

    // Save original files
    zip.file(`files/${fileName}`, file)
  }

  return zip
}

/**
 * Converts a PDF ArrayBuffer to a generic PDF File
 * @param arrayBuffer of a PDF
 * @param fileName identifier of the pdf file
 * @param type optional file type (defaults to pdf)
 */
export const toFile = (
  arrayBuffer: ArrayBuffer,
  fileName: string,
  type: string = 'application/pdf'
): File => {
  const blob = new Blob([arrayBuffer], { type })
  return new File([blob], fileName, { type })
}

export class SigitFile extends File {
  extension: string
  isPdf: boolean
  isImage: boolean

  pages?: PdfPage[]
  objectUrl?: string

  constructor(file: File) {
    super([file], file.name, { type: file.type })
    this.isPdf = isPdf(this)
    this.isImage = isImage(this)
    this.extension = extractFileExtension(this.name)
  }

  async process() {
    if (this.isPdf) this.pages = await pdfToImages(await this.arrayBuffer())
    if (this.isImage) this.objectUrl = URL.createObjectURL(this)
  }
}

export const getSigitFile = async (file: File) => {
  const sigitFile = new SigitFile(file)
  // Process sigit file
  // - generate pages for PDF files
  // - generate ObjectRL for image files
  await sigitFile.process()
  return sigitFile
}

/**
 * Takes an ArrayBuffer and converts to Sigit's Internal File type
 * @param arrayBuffer
 * @param fileName
 */
export const convertToSigitFile = async (
  arrayBuffer: ArrayBuffer,
  fileName: string
): Promise<SigitFile> => {
  const type = getMediaType(extractFileExtension(fileName))
  const file = toFile(arrayBuffer, fileName, type)
  const sigitFile = await getSigitFile(file)
  return sigitFile
}

/**
 * @param fileNames - List of filenames to check
 * @returns List of extensions and if all are same
 */
export const extractFileExtensions = (fileNames: string[]) => {
  const extensions = fileNames.reduce((result: string[], file: string) => {
    const extension = file.split('.').pop()
    if (extension) {
      result.push(extension)
    }
    return result
  }, [])

  const isSame = extensions.every((ext) => ext === extensions[0])

  return { extensions, isSame }
}

/**
 * @param fileName - Filename to check
 * @returns Extension string
 */
export const extractFileExtension = (fileName: string) => {
  const parts = fileName.split('.')
  return parts[parts.length - 1]
}

export const getMediaType = (extension: string) => {
  return MOST_COMMON_MEDIA_TYPES.get(extension)
}

export const isImage = (file: File) => {
  const validImageMediaTypes = [
    'image/png',
    'image/jpeg',
    'image/jpg',
    'image/gif',
    'image/svg+xml',
    'image/bmp',
    'image/x-icon'
  ]

  return validImageMediaTypes.includes(file.type.toLowerCase())
}
