import {
  Box,
  Button,
  IconButton,
  List,
  ListItem,
  ListSubheader,
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableRow,
  Tooltip,
  Typography,
  useTheme
} from '@mui/material'
import axios from 'axios'
import saveAs from 'file-saver'
import JSZip from 'jszip'
import _ from 'lodash'
import { MuiFileInput } from 'mui-file-input'
import { Event, kinds, verifyEvent } from 'nostr-tools'
import { useEffect, useState } from 'react'
import { useSelector } from 'react-redux'
import { useNavigate, useSearchParams, useLocation } from 'react-router-dom'
import { toast } from 'react-toastify'
import { LoadingSpinner } from '../../components/LoadingSpinner'
import { UserComponent } from '../../components/username'
import { MetadataController, NostrController } from '../../controllers'
import { appPublicRoutes } from '../../routes'
import { State } from '../../store/rootReducer'
import {
  CreateSignatureEventContent,
  Meta,
  ProfileMetadata,
  SignedEvent,
  SignedEventContent,
  User,
  UserRole
} from '../../types'
import {
  decryptArrayBuffer,
  encryptArrayBuffer,
  generateEncryptionKey,
  getHash,
  hexToNpub,
  parseJson,
  npubToHex,
  readContentOfZipEntry,
  sendDM,
  shorten,
  signEventForMetaFile,
  uploadToFileStorage,
  isOnline,
  generateKeysFile
} from '../../utils'
import styles from './style.module.scss'
import {
  Cancel,
  CheckCircle,
  Download,
  HourglassTop
} from '@mui/icons-material'
import CopyModal from '../../components/copyModal'
enum SignedStatus {
  Fully_Signed,
  User_Is_Next_Signer,
  User_Is_Not_Next_Signer
}

