import ContentCopyIcon from '@mui/icons-material/ContentCopy'
import {
  Box,
  IconButton,
  InputProps,
  List,
  ListItem,
  ListSubheader,
  TextField,
  Tooltip,
  Typography,
  useTheme
} from '@mui/material'
import { UnsignedEvent, nip19, kinds, VerifiedEvent, Event } from 'nostr-tools'
import React, { useEffect, useMemo, useRef, useState } from 'react'
import { Link, useParams } from 'react-router-dom'
import { toast } from 'react-toastify'
import { MetadataController, NostrController } from '../../../controllers'
import { NostrJoiningBlock, ProfileMetadata } from '../../../types'
import styles from './style.module.scss'
import { useDispatch, useSelector } from 'react-redux'
import { State } from '../../../store/rootReducer'
import { LoadingButton } from '@mui/lab'
import { Dispatch } from '../../../store/store'
import { setMetadataEvent } from '../../../store/actions'
import { LoadingSpinner } from '../../../components/LoadingSpinner'
import { LoginMethods } from '../../../store/auth/types'
import { SmartToy } from '@mui/icons-material'
import {
  getNostrJoiningBlockNumber,
  getRoboHashPicture,
  unixNow
} from '../../../utils'
import { Container } from '../../../components/Container'

export const ProfileSettingsPage = () => {
  const theme = useTheme()

  const { npub } = useParams()

  const dispatch: Dispatch = useDispatch()

  const metadataController = useMemo(() => new MetadataController(), [])
  const nostrController = NostrController.getInstance()

  const [pubkey, setPubkey] = useState<string>()
  const [nostrJoiningBlock, setNostrJoiningBlock] =
    useState<NostrJoiningBlock | null>(null)
  const [profileMetadata, setProfileMetadata] = useState<ProfileMetadata>()
  const [savingProfileMetadata, setSavingProfileMetadata] = useState(false)
  const metadataState = useSelector((state: State) => state.metadata)
  const keys = useSelector((state: State) => state.auth?.keyPair)
  const { usersPubkey, loginMethod } = useSelector((state: State) => state.auth)
  const userRobotImage = useSelector((state: State) => state.userRobotImage)

  const [isUsersOwnProfile, setIsUsersOwnProfile] = useState(false)

  const [isLoading, setIsLoading] = useState(true)
  const [loadingSpinnerDesc] = useState('Fetching metadata')

  const robotSet = useRef(1)

  useEffect(() => {
    if (npub) {
      try {
        const hexPubkey = nip19.decode(npub).data as string
        setPubkey(hexPubkey)

        if (hexPubkey === usersPubkey) setIsUsersOwnProfile(true)
      } catch (error) {
        toast.error('Error occurred in decoding npub' + error)
      }
    }
  }, [npub, usersPubkey])

  useEffect(() => {
    if (pubkey) {
      getNostrJoiningBlockNumber(pubkey)
        .then((res) => {
          setNostrJoiningBlock(res)
        })
        .catch((err) => {
          // todo: handle error
          console.log('err :>> ', err)
        })
    }

    if (isUsersOwnProfile && metadataState) {
      const metadataContent = metadataController.extractProfileMetadataContent(
        metadataState as VerifiedEvent
      )
      if (metadataContent) {
        setProfileMetadata(metadataContent)
        setIsLoading(false)
      }

      return
    }

    if (pubkey) {
      const getMetadata = async (pubkey: string) => {
        const handleMetadataEvent = (event: Event) => {
          const metadataContent =
            metadataController.extractProfileMetadataContent(event)
          if (metadataContent) {
            setProfileMetadata(metadataContent)
          }
        }

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

        const metadataEvent = await metadataController
          .findMetadata(pubkey)
          .catch((err) => {
            toast.error(err)
            return null
          })

        if (metadataEvent) handleMetadataEvent(metadataEvent)

        setIsLoading(false)
      }

      getMetadata(pubkey)
    }
  }, [isUsersOwnProfile, metadataState, pubkey, metadataController])

  const editItem = (
    key: keyof ProfileMetadata,
    label: string,
    multiline = false,
    rows = 1,
    inputProps?: InputProps
  ) => (
    <ListItem sx={{ marginTop: 1 }}>
      <TextField
        label={label}
        id={label.split(' ').join('-')}
        value={profileMetadata![key] || ''}
        size="small"
        multiline={multiline}
        rows={rows}
        className={styles.textField}
        disabled={!isUsersOwnProfile}
        InputProps={inputProps}
        onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
          const { value } = event.target

          setProfileMetadata((prev) => ({
            ...prev,
            [key]: value
          }))
        }}
      />
    </ListItem>
  )

  const copyItem = (
    value: string,
    label: string,
    copyValue?: string,
    isPassword = false
  ) => (
    <ListItem
      sx={{ marginTop: 1 }}
      onClick={() => {
        navigator.clipboard.writeText(copyValue || value)

        toast.success('Copied to clipboard', {
          autoClose: 1000,
          hideProgressBar: true
        })
      }}
    >
      <TextField
        label={label}
        id={label.split(' ').join('-')}
        defaultValue={value}
        size="small"
        className={styles.textField}
        disabled
        type={isPassword ? 'password' : 'text'}
        InputProps={{
          endAdornment: <ContentCopyIcon className={styles.copyItem} />
        }}
      />
    </ListItem>
  )

  const handleSaveMetadata = async () => {
    setSavingProfileMetadata(true)

    const content = JSON.stringify(profileMetadata)

    // We need to omit cachedAt and create new event
    // Relay will reject if created_at is too late
    const updatedMetadataState: UnsignedEvent = {
      content: content,
      created_at: unixNow(),
      kind: kinds.Metadata,
      pubkey: pubkey!,
      tags: metadataState?.tags || []
    }

    const signedEvent = await nostrController
      .signEvent(updatedMetadataState)
      .catch((error) => {
        toast.error(`Error saving profile metadata. ${error}`)
      })

    if (signedEvent) {
      if (!metadataController.validate(signedEvent)) {
        toast.error(`Metadata is not valid.`)
      }

      await metadataController.publishMetadataEvent(signedEvent)

      dispatch(setMetadataEvent(signedEvent))
    }

    setSavingProfileMetadata(false)
  }

  /**
   * Called by clicking on the robot icon inside Picture URL input
   * On every click, next robohash set will be generated.
   * There are 5 sets at the moment, after 5th set function will start over from set 1.
   */
  const generateRobotAvatar = () => {
    robotSet.current++
    if (robotSet.current > 5) robotSet.current = 1

    const robotAvatarLink = getRoboHashPicture(npub!, robotSet.current)

    setProfileMetadata((prev) => ({
      ...prev,
      picture: robotAvatarLink
    }))
  }

  /**
   *
   * @returns robohash generate button, loading spinner or no button
   */
  const robohashButton = () => {
    return (
      <Tooltip title="Generate a robohash avatar">
        <IconButton onClick={generateRobotAvatar}>
          <SmartToy />
        </IconButton>
      </Tooltip>
    )
  }

  /**
   * Handles the logic for Image URL.
   * If no picture in kind 0 found - use robohash avatar
   *
   * @returns robohash image url
   */
  const getProfileImage = (metadata: ProfileMetadata) => {
    if (!isUsersOwnProfile) {
      return metadata.picture || getRoboHashPicture(npub!)
    }

    // userRobotImage is used only when visiting own profile
    // while kind 0 picture is not set
    return metadata.picture || userRobotImage || getRoboHashPicture(npub!)
  }

  return (
    <>
      {isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
      <Container className={styles.container}>
        <List
          sx={{
            bgcolor: 'background.paper',
            marginTop: 2
          }}
          subheader={
            <ListSubheader
              sx={{
                paddingBottom: 1,
                paddingTop: 1,
                fontSize: '1.5rem'
              }}
              className={styles.subHeader}
            >
              Profile Settings
            </ListSubheader>
          }
        >
          {profileMetadata && (
            <div>
              <ListItem
                sx={{
                  marginTop: 1,
                  display: 'flex',
                  flexDirection: 'column'
                }}
              >
                {profileMetadata.banner ? (
                  <img
                    className={styles.bannerImg}
                    src={profileMetadata.banner}
                    alt="Banner Image"
                  />
                ) : (
                  <Box className={styles.noBanner}> No banner found </Box>
                )}
              </ListItem>

              {editItem('banner', 'Banner URL', undefined, undefined)}

              <ListItem
                sx={{
                  marginTop: 1,
                  display: 'flex',
                  flexDirection: 'column'
                }}
              >
                <img
                  onError={(event: React.SyntheticEvent<HTMLImageElement>) => {
                    event.currentTarget.src = getRoboHashPicture(npub!)
                  }}
                  className={styles.img}
                  src={getProfileImage(profileMetadata)}
                  alt="Profile Image"
                />

                {nostrJoiningBlock && (
                  <Typography
                    sx={{
                      color: theme.palette.getContrastText(
                        theme.palette.background.paper
                      )
                    }}
                    component={Link}
                    to={`https://njump.me/${nostrJoiningBlock.encodedEventPointer}`}
                    target="_blank"
                  >
                    On nostr since {nostrJoiningBlock.block.toLocaleString()}
                  </Typography>
                )}
              </ListItem>

              {editItem('picture', 'Picture URL', undefined, undefined, {
                endAdornment: isUsersOwnProfile ? robohashButton() : undefined
              })}

              {editItem('name', 'Username')}
              {editItem('display_name', 'Display Name')}
              {editItem('nip05', 'Nostr Address (nip05)')}
              {editItem('lud16', 'Lightning Address (lud16)')}
              {editItem('about', 'About', true, 4)}
              {editItem('website', 'Website')}
              {isUsersOwnProfile && (
                <>
                  {usersPubkey &&
                    copyItem(nip19.npubEncode(usersPubkey), 'Public Key')}
                  {loginMethod === LoginMethods.privateKey &&
                    keys &&
                    keys.private &&
                    copyItem(
                      '••••••••••••••••••••••••••••••••••••••••••••••••••',
                      'Private Key',
                      keys.private
                    )}
                </>
              )}
            </div>
          )}
        </List>
        {isUsersOwnProfile && (
          <LoadingButton
            loading={savingProfileMetadata}
            variant="contained"
            onClick={handleSaveMetadata}
          >
            SAVE
          </LoadingButton>
        )}
      </Container>
    </>
  )
}
