import { useEffect, useState } from 'react'
import { Box, List, ListItem, TextField } from '@mui/material'
import RouterIcon from '@mui/icons-material/Router'
import styles from './style.module.scss'
import Switch from '@mui/material/Switch'
import ListItemText from '@mui/material/ListItemText'
import Divider from '@mui/material/Divider'
import { NostrController } from '../../controllers'
import {
  RelayMap,
  RelayInfoObject,
  RelayFee,
  RelayConnectionState
} from '../../types'
import LogoutIcon from '@mui/icons-material/Logout'
import { useAppSelector, useAppDispatch } from '../../hooks'
import {
  compareObjects,
  shorten,
  hexToNpub,
  capitalizeFirstLetter
} from '../../utils'
import {
  setRelayMapAction,
  setRelayMapUpdatedAction
} from '../../store/actions'
import { toast } from 'react-toastify'
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
import ElectricBoltIcon from '@mui/icons-material/ElectricBolt'
import { Tooltip } from '@mui/material'
import InputAdornment from '@mui/material/InputAdornment'
import Button from '@mui/material/Button'

export const RelaysPage = () => {
  const nostrController = NostrController.getInstance()

  const relaysState = useAppSelector((state) => state.relays)
  const usersPubkey = useAppSelector((state) => state.auth?.usersPubkey)

  const dispatch = useAppDispatch()

  const [newRelayURI, setNewRelayURI] = useState<string>()
  const [newRelayURIerror, setNewRelayURIerror] = useState<string>()
  const [relayMap, setRelayMap] = useState<RelayMap | undefined>(
    relaysState?.map
  )
  const [relaysInfo, setRelaysInfo] = useState<RelayInfoObject | undefined>(
    relaysState?.info
  )
  const [displayRelaysInfo, setDisplayRelaysInfo] = useState<string[]>([])
  const [relaysConnectionStatus, setRelaysConnectionStatus] = useState(
    relaysState?.connectionStatus
  )

  const webSocketPrefix = 'wss://'

  // Update relay connection status
  useEffect(() => {
    if (
      !compareObjects(relaysConnectionStatus, relaysState?.connectionStatus)
    ) {
      setRelaysConnectionStatus(relaysState?.connectionStatus)
    }
  }, [relaysConnectionStatus, relaysState?.connectionStatus])

  useEffect(() => {
    if (!compareObjects(relaysInfo, relaysState?.info)) {
      setRelaysInfo(relaysState?.info)
    }
  }, [relaysInfo, relaysState?.info])

  useEffect(() => {
    if (!compareObjects(relayMap, relaysState?.map)) {
      setRelayMap(relaysState?.map)
    }
  }, [relayMap, relaysState?.map])

  useEffect(() => {
    let isMounted = false

    const fetchData = async () => {
      if (usersPubkey) {
        isMounted = true

        // call async func to fetch relay map
        const newRelayMap = await nostrController.getRelayMap(usersPubkey)

        // handle fetched relay map
        if (isMounted) {
          if (
            !relaysState?.mapUpdated ||
            newRelayMap.mapUpdated > relaysState?.mapUpdated
          ) {
            if (
              !relaysState?.map ||
              !compareObjects(relaysState.map, newRelayMap)
            ) {
              setRelayMap(newRelayMap.map)

              dispatch(setRelayMapAction(newRelayMap.map))
            } else {
              // Update relay map updated timestamp
              dispatch(setRelayMapUpdatedAction())
            }
          }
        }
      }
    }

    // Publishing relay map can take some time.
    // This is why data fetch should happen only if relay map was received more than 5 minutes ago.
    if (
      usersPubkey &&
      (!relaysState?.mapUpdated ||
        Date.now() - relaysState?.mapUpdated > 5 * 60 * 1000) // 5 minutes
    ) {
      fetchData()

      // Update relay connection status
      if (relaysConnectionStatus) {
        const notConnectedRelays = Object.keys(relaysConnectionStatus).filter(
          (key) =>
            relaysConnectionStatus[key] === RelayConnectionState.NotConnected
        )

        if (notConnectedRelays.length) {
          nostrController.connectToRelays(notConnectedRelays)
        }
      }
    }

    // cleanup func
    return () => {
      isMounted = false
    }
  }, [
    dispatch,
    usersPubkey,
    relaysState?.map,
    relaysState?.mapUpdated,
    nostrController,
    relaysConnectionStatus
  ])

  useEffect(() => {
    // Display notification if an empty relay map has been received
    if (relayMap && Object.keys(relayMap).length === 0) {
      relayRequirementWarning()
    }
  }, [relayMap])

  const relayRequirementWarning = () =>
    toast.warning('At least one write relay is needed for SIGit to work.')

  const handleLeaveRelay = async (relay: string) => {
    if (relayMap) {
      const relaysInMap = Object.keys(relayMap).length
      const writeRelays = Object.keys(relayMap).filter(
        (key) => relayMap[key].write
      )

      // Check if at least one write relay is present in relay map
      if (
        relaysInMap <= 1 ||
        (writeRelays.length === 1 && writeRelays.includes(relay))
      ) {
        relayRequirementWarning()
      } else {
        const relayMapCopy = JSON.parse(JSON.stringify(relayMap))
        // Remove relay from relay map
        delete relayMapCopy[relay]

        if (usersPubkey) {
          // Publish updated relay map.
          const relayMapPublishingRes = await nostrController
            .publishRelayMap(relayMapCopy, usersPubkey, [relay])
            .catch((err) => handlePublishRelayMapError(err))

          if (relayMapPublishingRes) {
            toast.success(relayMapPublishingRes)

            setRelayMap(relayMapCopy)

            dispatch(setRelayMapAction(relayMapCopy))
          }
        }

        nostrController.disconnectFromRelays([relay])
      }
    }
  }

  const handlePublishRelayMapError = (err: any) => {
    const errorPrefix = 'Error while publishing Relay Map'

    if (Array.isArray(err)) {
      err.forEach((errorObj: { relay: string; error: string }) => {
        toast.error(
          `${errorPrefix} to ${errorObj.relay}. Error: ${errorObj.error || 'Unknown'}`
        )
      })
    } else {
      toast.error(`${errorPrefix}. Error: ${err.message || 'Unknown'}`)
    }
  }

  const handleRelayWriteChange = async (
    relay: string,
    event: React.ChangeEvent<HTMLInputElement>
  ) => {
    if (relayMap && relayMap[relay]) {
      if (
        !event.target.checked &&
        Object.keys(relayMap).filter((relay) => relayMap[relay].write)
          .length === 1
      ) {
        relayRequirementWarning()
      } else {
        const relayMapCopy = JSON.parse(JSON.stringify(relayMap))
        relayMapCopy[relay].write = event.target.checked

        if (usersPubkey) {
          // Publish updated relay map
          const relayMapPublishingRes = await nostrController
            .publishRelayMap(relayMapCopy, usersPubkey)
            .catch((err) => handlePublishRelayMapError(err))

          if (relayMapPublishingRes) {
            toast.success(relayMapPublishingRes)

            setRelayMap(relayMapCopy)

            dispatch(setRelayMapAction(relayMapCopy))
          }
        }
      }
    }
  }

  const handleAddNewRelay = async () => {
    const relayURI = `${webSocketPrefix}${newRelayURI?.trim().replace(webSocketPrefix, '')}`

    // Check if new relay URI is a valid string
    if (
      relayURI &&
      !/^wss:\/\/[-a-zA-Z0-9@:%._\\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}/.test(
        relayURI
      )
    ) {
      if (relayURI !== webSocketPrefix) {
        setNewRelayURIerror(
          'New relay URI is not valid. Example of valid relay URI: wss://sigit.relay.io'
        )
      }
    } else if (relayURI && usersPubkey) {
      const connectionStatus = await nostrController.connectToRelays([relayURI])

      if (
        connectionStatus &&
        connectionStatus[relayURI] &&
        connectionStatus[relayURI] === RelayConnectionState.Connected
      ) {
        const relayMapCopy = JSON.parse(JSON.stringify(relayMap))

        relayMapCopy[relayURI] = { write: true, read: true }

        // Publish updated relay map
        const relayMapPublishingRes = await nostrController
          .publishRelayMap(relayMapCopy, usersPubkey)
          .catch((err) => handlePublishRelayMapError(err))

        if (relayMapPublishingRes) {
          setRelayMap(relayMapCopy)
          setNewRelayURI('')

          dispatch(setRelayMapAction(relayMapCopy))

          nostrController.getRelayInfo([relayURI])

          toast.success(relayMapPublishingRes)
        }

        setNewRelayURIerror(undefined)
      } else {
        toast.error(`Relay '${relayURI}' wasn't added.`)

        setNewRelayURIerror(`Connection to '${relayURI}' was unsuccessful.`)
      }
    }
  }

  // Handle relay open and close state
  const handleRelayInfo = (relay: string) => {
    if (relaysInfo) {
      const info = relaysInfo[relay]

      if (info) {
        let displayRelaysInfoCopy: string[] = JSON.parse(
          JSON.stringify(displayRelaysInfo)
        )

        if (displayRelaysInfoCopy.includes(relay)) {
          displayRelaysInfoCopy = displayRelaysInfoCopy.filter(
            (rel) => rel !== relay
          )
        } else {
          displayRelaysInfoCopy.push(relay)
        }

        setDisplayRelaysInfo(displayRelaysInfoCopy)
      }
    }
  }

  return (
    <Box className={styles.container}>
      <Box className={styles.relayAddContainer}>
        <TextField
          label="Add new relay"
          value={newRelayURI}
          onChange={(e) => setNewRelayURI(e.target.value)}
          helperText={newRelayURIerror}
          error={!!newRelayURIerror}
          InputProps={{
            startAdornment: (
              <InputAdornment position="start">
                {webSocketPrefix}
              </InputAdornment>
            )
          }}
          className={styles.relayURItextfield}
        />
        <Button variant="contained" onClick={() => handleAddNewRelay()}>
          Add
        </Button>
      </Box>
      <Box className={styles.sectionTitle}>
        <RouterIcon className={styles.sectionIcon} />
        <span>YOUR RELAYS</span>
      </Box>
      {relayMap && (
        <Box className={styles.relaysContainer}>
          {Object.keys(relayMap).map((relay, i) => (
            <Box className={styles.relay} key={`relay_${i}`}>
              <List>
                <ListItem>
                  <span
                    className={[
                      styles.connectionStatus,
                      relaysConnectionStatus
                        ? relaysConnectionStatus[relay] ===
                          RelayConnectionState.Connected
                          ? styles.connectionStatusConnected
                          : styles.connectionStatusNotConnected
                        : styles.connectionStatusUnknown
                    ].join(' ')}
                  />
                  {relaysInfo &&
                    relaysInfo[relay] &&
                    relaysInfo[relay].limitation &&
                    relaysInfo[relay].limitation?.payment_required && (
                      <Tooltip title="Paid Relay" arrow placement="top">
                        <ElectricBoltIcon
                          className={styles.lightningIcon}
                          color="warning"
                          onClick={() => handleRelayInfo(relay)}
                        />
                      </Tooltip>
                    )}

                  <ListItemText primary={relay} />

                  <Box
                    className={styles.leaveRelayContainer}
                    onClick={() => handleLeaveRelay(relay)}
                  >
                    <LogoutIcon />
                    <span>Leave</span>
                  </Box>
                </ListItem>
                <Divider className={styles.relayDivider} />
                <ListItem>
                  <ListItemText
                    primary="Publish to this relay?"
                    secondary={
                      relaysInfo && relaysInfo[relay] ? (
                        <span
                          onClick={() => handleRelayInfo(relay)}
                          className={styles.showInfo}
                        >
                          Show info{' '}
                          {displayRelaysInfo.includes(relay) ? (
                            <KeyboardArrowUpIcon
                              className={styles.showInfoIcon}
                            />
                          ) : (
                            <KeyboardArrowDownIcon
                              className={styles.showInfoIcon}
                            />
                          )}
                        </span>
                      ) : (
                        ''
                      )
                    }
                  />
                  <Switch
                    checked={relayMap[relay].write}
                    onChange={(event) => handleRelayWriteChange(relay, event)}
                  />
                </ListItem>
                {displayRelaysInfo.includes(relay) && (
                  <>
                    <Divider className={styles.relayDivider} />
                    <ListItem>
                      <Box className={styles.relayInfoContainer}>
                        {relaysInfo &&
                          relaysInfo[relay] &&
                          Object.keys(relaysInfo[relay]).map((key: string) => {
                            const infoTitle = capitalizeFirstLetter(
                              key.replace('_', ' ')
                            )
                            let infoValue = (relaysInfo[relay] as any)[key]

                            switch (key) {
                              case 'pubkey':
                                infoValue = shorten(hexToNpub(infoValue), 15)

                                break

                              case 'limitation':
                                infoValue = (
                                  <ul key={`${i}_${key}`}>
                                    {Object.keys(infoValue).map((valueKey) => (
                                      <li key={`${i}_${key}_${valueKey}`}>
                                        <span
                                          className={styles.relayInfoSubTitle}
                                        >
                                          {capitalizeFirstLetter(
                                            valueKey.split('_').join(' ')
                                          )}
                                          :
                                        </span>{' '}
                                        {`${infoValue[valueKey]}`}
                                      </li>
                                    ))}
                                  </ul>
                                )

                                break

                              case 'fees':
                                infoValue = (
                                  <ul>
                                    {Object.keys(infoValue).map((valueKey) => (
                                      <li key={`${i}_${key}_${valueKey}`}>
                                        <span
                                          className={styles.relayInfoSubTitle}
                                        >
                                          {capitalizeFirstLetter(
                                            valueKey.split('_').join(' ')
                                          )}
                                          :
                                        </span>{' '}
                                        {`${infoValue[valueKey].map((fee: RelayFee) => `${fee.amount} ${fee.unit}`)}`}
                                      </li>
                                    ))}
                                  </ul>
                                )
                                break
                              default:
                                break
                            }

                            if (Array.isArray(infoValue)) {
                              infoValue = infoValue.join(', ')
                            }

                            return (
                              <span key={`${i}_${key}_container`}>
                                <span className={styles.relayInfoTitle}>
                                  {infoTitle}:
                                </span>{' '}
                                {infoValue}
                                {key === 'pubkey' ? (
                                  <ContentCopyIcon
                                    className={styles.copyItem}
                                    onClick={() => {
                                      navigator.clipboard.writeText(
                                        hexToNpub(
                                          (relaysInfo[relay] as any)[key]
                                        )
                                      )

                                      toast.success('Copied to clipboard', {
                                        autoClose: 1000,
                                        hideProgressBar: true
                                      })
                                    }}
                                  />
                                ) : null}
                              </span>
                            )
                          })}
                      </Box>
                    </ListItem>
                  </>
                )}
              </List>
            </Box>
          ))}
        </Box>
      )}
    </Box>
  )
}
