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

export const CreatePage = () => {
  const navigate = useNavigate()
  const [isLoading, setIsLoading] = useState(false)
  const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')

  const [authUrl, setAuthUrl] = useState<string>()

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

  const [userInput, setUserInput] = useState('')
  const [userRole, setUserRole] = useState<UserRole>(UserRole.signer)
  const [error, setError] = useState<string>()

  const [users, setUsers] = useState<User[]>([])

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

  const nostrController = NostrController.getInstance()

  useEffect(() => {
    if (usersPubkey) {
      setUsers((prev) => {
        const existingUserIndex = prev.findIndex(
          (user) => user.pubkey === usersPubkey
        )

        // make logged in user the first signer by default
        if (existingUserIndex === -1)
          return [{ pubkey: usersPubkey, role: UserRole.signer }, ...prev]

        return prev
      })
    }
  }, [usersPubkey])

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

    const addUser = (pubkey: string) => {
      setUsers((prev) => {
        const existingUserIndex = prev.findIndex(
          (user) => user.pubkey === pubkey
        )

        // add new
        if (existingUserIndex === -1)
          return [...prev, { pubkey, role: userRole }]

        const existingUser = prev[existingUserIndex]

        // return existing
        if (existingUser.role === userRole) return prev

        // change user role
        const updatedUsers = [...prev]
        const updatedUser = { ...updatedUsers[existingUserIndex] }
        updatedUser.role = userRole
        updatedUsers[existingUserIndex] = updatedUser

        return updatedUsers
      })
    }

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

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

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

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

  const handleUserRoleChange = (role: UserRole, index: number) => {
    setUsers((prevUsers) => {
      // Create a shallow copy of the previous state
      const updatedUsers = [...prevUsers]
      // Create a shallow copy of the user object at the specified index
      const updatedUser = { ...updatedUsers[index] }
      // Update the role property of the copied user object
      updatedUser.role = role
      // Update the user object at the specified index in the copied array
      updatedUsers[index] = updatedUser
      // Return the updated array
      return updatedUsers
    })
  }

  const handleRemoveUser = (pubkey: string) => {
    setUsers((prev) => prev.filter((user) => user.pubkey !== pubkey))
  }

  const handleSelectFiles = (files: File[]) => {
    setDisplayUserInput(true)
    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 handleCreate = async () => {
    if (users.length === 0) {
      toast.error(
        'No signer/viewer is provided.  At least add one signer or viewer.'
      )
      return
    }

    if (selectedFiles.length === 0) {
      toast.error('No file is selected. Select at least 1 file')
      return
    }

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

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

    // generating file hashes
    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()

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

    const signers = users.filter((user) => user.role === UserRole.signer)
    const viewers = users.filter((user) => user.role === UserRole.viewer)

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

    if (!signedEvent) return

    // create content for meta file
    const meta = {
      signers: signers.map((signer) => signer.pubkey),
      viewers: viewers.map((viewer) => viewer.pubkey),
      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) {
      console.error(err)
      toast.error('An error occurred in converting meta json to string')
      return
    }

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

    if (!fileUrl) return

    setLoadingSpinnerDesc('Sending DM to signers/viewers')

    // send DM to first signer if exists
    const signer =
      signers.length > 0 && signers[0].pubkey === usersPubkey
        ? signers[1]
        : signers[0]
    if (signer) {
      await sendDM(
        fileUrl,
        encryptionKey,
        signer.pubkey,
        nostrController,
        true,
        setAuthUrl
      )
    } else {
      // send DM to all viewers if no signer
      for (const viewer of viewers) {
        // todo: execute in parallel
        await sendDM(
          fileUrl,
          encryptionKey,
          viewer.pubkey,
          nostrController,
          false,
          setAuthUrl
        )
      }
    }
    setIsLoading(false)

    navigate(
      `${appPrivateRoutes.verify}?file=${encodeURIComponent(
        fileUrl
      )}&key=${encodeURIComponent(encryptionKey)}`
    )
  }

  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 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>

        {displayUserInput && (
          <>
            <Typography component='label' variant='h6'>
              Select signers and viewers
            </Typography>
            <Box className={styles.inputBlock}>
              <TextField
                label='nip05 / npub'
                value={userInput}
                onChange={(e) => setUserInput(e.target.value)}
                helperText={error}
                error={!!error}
              />
              <FormControl fullWidth>
                <InputLabel id='select-role-label'>Role</InputLabel>
                <Select
                  labelId='select-role-label'
                  id='demo-simple-select'
                  value={userRole}
                  label='Role'
                  onChange={(e) => setUserRole(e.target.value as UserRole)}
                >
                  <MenuItem value={UserRole.signer}>{UserRole.signer}</MenuItem>
                  <MenuItem value={UserRole.viewer}>{UserRole.viewer}</MenuItem>
                </Select>
              </FormControl>

              <Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
                <Button
                  disabled={!userInput}
                  onClick={handleAddUser}
                  variant='contained'
                >
                  Add
                </Button>
              </Box>
            </Box>
            <DisplayUser
              users={users}
              handleUserRoleChange={handleUserRoleChange}
              handleRemoveUser={handleRemoveUser}
            />
            <Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
              <Button onClick={handleCreate} variant='contained'>
                Create
              </Button>
            </Box>
          </>
        )}
      </Box>
    </>
  )
}

