import { useEffect, useState } from 'react'
import { CreateSignatureEventContent, Meta } from '../types'
import { Mark } from '../types/mark'
import {
  fromUnixTimestamp,
  hexToNpub,
  parseCreateSignatureEvent,
  parseCreateSignatureEventContent,
  SigitMetaParseError,
  SigitStatus,
  SignStatus
} from '../utils'
import { toast } from 'react-toastify'
import { verifyEvent } from 'nostr-tools'
import { Event } from 'nostr-tools'
import store from '../store/store'
import { AuthState } from '../store/auth/types'
import { NostrController } from '../controllers'

/**
 * Flattened interface that combines properties `Meta`, `CreateSignatureEventContent`,
 * and `Event` (event's fields are made optional and pubkey and created_at replaced with our versions)
 */
interface FlatMeta
  extends Meta,
    CreateSignatureEventContent,
    Partial<Omit<Event, 'pubkey' | 'created_at'>> {
  // Remove pubkey and use submittedBy as `npub1${string}`
  submittedBy?: `npub1${string}`

  // Remove created_at and replace with createdAt
  createdAt?: number

  // Validated create signature event
  isValid: boolean

  // Decryption
  encryptionKey: string | null

  // Calculated status fields
  signedStatus: SigitStatus
  signersStatus: {
    [signer: `npub1${string}`]: SignStatus
  }
}

/**
 * Custom use hook for parsing the Sigit Meta
 * @param meta Sigit Meta
 * @returns flattened Meta object with calculated signed status
 */
export const useSigitMeta = (meta: Meta): FlatMeta => {
  const [isValid, setIsValid] = useState(false)
  const [kind, setKind] = useState<number>()
  const [tags, setTags] = useState<string[][]>()
  const [createdAt, setCreatedAt] = useState<number>()
  const [submittedBy, setSubmittedBy] = useState<`npub1${string}`>() // submittedBy, pubkey from nostr event
  const [id, setId] = useState<string>()
  const [sig, setSig] = useState<string>()

  const [signers, setSigners] = useState<`npub1${string}`[]>([])
  const [viewers, setViewers] = useState<`npub1${string}`[]>([])
  const [fileHashes, setFileHashes] = useState<{
    [user: `npub1${string}`]: string
  }>({})
  const [markConfig, setMarkConfig] = useState<Mark[]>([])
  const [title, setTitle] = useState<string>('')
  const [zipUrl, setZipUrl] = useState<string>('')

  const [signedStatus, setSignedStatus] = useState<SigitStatus>(
    SigitStatus.Partial
  )
  const [signersStatus, setSignersStatus] = useState<{
    [signer: `npub1${string}`]: SignStatus
  }>({})

  const [encryptionKey, setEncryptionKey] = useState<string | null>(null)

  useEffect(() => {
    if (!meta) return
    ;(async function () {
      try {
        const createSignatureEvent = await parseCreateSignatureEvent(
          meta.createSignature
        )

        const { kind, tags, created_at, pubkey, id, sig, content } =
          createSignatureEvent

        setIsValid(verifyEvent(createSignatureEvent))
        setKind(kind)
        setTags(tags)
        // created_at in nostr events are stored in seconds
        setCreatedAt(fromUnixTimestamp(created_at))
        setSubmittedBy(pubkey as `npub1${string}`)
        setId(id)
        setSig(sig)

        const { title, signers, viewers, fileHashes, markConfig, zipUrl } =
          await parseCreateSignatureEventContent(content)

        setTitle(title)
        setSigners(signers)
        setViewers(viewers)
        setFileHashes(fileHashes)
        setMarkConfig(markConfig)
        setZipUrl(zipUrl)

        if (meta.keys) {
          const { sender, keys } = meta.keys

          // Retrieve the user's public key from the state
          const usersPubkey = (store.getState().auth as AuthState).usersPubkey!
          const usersNpub = hexToNpub(usersPubkey)

          // Check if the user's public key is in the keys object
          if (usersNpub in keys) {
            // Instantiate the NostrController to decrypt the encryption key
            const nostrController = NostrController.getInstance()
            const decrypted = await nostrController
              .nip04Decrypt(sender, keys[usersNpub])
              .catch((err) => {
                console.log(
                  'An error occurred in decrypting encryption key',
                  err
                )
                return null
              })

            setEncryptionKey(decrypted)
          }
        }

        // Parse each signature event and set signer status
        for (const npub in meta.docSignatures) {
          try {
            const event = await parseCreateSignatureEvent(
              meta.docSignatures[npub as `npub1${string}`]
            )
            const isValidSignature = verifyEvent(event)
            setSignersStatus((prev) => {
              return {
                ...prev,
                [npub]: isValidSignature
                  ? SignStatus.Signed
                  : SignStatus.Invalid
              }
            })
          } catch (error) {
            setSignersStatus((prev) => {
              return {
                ...prev,
                [npub]: SignStatus.Invalid
              }
            })
          }
        }
        const signedBy = Object.keys(meta.docSignatures) as `npub1${string}`[]
        const isCompletelySigned = signers.every((signer) =>
          signedBy.includes(signer)
        )
        setSignedStatus(
          isCompletelySigned ? SigitStatus.Complete : SigitStatus.Partial
        )
      } catch (error) {
        if (error instanceof SigitMetaParseError) {
          toast.error(error.message)
        }
        console.error(error)
      }
    })()
  }, [meta])

  return {
    modifiedAt: meta?.modifiedAt,
    createSignature: meta?.createSignature,
    docSignatures: meta?.docSignatures,
    keys: meta?.keys,
    isValid,
    kind,
    tags,
    createdAt,
    submittedBy,
    id,
    sig,
    signers,
    viewers,
    fileHashes,
    markConfig,
    title,
    zipUrl,
    signedStatus,
    signersStatus,
    encryptionKey
  }
}