export const SignPage = () => {
  const navigate = useNavigate()
  const location = useLocation()
  const { arrayBuffer: decryptedArrayBuffer } = location.state || {}

  const [searchParams] = useSearchParams()

  const [displayInput, setDisplayInput] = useState(false)

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

  const [zip, setZip] = useState<JSZip>()

  const [isLoading, setIsLoading] = useState(true)
  const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
  const [openCopyModal, setOpenCopyModel] = useState(false)
  const [textToCopy, setTextToCopy] = useState('')

  const [meta, setMeta] = useState<Meta | null>(null)
  const [signedStatus, setSignedStatus] = useState<SignedStatus>()

  const [submittedBy, setSubmittedBy] = useState<string>()

  const [signers, setSigners] = useState<`npub1${string}`[]>([])
  const [viewers, setViewers] = useState<`npub1${string}`[]>([])
  const [creatorFileHashes, setCreatorFileHashes] = useState<{
    [key: string]: string
  }>({})
  const [currentFileHashes, setCurrentFileHashes] = useState<{
    [key: string]: string | null
  }>({})

  const [signedBy, setSignedBy] = useState<`npub1${string}`[]>([])

  const [nextSinger, setNextSinger] = useState<string>()

  // This state variable indicates whether the logged-in user is a signer, a creator, or neither.
  const [isSignerOrCreator, setIsSignerOrCreator] = useState(false)

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

  const [authUrl, setAuthUrl] = useState<string>()
  const nostrController = NostrController.getInstance()

  useEffect(() => {
    if (zip) {
      const generateCurrentFileHashes = async () => {
        const fileHashes: { [key: string]: string | null } = {}
        const fileNames = Object.values(zip.files)
          .filter((entry) => entry.name.startsWith('files/') && !entry.dir)
          .map((entry) => entry.name)

        // 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 arrayBuffer = await readContentOfZipEntry(
            zip,
            fileName,
            'arraybuffer'
          )

          if (arrayBuffer) {
            const hash = await getHash(arrayBuffer)

            if (hash) {
              fileHashes[fileName.replace(/^files\//, '')] = hash
            }
          } else {
            fileHashes[fileName.replace(/^files\//, '')] = null
          }
        }

        setCurrentFileHashes(fileHashes)
      }

      generateCurrentFileHashes()
    }
  }, [zip])

  useEffect(() => {
    if (signers.length > 0) {
      // check if all signers have signed then its fully signed
      if (signers.every((signer) => signedBy.includes(signer))) {
        setSignedStatus(SignedStatus.Fully_Signed)
      } else {
        for (const signer of signers) {
          if (!signedBy.includes(signer)) {
            // signers in meta.json are in npub1 format
            // so, convert it to hex before setting to nextSigner
            setNextSinger(npubToHex(signer)!)

            const usersNpub = hexToNpub(usersPubkey!)

            if (signer === usersNpub) {
              // logged in user is the next signer
              setSignedStatus(SignedStatus.User_Is_Next_Signer)
            } else {
              setSignedStatus(SignedStatus.User_Is_Not_Next_Signer)
            }

            break
          }
        }
      }
    } else {
      // there's no signer just viewers. So its fully signed
      setSignedStatus(SignedStatus.Fully_Signed)
    }

    // Determine and set the status of the user
    if (submittedBy && usersPubkey && submittedBy === usersPubkey) {
      // If the submission was made by the user, set the status to true
      setIsSignerOrCreator(true)
    } else if (usersPubkey) {
      // Convert the user's public key from hex to npub format
      const usersNpub = hexToNpub(usersPubkey)
      if (signers.includes(usersNpub)) {
        // If the user's npub is in the list of signers, set the status to true
        setIsSignerOrCreator(true)
      }
    }
  }, [signers, signedBy, usersPubkey, submittedBy])

  useEffect(() => {
    const fileUrl = searchParams.get('file')

    if (fileUrl) {
      setIsLoading(true)
      setLoadingSpinnerDesc('Fetching file from file server')

      axios
        .get(fileUrl, {
          responseType: 'arraybuffer'
        })
        .then((res) => {
          const fileName = fileUrl.split('/').pop()
          const file = new File([res.data], fileName!)

          decrypt(file).then((arrayBuffer) => {
            if (arrayBuffer) handleDecryptedArrayBuffer(arrayBuffer)
          })
        })
        .catch((err) => {
          console.error(`error occurred in getting file from ${fileUrl}`, err)
          toast.error(
            err.message || `error occurred in getting file from ${fileUrl}`
          )
        })
        .finally(() => {
          setIsLoading(false)
        })
    } else if (decryptedArrayBuffer) {
      handleDecryptedArrayBuffer(decryptedArrayBuffer).finally(() =>
        setIsLoading(false)
      )
    } else {
      setIsLoading(false)
      setDisplayInput(true)
    }
  }, [searchParams, decryptedArrayBuffer])

  const parseKeysJson = async (zip: JSZip) => {
    const keysFileContent = await readContentOfZipEntry(
      zip,
      'keys.json',
      'string'
    )

    if (!keysFileContent) return null

    const parsedJSON = await parseJson<{ sender: string; keys: string[] }>(
      keysFileContent
    ).catch((err) => {
      console.log(`Error parsing content of keys.json:`, err)
      toast.error(err.message || `Error parsing content of keys.json`)
      return null
    })

    return parsedJSON
  }

  const decrypt = async (file: File) => {
    setLoadingSpinnerDesc('Decrypting file')

    const zip = await JSZip.loadAsync(file).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

    const parsedKeysJson = await parseKeysJson(zip)
    if (!parsedKeysJson) return

    const encryptedArrayBuffer = await readContentOfZipEntry(
      zip,
      'compressed.sigit',
      'arraybuffer'
    )

    if (!encryptedArrayBuffer) return

    const { keys, sender } = parsedKeysJson

    for (const key of keys) {
      // Set up event listener for authentication event
      nostrController.on('nsecbunker-auth', (url) => {
        setAuthUrl(url)
      })

      // Set up timeout promise to handle encryption timeout
      const timeoutPromise = new Promise<never>((_, reject) => {
        setTimeout(() => {
          reject(new Error('Timeout occurred'))
        }, 60000) // Timeout duration = 60 seconds
      })

      // decrypt the encryptionKey, with timeout
      const encryptionKey = await Promise.race([
        nostrController.nip04Decrypt(sender, key),
        timeoutPromise
      ])
        .then((res) => {
          return res
        })
        .catch((err) => {
          console.log('err :>> ', err)
          return null
        })
        .finally(() => {
          setAuthUrl(undefined) // Clear authentication URL
        })

      console.log('encryptionKey :>> ', encryptionKey)

      // Return if encryption failed
      if (!encryptionKey) continue

      const arrayBuffer = await decryptArrayBuffer(
        encryptedArrayBuffer,
        encryptionKey
      )
        .catch((err) => {
          console.log('err in decryption:>> ', err)
          return null
        })
        .finally(() => {
          setIsLoading(false)
        })

      if (arrayBuffer) return arrayBuffer
    }

    return null
  }

  const handleDecryptedArrayBuffer = async (arrayBuffer: ArrayBuffer) => {
    const decryptedZipFile = new File([arrayBuffer], 'decrypted.zip')

    setLoadingSpinnerDesc('Parsing zip file')

    const zip = await JSZip.loadAsync(decryptedZipFile).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

    setZip(zip)
    setDisplayInput(false)

    setLoadingSpinnerDesc('Parsing meta.json')

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

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

    const parsedMetaJson = await parseJson<Meta>(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
      }
    )

    if (!parsedMetaJson) return

    const createSignatureEvent = await parseJson<Event>(
      parsedMetaJson.createSignature
    ).catch((err) => {
      console.log('err in parsing the createSignature event:>> ', err)
      toast.error(
        err.message || 'error occurred in parsing the create signature event'
      )
      setIsLoading(false)
      return null
    })

    if (!createSignatureEvent) return

    const isValidCreateSignature = verifyEvent(createSignatureEvent)

    if (!isValidCreateSignature) {
      toast.error('Create signature is invalid')
      setIsLoading(false)
      return
    }

    const createSignatureContent = await parseJson<CreateSignatureEventContent>(
      createSignatureEvent.content
    ).catch((err) => {
      console.log(
        `err in parsing the createSignature event's content :>> `,
        err
      )
      toast.error(
        err.message ||
          `error occurred in parsing the create signature event's content`
      )
      setIsLoading(false)
      return null
    })

    if (!createSignatureContent) return

    setSigners(createSignatureContent.signers)
    setViewers(createSignatureContent.viewers)
    setCreatorFileHashes(createSignatureContent.fileHashes)
    setSubmittedBy(createSignatureEvent.pubkey)

    setSignedBy(Object.keys(parsedMetaJson.docSignatures) as `npub1${string}`[])

    setMeta(parsedMetaJson)
  }

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

    setIsLoading(true)
    const arrayBuffer = await decrypt(selectedFile)

    if (!arrayBuffer) return

    handleDecryptedArrayBuffer(arrayBuffer)
  }

  const handleSign = async () => {
    if (!zip || !meta) return

    setIsLoading(true)
    setLoadingSpinnerDesc('parsing hashes.json file')

    const hashesFileContent = await readHashesFile()
    if (!hashesFileContent) return

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

    const hashes = await parseHashes(hashesFileContent)
    if (!hashes) return

    setLoadingSpinnerDesc('Generating hashes for files')

    setLoadingSpinnerDesc('Signing nostr event')

    const prevSig = getPrevSignersSig(hexToNpub(usersPubkey!))
    if (!prevSig) return

    const signedEvent = await signEventForMeta(prevSig)
    if (!signedEvent) return

    const updatedMeta = updateMetaSignatures(meta, signedEvent)

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

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

    const updatedHashes = updateHashes(hashes, metaHash)
    zip.file('hashes.json', JSON.stringify(updatedHashes, null, 2))

    const arrayBuffer = await generateZipArrayBuffer(zip)
    if (!arrayBuffer) return

    const key = await generateEncryptionKey()

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

    const finalZipFile = await createFinalZipFile(encryptedArrayBuffer, key)

    if (!finalZipFile) return

    if (await isOnline()) {
      await handleOnlineFlow(finalZipFile)
    }

    handleDecryptedArrayBuffer(arrayBuffer).finally(() => setIsLoading(false))
  }

  // Read the content of the hashes.json file
  const readHashesFile = async (): Promise<string | null> => {
    return await readContentOfZipEntry(zip!, 'hashes.json', 'string').catch(
      (err) => {
        console.log('Error reading hashes.json file:', err)
        setIsLoading(false)
        return null
      }
    )
  }

  // Parse the JSON content of the hashes file
  const parseHashes = async (
    hashesFileContent: string
  ): Promise<Record<string, string> | null> => {
    return await parseJson<Record<string, string>>(hashesFileContent).catch(
      (err) => {
        console.log('Error parsing hashes.json content:', err)
        toast.error(err.message || 'Error parsing hashes.json content')
        setIsLoading(false)
        return null
      }
    )
  }

  // Sign the event for the meta file
  const signEventForMeta = async (prevSig: string) => {
    return await signEventForMetaFile(
      JSON.stringify({ prevSig }),
      nostrController,
      setIsLoading
    )
  }

  // Update the meta signatures
  const updateMetaSignatures = (meta: Meta, signedEvent: SignedEvent): Meta => {
    const metaCopy = _.cloneDeep(meta)
    metaCopy.docSignatures = {
      ...metaCopy.docSignatures,
      [hexToNpub(signedEvent.pubkey)]: JSON.stringify(signedEvent, null, 2)
    }
    return metaCopy
  }

  // Update the hashes with the new meta hash
  const updateHashes = (
    hashes: Record<string, string>,
    metaHash: string
  ): Record<string, string> => {
    return {
      ...hashes,
      [usersPubkey!]: metaHash
    }
  }

  // Generate the zip array buffer
  const generateZipArrayBuffer = async (
    zip: JSZip
  ): Promise<ArrayBuffer | null> => {
    return await zip
      .generateAsync({
        type: 'arraybuffer',
        compression: 'DEFLATE',
        compressionOptions: {
          level: 6
        }
      })
      .catch((err) => {
        console.log('Error generating zip file:', err)
        setIsLoading(false)
        toast.error(err.message || 'Error generating zip file')
        return null
      })
  }

  // create final zip file
  const createFinalZipFile = async (
    encryptedArrayBuffer: ArrayBuffer,
    encryptionKey: string
  ): Promise<File | null> => {
    // Get the current timestamp in seconds
    const unixNow = Math.floor(Date.now() / 1000)
    const blob = new Blob([encryptedArrayBuffer])
    // Create a File object with the Blob data
    const file = new File([blob], `compressed.sigit`, {
      type: 'application/sigit'
    })

    const isLastSigner = checkIsLastSigner(signers)

    const userSet = new Set<string>()

    if (isLastSigner) {
      if (submittedBy) {
        userSet.add(submittedBy)
      }

      signers.forEach((signer) => {
        userSet.add(npubToHex(signer)!)
      })

      viewers.forEach((viewer) => {
        userSet.add(npubToHex(viewer)!)
      })
    } else {
      const usersNpub = hexToNpub(usersPubkey!)
      const signerIndex = signers.indexOf(usersNpub)
      const nextSigner = signers[signerIndex + 1]
      userSet.add(npubToHex(nextSigner)!)
    }

    const keysFileContent = await generateKeysFile(
      Array.from(userSet),
      encryptionKey
    )
    if (!keysFileContent) return null

    const zip = new JSZip()
    zip.file(`compressed.sigit`, file)
    zip.file('keys.json', keysFileContent)

    const arraybuffer = await zip
      .generateAsync({
        type: 'arraybuffer',
        compression: 'DEFLATE',
        compressionOptions: { level: 6 }
      })
      .catch(handleZipError)

    if (!arraybuffer) return null

    const finalZipFile = new File(
      [new Blob([arraybuffer])],
      `${unixNow}.sigit.zip`,
      {
        type: 'application/zip'
      }
    )

    return finalZipFile
  }

  // Handle errors during zip file generation
  const handleZipError = (err: any) => {
    console.log('Error in zip:>> ', err)
    setIsLoading(false)
    toast.error(err.message || 'Error occurred in generating zip file')
    return null
  }

  // Handle the online flow: upload file and send DMs
  const handleOnlineFlow = async (file: File) => {
    const fileUrl = await uploadZipFile(file)
    if (!fileUrl) return

    const isLastSigner = checkIsLastSigner(signers)

    if (isLastSigner) {
      await sendDMToAllUsers(fileUrl)
    } else {
      await sendDMToNextSigner(fileUrl)
    }

    setIsLoading(false)
  }

  // Upload the zip file to file storage
  const uploadZipFile = async (file: File): Promise<string | null> => {
    setLoadingSpinnerDesc('Uploading zip file to file storage.')
    const fileUrl = await uploadToFileStorage(file, nostrController)
      .then((url) => {
        toast.success('Zip file uploaded to file storage')
        return url
      })
      .catch((err) => {
        console.log('Error uploading file:', err)
        setIsLoading(false)
        toast.error(err.message || 'Error uploading file')
        return null
      })

    return fileUrl
  }

  // Check if the current user is the last signer
  const checkIsLastSigner = (signers: string[]): boolean => {
    const usersNpub = hexToNpub(usersPubkey!)
    const lastSignerIndex = signers.length - 1
    const signerIndex = signers.indexOf(usersNpub)
    return signerIndex === lastSignerIndex
  }

  // Send DM to all users (signers and viewers)
  const sendDMToAllUsers = async (fileUrl: string) => {
    const userSet = new Set<`npub1${string}`>()

    if (submittedBy) {
      userSet.add(hexToNpub(submittedBy))
    }

    signers.forEach((signer) => {
      userSet.add(signer)
    })

    viewers.forEach((viewer) => {
      userSet.add(viewer)
    })

    const users = Array.from(userSet)

    for (const user of users) {
      await sendDM(
        fileUrl,
        npubToHex(user)!,
        nostrController,
        false,
        setAuthUrl
      )
    }
  }

  // Send DM to the next signer
  const sendDMToNextSigner = async (fileUrl: string) => {
    const usersNpub = hexToNpub(usersPubkey!)
    const signerIndex = signers.indexOf(usersNpub)
    const nextSigner = signers[signerIndex + 1]
    await sendDM(
      fileUrl,
      npubToHex(nextSigner)!,
      nostrController,
      true,
      setAuthUrl
    )
  }

  const handleExport = async () => {
    if (!meta || !zip || !usersPubkey) return

    const usersNpub = hexToNpub(usersPubkey)
    if (
      !signers.includes(usersNpub) &&
      !viewers.includes(usersNpub) &&
      submittedBy !== usersNpub
    )
      return

    setIsLoading(true)
    setLoadingSpinnerDesc('Signing nostr event')

    const prevSig = await getLastSignersSig()
    if (!prevSig) return

    const signedEvent = await signEventForMetaFile(
      JSON.stringify({
        prevSig
      }),
      nostrController,
      setIsLoading
    )

    if (!signedEvent) return

    const exportSignature = JSON.stringify(signedEvent, null, 2)

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

    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 blob = new Blob([arrayBuffer])
    saveAs(blob, 'exported.zip')

    setIsLoading(false)

    navigate(appPublicRoutes.verify)
  }

  const handleExportSigit = async () => {
    if (!zip) return

    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 key = await generateEncryptionKey()

    setLoadingSpinnerDesc('Encrypting zip file')
    const encryptedArrayBuffer = await encryptArrayBuffer(arrayBuffer, key)
    const blob = new Blob([encryptedArrayBuffer])
    saveAs(blob, 'exported.sigit')

    setTextToCopy(key)
    setOpenCopyModel(true)
  }

  /**
   * This function accepts an npub of a signer and return the signature of its previous signer.
   * This prevSig will be used in the content of the provided signer's signedEvent
   */
  const getPrevSignersSig = (npub: string) => {
    if (!meta) return null

    // if user is first signer then use creator's signature
    if (signers[0] === npub) {
      try {
        const createSignatureEvent: Event = JSON.parse(meta.createSignature)
        return createSignatureEvent.sig
      } catch (error) {
        return null
      }
    }

    // find the index of signer
    const currentSignerIndex = signers.findIndex((signer) => signer === npub)
    // return null if could not found user in signer's list
    if (currentSignerIndex === -1) return null
    // find prev signer
    const prevSigner = signers[currentSignerIndex - 1]

    // get the signature of prev signer
    try {
      const prevSignersEvent: Event = JSON.parse(meta.docSignatures[prevSigner])
      return prevSignersEvent.sig
    } catch (error) {
      return null
    }
  }

  /**
   * This function returns the signature of last signer
   * It will be used in the content of export signature's signedEvent
   */
  const getLastSignersSig = () => {
    if (!meta) return null

    // if there're no signers then use creator's signature
    if (signers.length === 0) {
      try {
        const createSignatureEvent: Event = JSON.parse(meta.createSignature)
        return createSignatureEvent.sig
      } catch (error) {
        return null
      }
    }

    // get last signer
    const lastSigner = signers[signers.length - 1]

    // get the signature of last signer
    try {
      const lastSignatureEvent: Event = JSON.parse(
        meta.docSignatures[lastSigner]
      )
      return lastSignatureEvent.sig
    } catch (error) {
      return null
    }
  }

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

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

            <Box className={styles.inputBlock}>
              <MuiFileInput
                placeholder="Select file"
                inputProps={{ accept: '.sigit' }}
                value={selectedFile}
                onChange={(value) => setSelectedFile(value)}
              />
            </Box>

            {selectedFile && (
              <Box sx={{ mt: 2, display: 'flex', justifyContent: 'center' }}>
                <Button onClick={handleDecrypt} variant="contained">
                  Decrypt
                </Button>
              </Box>
            )}
          </>
        )}

        {submittedBy && zip && meta && (
          <>
            <DisplayMeta
              meta={meta}
              zip={zip}
              submittedBy={submittedBy}
              signers={signers}
              viewers={viewers}
              creatorFileHashes={creatorFileHashes}
              currentFileHashes={currentFileHashes}
              signedBy={signedBy}
              nextSigner={nextSinger}
              getPrevSignersSig={getPrevSignersSig}
            />

            {signedStatus === SignedStatus.Fully_Signed && (
              <Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
                <Button onClick={handleExport} variant="contained">
                  Export
                </Button>
              </Box>
            )}

            {signedStatus === SignedStatus.User_Is_Next_Signer && (
              <Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
                <Button onClick={handleSign} variant="contained">
                  Sign
                </Button>
              </Box>
            )}

            {isSignerOrCreator &&
              signedStatus === SignedStatus.User_Is_Not_Next_Signer && (
                <Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
                  <Button onClick={handleExportSigit} variant="contained">
                    Export Sigit
                  </Button>
                </Box>
              )}
          </>
        )}
      </Box>
      <CopyModal
        open={openCopyModal}
        handleClose={() => setOpenCopyModel(false)}
        title="Decryption key for Sigit file"
        textToCopy={textToCopy}
      />
    </>
  )
}

