import { CalendarMonth, Description, Upload } from '@mui/icons-material'
import { Box, Button, Tooltip, Typography } from '@mui/material'
import JSZip from 'jszip'
import { Event, kinds, verifyEvent } from 'nostr-tools'
import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { toast } from 'react-toastify'
import { UserComponent } from '../../components/username'
import { MetadataController } from '../../controllers'
import { useAppSelector } from '../../hooks'
import { appPrivateRoutes, appPublicRoutes } from '../../routes'
import { CreateSignatureEventContent, Meta, ProfileMetadata } from '../../types'
import {
  formatTimestamp,
  hexToNpub,
  npubToHex,
  parseJson,
  shorten
} from '../../utils'
import styles from './style.module.scss'

export const HomePage = () => {
  const navigate = useNavigate()
  const fileInputRef = useRef<HTMLInputElement>(null)
  const [sigits, setSigits] = useState<Meta[]>([])
  const [profiles, setProfiles] = useState<{ [key: string]: ProfileMetadata }>(
    {}
  )
  const usersAppData = useAppSelector((state) => state.userAppData)

  useEffect(() => {
    if (usersAppData) {
      setSigits(Object.values(usersAppData.sigits))
    }
  }, [usersAppData])

  const handleUploadClick = () => {
    if (fileInputRef.current) {
      fileInputRef.current.click()
    }
  }

  const handleFileChange = async (
    event: React.ChangeEvent<HTMLInputElement>
  ) => {
    const file = event.target.files?.[0]
    if (file) {
      // Check if the file extension is .sigit.zip
      const fileName = file.name
      const fileExtension = fileName.slice(-10) // ".sigit.zip" has 10 characters
      if (fileExtension === '.sigit.zip') {
        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

        // navigate to sign page if zip contains keys.json
        if ('keys.json' in zip.files) {
          return navigate(appPrivateRoutes.sign, {
            state: { uploadedZip: file }
          })
        }

        // navigate to verify page if zip contains meta.json
        if ('meta.json' in zip.files) {
          return navigate(appPublicRoutes.verify, {
            state: { uploadedZip: file }
          })
        }

        toast.error('Invalid zip file')
        return
      }

      // navigate to create page
      navigate(appPrivateRoutes.create, { state: { uploadedFile: file } })
    }
  }

  return (
    <>
      <Box className={styles.container}>
        <Box className={styles.header}>
          <Typography variant="h3" className={styles.title}>
            Sigits
          </Typography>
          {/* This is for desktop view */}
          <Box
            className={styles.actionButtons}
            sx={{
              display: {
                xs: 'none',
                md: 'flex'
              }
            }}
          >
            <input
              type="file"
              ref={fileInputRef}
              style={{ display: 'none' }}
              onChange={handleFileChange}
            />
            <Button
              variant="outlined"
              startIcon={<Upload />}
              onClick={handleUploadClick}
            >
              Upload
            </Button>
          </Box>
          {/* This is for mobile view */}
          <Box
            className={styles.actionButtons}
            sx={{
              display: {
                xs: 'flex',
                md: 'none'
              }
            }}
          >
            <Tooltip title="Upload" arrow>
              <Button variant="outlined" onClick={handleUploadClick}>
                <Upload />
              </Button>
            </Tooltip>
          </Box>
        </Box>
        <Box className={styles.submissions}>
          {sigits.map((sigit, index) => (
            <DisplaySigit
              key={`sigit-${index}`}
              meta={sigit}
              profiles={profiles}
              setProfiles={setProfiles}
            />
          ))}
        </Box>
      </Box>
    </>
  )
}

type SigitProps = {
  meta: Meta
  profiles: { [key: string]: ProfileMetadata }
  setProfiles: Dispatch<SetStateAction<{ [key: string]: ProfileMetadata }>>
}

enum SignedStatus {
  Partial = 'Partially Signed',
  Complete = 'Completely Signed'
}

