import {
  Box,
  Button,
  List,
  ListItem,
  ListSubheader,
  Typography,
  useTheme
} from '@mui/material'
import JSZip from 'jszip'
import { MuiFileInput } from 'mui-file-input'
import { Event, verifyEvent } from 'nostr-tools'
import { useEffect, useState } from 'react'
import { toast } from 'react-toastify'
import { LoadingSpinner } from '../../components/LoadingSpinner'
import { UserComponent } from '../../components/username'
import { MetadataController } from '../../controllers'
import { CreateSignatureEventContent, Meta, ProfileMetadata } from '../../types'
import {
  getHash,
  hexToNpub,
  npubToHex,
  parseJson,
  readContentOfZipEntry,
  shorten
} from '../../utils'
import styles from './style.module.scss'

export const VerifyPage = () => {
  const theme = useTheme()

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

  const [isLoading, setIsLoading] = useState(false)
  const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')

  const [selectedFile, setSelectedFile] = useState<File | null>(null)
  const [zip, setZip] = useState<JSZip>()
  const [meta, setMeta] = useState<Meta | null>(null)

  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 [metadata, setMetadata] = useState<{ [key: string]: ProfileMetadata }>(
    {}
  )

  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 (submittedBy) {
      const metadataController = new MetadataController()

      const users = [submittedBy, ...signers, ...viewers]

      users.forEach((user) => {
        const pubkey = npubToHex(user)!

        if (!(pubkey in metadata)) {
          metadataController
            .findMetadata(pubkey)
            .then((metadataEvent) => {
              const metadataContent =
                metadataController.extractProfileMetadataContent(metadataEvent)
              if (metadataContent)
                setMetadata((prev) => ({
                  ...prev,
                  [pubkey]: metadataContent
                }))
            })
            .catch((err) => {
              console.error(
                `error occurred in finding metadata for: ${user}`,
                err
              )
            })
        }
      })
    }
  }, [submittedBy, signers, viewers])

  const handleVerify = async () => {
    if (!selectedFile) return
    setIsLoading(true)

    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
    setZip(zip)

    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)

    setMeta(parsedMetaJson)
    setIsLoading(false)
  }

  const displayUser = (pubkey: string, verifySignature = false) => {
    const profile = metadata[pubkey]

    let isValidSignature = false

    if (verifySignature) {
      const npub = hexToNpub(pubkey)
      const signedEventString = meta ? meta.docSignatures[npub] : null
      if (signedEventString) {
        try {
          const signedEvent = JSON.parse(signedEventString)
          isValidSignature = verifyEvent(signedEvent)
        } catch (error) {
          console.error(
            `An error occurred in parsing and verifying the signature event for ${pubkey}`,
            error
          )
        }
      }
    }

    return (
      <>
        <UserComponent
          pubkey={pubkey}
          name={
            profile?.display_name || profile?.name || shorten(hexToNpub(pubkey))
          }
          image={profile?.picture}
        />

        {verifySignature && (
          <Typography
            component="label"
            sx={{
              color: isValidSignature
                ? theme.palette.success.light
                : theme.palette.error.main
            }}
          >
            {isValidSignature ? 'Valid' : 'Invalid'} Signature
          </Typography>
        )}
      </>
    )
  }

  const displayExportedBy = () => {
    if (!meta || !meta.exportSignature) return null

    const exportSignatureString = meta.exportSignature

    try {
      const exportSignatureEvent = JSON.parse(exportSignatureString) as Event

      if (verifyEvent(exportSignatureEvent)) {
        return displayUser(exportSignatureEvent.pubkey)
      } else {
        toast.error(`Invalid export signature!`)
        return (
          <Typography component="label" sx={{ color: 'red' }}>
            Invalid export signature
          </Typography>
        )
      }
    } catch (error) {
      console.error(`An error occurred wile parsing exportSignature`, error)
      return null
    }
  }

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

            <MuiFileInput
              placeholder="Select file"
              value={selectedFile}
              onChange={(value) => setSelectedFile(value)}
              InputProps={{
                inputProps: {
                  accept: '.zip'
                }
              }}
            />

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

        {meta && (
          <>
            <List
              sx={{
                bgcolor: 'background.paper',
                marginTop: 2
              }}
              subheader={
                <ListSubheader className={styles.subHeader}>
                  Meta Info
                </ListSubheader>
              }
            >
              {submittedBy && (
                <ListItem
                  sx={{
                    marginTop: 1,
                    gap: '15px'
                  }}
                >
                  <Typography variant="h6" sx={{ color: textColor }}>
                    Submitted By
                  </Typography>
                  {displayUser(submittedBy)}
                </ListItem>
              )}

              <ListItem
                sx={{
                  marginTop: 1,
                  gap: '15px'
                }}
              >
                <Typography variant="h6" sx={{ color: textColor }}>
                  Exported By
                </Typography>
                {displayExportedBy()}
              </ListItem>

              {signers.length > 0 && (
                <ListItem
                  sx={{
                    marginTop: 1,
                    flexDirection: 'column',
                    alignItems: 'flex-start'
                  }}
                >
                  <Typography variant="h6" sx={{ color: textColor }}>
                    Signers
                  </Typography>
                  <ul className={styles.usersList}>
                    {signers.map((signer) => (
                      <li
                        key={signer}
                        style={{
                          color: textColor,
                          display: 'flex',
                          alignItems: 'center',
                          gap: '15px'
                        }}
                      >
                        {displayUser(npubToHex(signer)!, true)}
                      </li>
                    ))}
                  </ul>
                </ListItem>
              )}

              {viewers.length > 0 && (
                <ListItem
                  sx={{
                    marginTop: 1,
                    flexDirection: 'column',
                    alignItems: 'flex-start'
                  }}
                >
                  <Typography variant="h6" sx={{ color: textColor }}>
                    Viewers
                  </Typography>
                  <ul className={styles.usersList}>
                    {viewers.map((viewer) => (
                      <li key={viewer} style={{ color: textColor }}>
                        {displayUser(npubToHex(viewer)!)}
                      </li>
                    ))}
                  </ul>
                </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}>
                          <Typography
                            component="label"
                            sx={{
                              color: textColor,
                              flexGrow: 1
                            }}
                          >
                            {filename}
                          </Typography>
                          <Typography
                            component="label"
                            sx={{
                              color: isValidHash
                                ? theme.palette.success.light
                                : theme.palette.error.main
                            }}
                          >
                            {isValidHash ? 'Valid' : 'Invalid'} hash
                          </Typography>
                        </Box>
                      )
                    }
                  )}
                </Box>
              </ListItem>
            </List>
          </>
        )}
      </Box>
    </>
  )
}