type DisplayUsersProps = {
  users: User[]
  handleUserRoleChange: (role: UserRole, index: number) => void
  handleRemoveUser: (pubkey: string) => void
}

const DisplayUser = ({
  users,
  handleUserRoleChange,
  handleRemoveUser
}: DisplayUsersProps) => {
  const [metadata, setMetadata] = useState<{ [key: string]: ProfileMetadata }>(
    {}
  )

  useEffect(() => {
    users.forEach((user) => {
      if (!(user.pubkey in metadata)) {
        const metadataController = new MetadataController()

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

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

  return (
    <TableContainer component={Paper} elevation={3} sx={{ marginTop: '20px' }}>
      <Table>
        <TableHead>
          <TableRow>
            <TableCell className={styles.tableCell}>User</TableCell>
            <TableCell className={styles.tableCell}>Role</TableCell>
            <TableCell>Action</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {users.map((user, index) => {
            const userMeta = metadata[user.pubkey]
            const npub = hexToNpub(user.pubkey)
            const roboUrl = `https://robohash.org/${npub}.png?set=set3`
            return (
              <TableRow key={index}>
                <TableCell className={styles.tableCell}>
                  <Box className={styles.user}>
                    <img
                      onError={imageLoadError}
                      src={userMeta?.picture || roboUrl}
                      alt='Profile Image'
                      className='profile-image'
                      style={{
                        borderWidth: '3px',
                        borderStyle: 'solid',
                        borderColor: `#${user.pubkey.substring(0, 6)}`
                      }}
                    />
                    <Link to={getProfileRoute(user.pubkey)}>
                      <Typography component='label' className={styles.name}>
                        {userMeta?.display_name ||
                          userMeta?.name ||
                          shorten(npub)}
                      </Typography>
                    </Link>
                  </Box>
                </TableCell>
                <TableCell className={styles.tableCell}>
                  <Select
                    fullWidth
                    value={user.role}
                    onChange={(e) =>
                      handleUserRoleChange(e.target.value as UserRole, index)
                    }
                  >
                    <MenuItem value={UserRole.signer}>
                      {UserRole.signer}
                    </MenuItem>
                    <MenuItem value={UserRole.viewer}>
                      {UserRole.viewer}
                    </MenuItem>
                  </Select>
                </TableCell>
                <TableCell>
                  <Tooltip title='Remove User' arrow>
                    <IconButton onClick={() => handleRemoveUser(user.pubkey)}>
                      <Clear style={{ color: 'red' }} />
                    </IconButton>
                  </Tooltip>
                </TableCell>
              </TableRow>
            )
          })}
        </TableBody>
      </Table>
    </TableContainer>
  )
}