type DisplayMetaProps = {
  meta: Meta
  zip: JSZip
  submittedBy: string
  signers: `npub1${string}`[]
  viewers: `npub1${string}`[]
  creatorFileHashes: { [key: string]: string }
  currentFileHashes: { [key: string]: string | null }
  signedBy: `npub1${string}`[]
  nextSigner?: string
  getPrevSignersSig: (usersNpub: string) => string | null
}

const DisplayMeta = ({
  meta,
  zip,
  submittedBy,
  signers,
  viewers,
  creatorFileHashes,
  currentFileHashes,
  signedBy,
  nextSigner,
  getPrevSignersSig
}: DisplayMetaProps) => {
  const theme = useTheme()

  const textColor = theme.palette.getContrastText(
    theme.palette.background.paper
  )

  const [metadata, setMetadata] = useState<{ [key: string]: ProfileMetadata }>(
    {}
  )
  const [users, setUsers] = useState<User[]>([])

  useEffect(() => {
    signers.forEach((signer) => {
      const hexKey = npubToHex(signer)
      setUsers((prev) => {
        if (prev.findIndex((user) => user.pubkey === hexKey) !== -1) return prev

        return [
          ...prev,
          {
            pubkey: hexKey!,
            role: UserRole.signer
          }
        ]
      })
    })

    viewers.forEach((viewer) => {
      const hexKey = npubToHex(viewer)
      setUsers((prev) => {
        if (prev.findIndex((user) => user.pubkey === hexKey) !== -1) return prev

        return [
          ...prev,
          {
            pubkey: hexKey!,
            role: UserRole.viewer
          }
        ]
      })
    })
  }, [signers, viewers])

  useEffect(() => {
    const metadataController = new MetadataController()

    const hexKeys: string[] = [
      npubToHex(submittedBy)!,
      ...users.map((user) => user.pubkey)
    ]

    hexKeys.forEach((key) => {
      if (!(key in metadata)) {
        const handleMetadataEvent = (event: Event) => {
          const metadataContent =
            metadataController.extractProfileMetadataContent(event)

          if (metadataContent)
            setMetadata((prev) => ({
              ...prev,
              [key]: metadataContent
            }))
        }

        metadataController.on(key, (kind: number, event: Event) => {
          if (kind === kinds.Metadata) {
            handleMetadataEvent(event)
          }
        })

        metadataController
          .findMetadata(key)
          .then((metadataEvent) => {
            if (metadataEvent) handleMetadataEvent(metadataEvent)
          })
          .catch((err) => {
            console.error(`error occurred in finding metadata for: ${key}`, err)
          })
      }
    })
  }, [users, submittedBy])

  const downloadFile = async (filename: string) => {
    const arrayBuffer = await readContentOfZipEntry(
      zip,
      `files/${filename}`,
      'arraybuffer'
    )
    if (!arrayBuffer) return

    const blob = new Blob([arrayBuffer])
    saveAs(blob, filename)
  }

  return (
    <List
      sx={{
        bgcolor: 'background.paper',
        marginTop: 2
      }}
      subheader={
        <ListSubheader className={styles.subHeader}>Meta Info</ListSubheader>
      }
    >
      <ListItem
        sx={{
          marginTop: 1,
          gap: '15px'
        }}
      >
        <Typography variant="h6" sx={{ color: textColor }}>
          Submitted By
        </Typography>
        {(function () {
          const profile = metadata[submittedBy]
          return (
            <UserComponent
              pubkey={submittedBy}
              name={
                profile?.display_name ||
                profile?.name ||
                shorten(hexToNpub(submittedBy))
              }
              image={profile?.picture}
            />
          )
        })()}
      </ListItem>
      <ListItem
        sx={{
          marginTop: 1,
          flexDirection: 'column',
          alignItems: 'flex-start'
        }}
      >
        <Typography variant="h6" sx={{ color: textColor }}>
          Files
        </Typography>
        <Box className={styles.filesWrapper}>
          {Object.entries(currentFileHashes).map(([filename, hash], index) => {
            const isValidHash = creatorFileHashes[filename] === hash

            return (
              <Box key={`file-${index}`} className={styles.file}>
                <Tooltip title="Download File" arrow>
                  <IconButton onClick={() => downloadFile(filename)}>
                    <Download />
                  </IconButton>
                </Tooltip>
                <Typography
                  component="label"
                  sx={{
                    color: textColor,
                    flexGrow: 1
                  }}
                >
                  {filename}
                </Typography>
                {isValidHash && (
                  <Tooltip title="File integrity check passed" arrow>
                    <CheckCircle sx={{ color: theme.palette.success.light }} />
                  </Tooltip>
                )}
                {!isValidHash && (
                  <Tooltip title="File integrity check failed" arrow>
                    <Cancel sx={{ color: theme.palette.error.main }} />
                  </Tooltip>
                )}
              </Box>
            )
          })}
        </Box>
      </ListItem>
      <ListItem sx={{ marginTop: 1 }}>
        <Table>
          <TableHead>
            <TableRow>
              <TableCell className={styles.tableCell}>User</TableCell>
              <TableCell className={styles.tableCell}>Role</TableCell>
              <TableCell>Signed Status</TableCell>
            </TableRow>
          </TableHead>
          <TableBody>
            {users.map((user) => (
              <DisplayUser
                key={user.pubkey}
                meta={meta}
                user={user}
                metadata={metadata}
                signedBy={signedBy}
                nextSigner={nextSigner}
                getPrevSignersSig={getPrevSignersSig}
              />
            ))}
          </TableBody>
        </Table>
      </ListItem>
    </List>
  )
}

