import { Clear } from '@mui/icons-material'
import {
  Box,
  Button,
  FormControl,
  IconButton,
  InputLabel,
  List,
  ListItem,
  ListSubheader,
  MenuItem,
  Select,
  TextField,
  Typography
} from '@mui/material'
import JSZip from 'jszip'
import { MuiFileInput } from 'mui-file-input'
import { useEffect, useState } from 'react'
import { useSelector } from 'react-redux'
import { Link } from 'react-router-dom'
import { toast } from 'react-toastify'
import placeholderAvatar from '../../assets/images/nostr-logo.jpg'
import { LoadingSpinner } from '../../components/LoadingSpinner'
import { MetadataController, NostrController } from '../../controllers'
import { getProfileRoute } from '../../routes'
import { State } from '../../store/rootReducer'
import { ProfileMetadata } from '../../types'
import {
  encryptArrayBuffer,
  generateEncryptionKey,
  getHash,
  pubToHex,
  queryNip05,
  sendDM,
  shorten,
  signEventForMetaFile,
  uploadToFileStorage
} from '../../utils'
import styles from './style.module.scss'

enum SelectionType {
  signer = 'Signer',
  viewer = 'Viewer'
}

export const HomePage = () => {
  const [inputValue, setInputValue] = useState('')
  const [type, setType] = useState<SelectionType>(SelectionType.signer)
  const [error, setError] = useState<string>()

  const [signers, setSigners] = useState<string[]>([])
  const [viewers, setViewers] = useState<string[]>([])

  const [selectedFiles, setSelectedFiles] = useState<File[]>([])

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

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

  const nostrController = NostrController.getInstance()

  const handleAddClick = async () => {
    setError(undefined)

    const addPubkey = (pubkey: string) => {
      const addElement = (prev: string[]) => {
        // if key is already in the list just return that
        if (prev.includes(pubkey)) return prev

        return [...prev, pubkey]
      }
      if (type === SelectionType.signer) {
        setSigners(addElement)
      } else {
        setViewers(addElement)
      }
    }

    if (inputValue.startsWith('npub')) {
      const pubkey = await pubToHex(inputValue)
      if (pubkey) {
        addPubkey(pubkey)
        setInputValue('')
      } else {
        setError('Provided npub is not valid. Please enter correct npub.')
      }
      return
    }

    if (inputValue.includes('@')) {
      setIsLoading(true)
      setLoadingSpinnerDesc('Querying for nip05')
      const nip05Profile = await queryNip05(inputValue)
        .catch((err) => {
          console.error(`error occurred in querying nip05: ${inputValue}`, err)
          return null
        })
        .finally(() => {
          setIsLoading(false)
          setLoadingSpinnerDesc('')
        })

      if (nip05Profile) {
        const pubkey = nip05Profile.pubkey
        addPubkey(pubkey)
        setInputValue('')
      } else {
        setError('Provided nip05 is not valid. Please enter correct nip05.')
      }
      return
    }

    setError('Invalid input! Make sure to provide correct npub or nip05.')
  }

  const handleRemove = (pubkey: string, selectionType: SelectionType) => {
    if (selectionType === SelectionType.signer) {
      setSigners((prev) => prev.filter((signer) => signer !== pubkey))
    } else {
      setViewers((prev) => prev.filter((viewer) => viewer !== pubkey))
    }
  }

  const handleSelectFiles = (files: File[]) => {
    setSelectedFiles((prev) => {
      const prevFileNames = prev.map((file) => file.name)

      const newFiles = files.filter(
        (file) => !prevFileNames.includes(file.name)
      )

      return [...prev, ...newFiles]
    })
  }

  const handleRemoveFile = (fileToRemove: File) => {
    setSelectedFiles((prevFiles) =>
      prevFiles.filter((file) => file.name !== fileToRemove.name)
    )
  }

  const handleSubmit = async () => {
    if (signers.length === 0) {
      toast.error('No signer is provided.  At least provide one signer.')
      return
    }

    if (viewers.length === 0) {
      toast.error('No viewer is provided.  At least provide one viewer.')
      return
    }

    if (selectedFiles.length === 0) {
      toast.error('No file is provided.  At least provide one file.')
      return
    }

    setIsLoading(true)
    setLoadingSpinnerDesc('Generating hashes for files')

    const fileHashes: { [key: string]: string } = {}

    for (const file of selectedFiles) {
      const arraybuffer = await file.arrayBuffer().catch((err) => {
        console.log(
          `err while getting arrayBuffer of file ${file.name} :>> `,
          err
        )
        toast.error(
          err.message || `err while getting arrayBuffer of file ${file.name}`
        )
        return null
      })

      if (!arraybuffer) return

      const hash = await getHash(arraybuffer)

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

      fileHashes[file.name] = hash
    }

    const zip = new JSZip()

    selectedFiles.forEach((file) => {
      zip.file(`files/${file.name}`, file)
    })

    setLoadingSpinnerDesc('Signing nostr event')
    const signedEvent = await signEventForMetaFile(
      signers[0],
      fileHashes,
      nostrController,
      setIsLoading
    )

    if (!signedEvent) return

    const meta = {
      signers,
      viewers,
      fileHashes,
      submittedBy: usersPubkey,
      signedEvents: {
        [signedEvent.pubkey]: JSON.stringify(signedEvent, null, 2)
      }
    }

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

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

      const metaHashJson = {
        [usersPubkey!]: metaHash
      }

      zip.file('hashes.json', JSON.stringify(metaHashJson, null, 2))
    } catch (err) {
      toast.error('An error occurred in converting meta json to string')
    }

    setLoadingSpinnerDesc('Generating zip file')

    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)
        toast.error(err.message || 'Error occurred in uploading zip file')
        return null
      })

    if (!fileUrl) return

    setLoadingSpinnerDesc('Sending DM to first signer')
    await sendDM(
      fileUrl,
      encryptionKey,
      signers[0],
      nostrController,
      true,
      setAuthUrl
    )

    setIsLoading(false)
  }

  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 signers and viewers
        </Typography>
        <Box className={styles.inputBlock}>
          <TextField
            label='nip05 / npub'
            value={inputValue}
            onChange={(e) => setInputValue(e.target.value)}
            helperText={error}
            error={!!error}
          />
          <FormControl fullWidth>
            <InputLabel id='select-type-label'>Type</InputLabel>
            <Select
              labelId='select-type-label'
              id='demo-simple-select'
              value={type}
              label='Type'
              onChange={(e) => setType(e.target.value as SelectionType)}
            >
              <MenuItem value={SelectionType.signer}>
                {SelectionType.signer}
              </MenuItem>
              <MenuItem value={SelectionType.viewer}>
                {SelectionType.viewer}
              </MenuItem>
            </Select>
          </FormControl>

          <Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
            <Button
              disabled={!inputValue}
              onClick={handleAddClick}
              variant='contained'
            >
              Add
            </Button>
          </Box>
        </Box>

        <Typography component='label' variant='h6'>
          Select files
        </Typography>

        <MuiFileInput
          multiple
          placeholder='Choose Files'
          value={selectedFiles}
          onChange={(value) => handleSelectFiles(value)}
        />

        <ul>
          {selectedFiles.map((file, index) => (
            <li key={index}>
              <Typography component='label'>{file.name}</Typography>
              <IconButton onClick={() => handleRemoveFile(file)}>
                <Clear style={{ color: 'red' }} />{' '}
              </IconButton>
            </li>
          ))}
        </ul>

        {signers.length > 0 && (
          <List
            sx={{
              bgcolor: 'background.paper',
              marginTop: 2
            }}
            subheader={
              <ListSubheader
                sx={{
                  paddingBottom: 1,
                  paddingTop: 1,
                  fontSize: '1.5rem'
                }}
                className={styles.subHeader}
              >
                Signers
              </ListSubheader>
            }
          >
            {signers.map((signer, index) => (
              <DisplaySignerOrViewer
                key={`signer-${index}`}
                pubkey={signer}
                remove={() => handleRemove(signer, SelectionType.signer)}
              />
            ))}
          </List>
        )}

        {viewers.length > 0 && (
          <List
            sx={{
              bgcolor: 'background.paper',
              marginTop: 2
            }}
            subheader={
              <ListSubheader
                sx={{
                  paddingBottom: 1,
                  paddingTop: 1,
                  fontSize: '1.5rem'
                }}
                className={styles.subHeader}
              >
                Viewers
              </ListSubheader>
            }
          >
            {viewers.map((viewer, index) => (
              <DisplaySignerOrViewer
                key={`viewer-${index}`}
                pubkey={viewer}
                remove={() => handleRemove(viewer, SelectionType.viewer)}
              />
            ))}
          </List>
        )}

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