const DisplaySigit = ({ meta, profiles, setProfiles }: SigitProps) => {
  const navigate = useNavigate()

  const [title, setTitle] = useState<string>()
  const [createdAt, setCreatedAt] = useState('')
  const [submittedBy, setSubmittedBy] = useState<string>()
  const [signers, setSigners] = useState<`npub1${string}`[]>([])
  const [signedStatus, setSignedStatus] = useState<SignedStatus>(
    SignedStatus.Partial
  )

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

      if (!createSignatureEvent) return

      // created_at in nostr events are stored in seconds
      // convert it to ms before formatting
      setCreatedAt(formatTimestamp(createSignatureEvent.created_at * 1000))

      const createSignatureContent =
        await parseJson<CreateSignatureEventContent>(
          createSignatureEvent.content
        ).catch((err) => {
          console.log(
            `err in parsing the createSignature event's content :>> `,
            err
          )
          return null
        })

      if (!createSignatureContent) return

      setTitle(createSignatureContent.title)
      setSubmittedBy(createSignatureEvent.pubkey)
      setSigners(createSignatureContent.signers)

      const signedBy = Object.keys(meta.docSignatures) as `npub1${string}`[]
      const isCompletelySigned = createSignatureContent.signers.every(
        (signer) => signedBy.includes(signer)
      )
      if (isCompletelySigned) {
        setSignedStatus(SignedStatus.Complete)
      }
    }
    extractInfo()
  }, [meta])

  useEffect(() => {
    const hexKeys: string[] = []

    if (submittedBy) {
      hexKeys.push(npubToHex(submittedBy)!)
    }
    hexKeys.push(...signers.map((signer) => npubToHex(signer)!))

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

          if (metadataContent)
            setProfiles((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)
          })
      }
    })
  }, [submittedBy, signers])

  const handleNavigation = () => {
    if (signedStatus === SignedStatus.Complete) {
      navigate(appPublicRoutes.verify, { state: { meta } })
    } else {
      navigate(appPrivateRoutes.sign, { state: { meta } })
    }
  }

  return (
    <Box
      className={styles.item}
      sx={{
        flexDirection: {
          xs: 'column',
          md: 'row'
        }
      }}
      onClick={handleNavigation}
    >
      <Box
        className={styles.titleBox}
        sx={{
          borderBottomLeftRadius: {
            xs: 'initial',
            md: 'inherit'
          },
          borderTopRightRadius: {
            xs: 'inherit',
            md: 'initial'
          }
        }}
      >
        <Typography variant="body1" className={styles.title}>
          <Description />
          {title}
        </Typography>
        {submittedBy &&
          (function () {
            const profile = profiles[submittedBy]
            return (
              <UserComponent
                pubkey={submittedBy}
                name={
                  profile?.display_name ||
                  profile?.name ||
                  shorten(hexToNpub(submittedBy))
                }
                image={profile?.picture}
              />
            )
          })()}
        <Typography variant="body2" className={styles.date}>
          <CalendarMonth />
          {createdAt}
        </Typography>
      </Box>
      <Box className={styles.signers}>
        {signers.map((signer) => {
          const pubkey = npubToHex(signer)!
          const profile = profiles[pubkey]

          return (
            <DisplaySigner
              key={signer}
              meta={meta}
              profile={profile}
              pubkey={pubkey}
            />
          )
        })}
      </Box>
    </Box>
  )
}

enum SignStatus {
  Signed = 'Signed',
  Pending = 'Pending',
  Invalid = 'Invalid Sign'
}

type DisplaySignerProps = {
  meta: Meta
  profile: ProfileMetadata
  pubkey: string
}

const DisplaySigner = ({ meta, profile, pubkey }: DisplaySignerProps) => {
  const [signStatus, setSignedStatus] = useState<SignStatus>()

  useEffect(() => {
    const updateSignStatus = async () => {
      const npub = hexToNpub(pubkey)
      if (npub in meta.docSignatures) {
        parseJson<Event>(meta.docSignatures[npub])
          .then((event) => {
            const isValidSignature = verifyEvent(event)
            if (isValidSignature) {
              setSignedStatus(SignStatus.Signed)
            } else {
              setSignedStatus(SignStatus.Invalid)
            }
          })
          .catch((err) => {
            console.log(`err in parsing the docSignatures for ${npub}:>> `, err)
            setSignedStatus(SignStatus.Invalid)
          })
      } else {
        setSignedStatus(SignStatus.Pending)
      }
    }

    updateSignStatus()
  }, [meta, pubkey])

  return (
    <Box className={styles.signerItem}>
      <Typography variant="button" className={styles.status}>
        {signStatus}
      </Typography>
      <UserComponent
        pubkey={pubkey}
        name={
          profile?.display_name ||
          profile?.name ||
          shorten(hexToNpub(pubkey), 5)
        }
        image={profile?.picture}
      />
    </Box>
  )
}