enum PrevSignatureValidationEnum {
  Pending,
  Valid,
  Invalid
}

enum UserStatus {
  Viewer = 'Viewer',
  Awaiting = 'Awaiting Signature',
  Signed = 'Signed',
  Pending = 'Pending'
}

type DisplayUserProps = {
  meta: Meta
  user: User
  metadata: { [key: string]: ProfileMetadata }
  signedBy: `npub1${string}`[]
  nextSigner?: string
  getPrevSignersSig: (usersNpub: string) => string | null
}

const DisplayUser = ({
  meta,
  user,
  metadata,
  signedBy,
  nextSigner,
  getPrevSignersSig
}: DisplayUserProps) => {
  const theme = useTheme()

  const userMeta = metadata[user.pubkey]
  const [userStatus, setUserStatus] = useState<UserStatus>(UserStatus.Pending)
  const [prevSignatureStatus, setPreviousSignatureStatus] =
    useState<PrevSignatureValidationEnum>(PrevSignatureValidationEnum.Pending)

  useEffect(() => {
    if (user.role === UserRole.viewer) {
      setUserStatus(UserStatus.Viewer)
      return
    }

    // check if user has signed the document
    const usersNpub = hexToNpub(user.pubkey)
    if (signedBy.includes(usersNpub)) {
      setUserStatus(UserStatus.Signed)
      return
    }

    // check if user is the next signer
    if (user.pubkey === nextSigner) {
      setUserStatus(UserStatus.Awaiting)
      return
    }
  }, [user, nextSigner, signedBy])

  useEffect(() => {
    const validatePrevSignature = async () => {
      const handleNullCase = () => {
        setPreviousSignatureStatus(PrevSignatureValidationEnum.Invalid)
        return
      }

      // get previous signers sig from the content of current signers signed event
      const npub = hexToNpub(user.pubkey)
      const signedEvent = await parseJson<Event>(
        meta.docSignatures[npub]
      ).catch((err) => {
        console.log(`err in parsing the singed event for ${npub}:>> `, err)
        toast.error(
          err.message ||
            'error occurred in parsing the signed event signature event'
        )
        return null
      })

      if (!signedEvent) return handleNullCase()

      // now that we have signed event of current signer, we'll extract prevSig from its content
      const parsedContent = await parseJson<SignedEventContent>(
        signedEvent.content
      ).catch((err) => {
        console.log(
          `an error occurred in parsing the content of signedEvent of ${npub}`,
          err
        )
        toast.error(
          err.message ||
            `an error occurred in parsing the content of signedEvent of ${npub}`
        )
        return null
      })

      if (!parsedContent) return handleNullCase()

      const prevSignersSignature = getPrevSignersSig(npub)

      if (!prevSignersSignature) return handleNullCase()

      setPreviousSignatureStatus(
        parsedContent.prevSig === prevSignersSignature
          ? PrevSignatureValidationEnum.Valid
          : PrevSignatureValidationEnum.Invalid
      )
    }

    if (userStatus === UserStatus.Signed) {
      validatePrevSignature()
    }
  }, [userStatus, meta.docSignatures, user.pubkey, getPrevSignersSig])

  return (
    <TableRow>
      <TableCell className={styles.tableCell}>
        <UserComponent
          pubkey={user.pubkey}
          name={
            userMeta?.display_name ||
            userMeta?.name ||
            shorten(hexToNpub(user.pubkey))
          }
          image={userMeta?.picture}
        />
      </TableCell>
      <TableCell className={styles.tableCell}>{user.role}</TableCell>
      <TableCell>
        <Box sx={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
          <Typography component="label">{userStatus}</Typography>
          {userStatus === UserStatus.Signed && (
            <>
              {prevSignatureStatus === PrevSignatureValidationEnum.Valid && (
                <Tooltip title="Contains valid signature of prev signer" arrow>
                  <CheckCircle sx={{ color: theme.palette.success.light }} />
                </Tooltip>
              )}
              {prevSignatureStatus === PrevSignatureValidationEnum.Invalid && (
                <Tooltip
                  title="Contains invalid signature of prev signer"
                  arrow
                >
                  <Cancel sx={{ color: theme.palette.error.main }} />
                </Tooltip>
              )}
            </>
          )}
          {userStatus === UserStatus.Awaiting && (
            <Tooltip title="Waiting for user's sign" arrow>
              <HourglassTop />
            </Tooltip>
          )}
        </Box>
      </TableCell>
    </TableRow>
  )
}