type DisplaySignerOrViewerProps = {
  pubkey: string
  remove: () => void
}

const DisplaySignerOrViewer = ({
  pubkey,
  remove
}: DisplaySignerOrViewerProps) => {
  const [metadata, setMetadata] = useState<ProfileMetadata>()

  useEffect(() => {
    const getMetadata = async (pubkey: string) => {
      const metadataController = new MetadataController()
      const metadataEvent = await metadataController
        .findMetadata(pubkey)
        .catch((err) => {
          console.error(
            `error occurred in finding metadata for: ${pubkey}`,
            err
          )
          return null
        })

      if (metadataEvent) {
        const metadataContent =
          metadataController.extractProfileMetadataContent(metadataEvent)
        if (metadataContent) setMetadata(metadataContent)
      }
    }

    getMetadata(pubkey)
  }, [pubkey])

  const imageLoadError = (event: any) => {
    event.target.src = placeholderAvatar
  }

  return (
    <ListItem sx={{ marginTop: 1 }} className={styles.listItem}>
      <img
        onError={imageLoadError}
        src={metadata?.picture || placeholderAvatar}
        alt='Profile Image'
        className={styles.img}
      />
      <Link to={getProfileRoute(pubkey)}>
        <Typography component='label' className={styles.name}>
          {metadata?.display_name || metadata?.name || shorten(pubkey)}
        </Typography>
      </Link>
      <IconButton onClick={remove}>
        <Clear style={{ color: 'red' }} />
      </IconButton>
    </ListItem>
  )
}
