import { Box } from '@mui/material'
import Container from '@mui/material/Container'
import { Event, kinds } from 'nostr-tools'
import { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { Outlet } from 'react-router-dom'
import { AppBar } from '../components/AppBar/AppBar'
import { LoadingSpinner } from '../components/LoadingSpinner'
import { MetadataController, NostrController } from '../controllers'
import {
  restoreState,
  setAuthState,
  setMetadataEvent,
  updateUserAppData
} from '../store/actions'
import { LoginMethods } from '../store/auth/types'
import { State } from '../store/rootReducer'
import { Dispatch } from '../store/store'
import { setUserRobotImage } from '../store/userRobotImage/action'
import {
  clearAuthToken,
  clearState,
  getRoboHashPicture,
  getUsersAppData,
  loadState,
  saveNsecBunkerDelegatedKey,
  subscribeForSigits
} from '../utils'
import { useAppSelector } from '../hooks'
import { SubCloser } from 'nostr-tools/abstract-pool'

export const MainLayout = () => {
  const dispatch: Dispatch = useDispatch()
  const [isLoading, setIsLoading] = useState(true)
  const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState(`Loading App`)
  const authState = useSelector((state: State) => state.auth)
  const usersAppData = useAppSelector((state) => state.userAppData)

  useEffect(() => {
    const metadataController = new MetadataController()

    const logout = () => {
      dispatch(
        setAuthState({
          keyPair: undefined,
          loggedIn: false,
          usersPubkey: undefined,
          loginMethod: undefined,
          nsecBunkerPubkey: undefined
        })
      )

      dispatch(setMetadataEvent(metadataController.getEmptyMetadataEvent()))

      // clear authToken saved in local storage
      clearAuthToken()
      clearState()

      // update nsecBunker delegated key
      const newDelegatedKey =
        NostrController.getInstance().generateDelegatedKey()
      saveNsecBunkerDelegatedKey(newDelegatedKey)
    }

    const restoredState = loadState()
    if (restoredState) {
      dispatch(restoreState(restoredState))

      const { loggedIn, loginMethod, usersPubkey, nsecBunkerRelays } =
        restoredState.auth

      if (loggedIn) {
        if (!loginMethod || !usersPubkey) return logout()

        if (loginMethod === LoginMethods.nsecBunker) {
          if (!nsecBunkerRelays) return logout()

          const nostrController = NostrController.getInstance()
          nostrController.nsecBunkerInit(nsecBunkerRelays).then(() => {
            nostrController.createNsecBunkerSigner(usersPubkey)
          })
        }

        const handleMetadataEvent = (event: Event) => {
          dispatch(setMetadataEvent(event))
        }

        metadataController.on(usersPubkey, (kind: number, event: Event) => {
          if (kind === kinds.Metadata) {
            handleMetadataEvent(event)
          }
        })

        metadataController.findMetadata(usersPubkey).then((metadataEvent) => {
          if (metadataEvent) handleMetadataEvent(metadataEvent)
        })

        setLoadingSpinnerDesc(`Fetching user's app data`)
        getUsersAppData()
          .then((appData) => {
            if (appData) {
              dispatch(updateUserAppData(appData))
            }
          })
          .finally(() => setIsLoading(false))
      } else {
        setIsLoading(false)
      }
    } else {
      setIsLoading(false)
    }
  }, [dispatch])

  useEffect(() => {
    let subCloser: SubCloser | null = null

    if (authState.loggedIn && usersAppData) {
      const pubkey = authState.usersPubkey || authState.keyPair?.public

      if (pubkey) {
        /**
         * Sigit notifications are wrapped using nip 59 seal and gift wrap scheme.
         * According to nip59 created_at for seal and gift wrap should be tweaked to thwart time-analysis attacks.
         * For the above purpose created_at is set to random time upto 2 days in past
         */
        subscribeForSigits(pubkey).then((res) => {
          subCloser = res || null
        })
      }
    }

    return () => {
      if (subCloser) {
        subCloser.close()
      }
    }
  }, [authState, usersAppData])

  /**
   * When authState change user logged in / or app reloaded
   * we set robohash avatar in the global state based on user npub
   * so that avatar will be consistent across the app when kind 0 is empty
   */
  useEffect(() => {
    if (authState && authState.loggedIn) {
      const pubkey = authState.usersPubkey || authState.keyPair?.public

      if (pubkey) {
        dispatch(setUserRobotImage(getRoboHashPicture(pubkey)))
      }
    }
  }, [authState])

  if (isLoading) return <LoadingSpinner desc={loadingSpinnerDesc} />

  return (
    <>
      <AppBar />

      <Box className="main">
        <Container
          sx={{
            position: 'relative',
            maxWidth: {
              xs: '550px'
            }
          }}
        >
          <Outlet />
        </Container>
      </Box>
    </>
  )
}
