import { Box, Button, Typography } from '@mui/material'
import { MuiFileInput } from 'mui-file-input'
import { useState } from 'react'
import { LoadingSpinner } from '../../components/LoadingSpinner'
import styles from './style.module.scss'
import JSZip from 'jszip'
import { toast } from 'react-toastify'
import {
  encryptArrayBuffer,
  generateEncryptionKey,
  getHash,
  parseJson,
  readContentOfZipEntry,
  sendDM,
  signEventForMetaFile,
  uploadToFileStorage
} from '../../utils'
import { useSelector } from 'react-redux'
import { State } from '../../store/rootReducer'
import { NostrController } from '../../controllers'

export const SignDocument = () => {
  const [selectedFile, setSelectedFile] = useState<File | null>(null)

  const [isLoading, setIsLoading] = useState(false)
  const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
  const [isDraggingOver, setIsDraggingOver] = useState(false)
  const [authUrl, setAuthUrl] = useState<string>()

  const usersPubkey = useSelector((state: State) => state.auth.usersPubkey)

  const nostrController = NostrController.getInstance()

  const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
    event.preventDefault()
    setIsDraggingOver(false)
    const file = event.dataTransfer.files[0]
    if (file.type === 'application/zip') setSelectedFile(file)
  }

  const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {
    event.preventDefault()
    setIsDraggingOver(true)
  }

  const handleSign = async () => {
    if (!selectedFile) return

    setIsLoading(true)
    setLoadingSpinnerDesc('Parsing zip file')
    const zip = await JSZip.loadAsync(selectedFile).catch((err) => {
      console.log('err in loading zip file :>> ', err)
      toast.error(err.message || 'An error occurred in loading zip file.')
      return null
    })

    if (!zip) return

    setLoadingSpinnerDesc('Parsing meta.json')

    const metaFileContent = await readContentOfZipEntry(
      zip,
      'meta.json',
      'string'
    )

    if (!metaFileContent) {
      setIsLoading(false)
      return
    }

    const meta = await parseJson(metaFileContent).catch((err) => {
      console.log('err in parsing the content of meta.json :>> ', err)
      toast.error(
        err.message || 'error occurred in parsing the content of meta.json'
      )
      setIsLoading(false)
      return null
    })

    const hashesFileContent = await readContentOfZipEntry(
      zip,
      'hashes.json',
      'string'
    )

    if (!hashesFileContent) {
      setIsLoading(false)
      return
    }

    const hashes = await parseJson(hashesFileContent).catch((err) => {
      console.log('err in parsing the content of hashes.json :>> ', err)
      toast.error(
        err.message || 'error occurred in parsing the content of hashes.json'
      )
      setIsLoading(false)
      return null
    })

    if (!hashes) return

    setLoadingSpinnerDesc('Generating hashes for files')

    const fileHashes: { [key: string]: string } = {}
    const fileNames = Object.keys(meta.fileHashes)

    // create generate hashes for all entries in files folder of zipArchive
    // these hashes can be used to verify the originality of files
    for (const fileName of fileNames) {
      const filePath = `files/${fileName}`
      const arrayBuffer = await readContentOfZipEntry(
        zip,
        filePath,
        'arraybuffer'
      )

      if (!arrayBuffer) {
        setIsLoading(false)
        return
      }

      const hash = await getHash(arrayBuffer)
      if (!hash) {
        setIsLoading(false)
        return
      }

      fileHashes[fileName] = hash
    }

    // check if the current user is the last signer
    const lastSignerIndex = meta.signers.length - 1
    const signerIndex = meta.signers.indexOf(usersPubkey)
    const isLastSigner = signerIndex === lastSignerIndex

    // if current user is the last signer, then send DMs to all viewers
    if (isLastSigner) {
      for (const viewer of meta.viewers) {
        await signAndSendToNext(zip, meta, hashes, viewer, fileHashes, false)
      }
    } else {
      const nextSigner = meta.signers[signerIndex + 1]

      await signAndSendToNext(zip, meta, hashes, nextSigner, fileHashes, true)
    }

    setIsLoading(false)
  }

  const signAndSendToNext = async (
    zip: JSZip,
    meta: any,
    hashes: any,
    receiver: string,
    fileHashes: {
      [key: string]: string
    },
    isNextSigner: boolean
  ) => {
    setLoadingSpinnerDesc('Signing nostr event for meta file')

    const signedEvent = await signEventForMetaFile(
      receiver,
      fileHashes,
      nostrController,
      setIsLoading
    )
    if (!signedEvent) return

    meta.signedEvents = {
      ...meta.signedEvents,
      [signedEvent.pubkey]: JSON.stringify(signedEvent, null, 2)
    }

    const stringifiedMeta = JSON.stringify(meta, null, 2)
    zip.file('meta.json', stringifiedMeta)

    const metaHash = await getHash(stringifiedMeta)
    if (!metaHash) return

    hashes = {
      ...hashes,
      [usersPubkey!]: metaHash
    }

    zip.file('hashes.json', JSON.stringify(hashes, null, 2))

    const arrayBuffer = await zip
      .generateAsync({
        type: 'arraybuffer',
        compression: 'DEFLATE',
        compressionOptions: {
          level: 6
        }
      })
      .catch((err) => {
        console.log('err in zip:>> ', err)
        setIsLoading(false)
        toast.error(err.message || 'Error occurred in generating zip file')
        return null
      })

    if (!arrayBuffer) return

    const encryptionKey = await generateEncryptionKey()

    setLoadingSpinnerDesc('Encrypting zip file')
    const encryptedArrayBuffer = await encryptArrayBuffer(
      arrayBuffer,
      encryptionKey
    )

    const blob = new Blob([encryptedArrayBuffer])

    setLoadingSpinnerDesc('Uploading zip file to file storage.')
    const fileUrl = await uploadToFileStorage(blob, nostrController)
      .then((url) => {
        toast.success('zip file uploaded to file storage')
        return url
      })
      .catch((err) => {
        console.log('err in upload:>> ', err)
        setIsLoading(false)
        toast.error(err.message || 'Error occurred in uploading zip file')
        return null
      })

    if (!fileUrl) return

    setLoadingSpinnerDesc(
      `Sending DM to next ${isNextSigner ? 'signer' : 'viewer'}`
    )
    await sendDM(
      fileUrl,
      encryptionKey,
      receiver,
      nostrController,
      isNextSigner,
      setAuthUrl
    )
  }

  if (authUrl) {
    return (
      <iframe
        title='Nsecbunker auth'
        src={authUrl}
        width='100%'
        height='500px'
      />
    )
  }

  return (
    <>
      {isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
      <Box className={styles.container}>
        <Typography component='label' variant='h6'>
          Select decrypted zip file
        </Typography>

        <Box
          className={styles.inputBlock}
          onDrop={handleDrop}
          onDragOver={handleDragOver}
        >
          {isDraggingOver && (
            <Box className={styles.fileDragOver}>
              <Typography variant='body1'>Drop file here</Typography>
            </Box>
          )}
          <MuiFileInput
            placeholder='Drop file here, or click to select'
            value={selectedFile}
            onChange={(value) => setSelectedFile(value)}
            InputProps={{
              inputProps: {
                accept: '.zip'
              }
            }}
          />
        </Box>

        <Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
          <Button onClick={handleSign} variant='contained'>
            Sign
          </Button>
        </Box>
      </Box>
    </>
  )
}
