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

export const RelaysPage = () => {
  const usersPubkey = useAppSelector((state) => state.auth?.usersPubkey)

  const dispatch = useAppDispatch()

  const [newRelayURI, setNewRelayURI] = useState<string>()
  const [newRelayURIerror, setNewRelayURIerror] = useState<string>()

  const relayMap = useAppSelector((state) => state.relays?.map)
  const relaysInfo = useAppSelector((state) => state.relays?.info)

  const webSocketPrefix = 'wss://'

  useDidMount(() => {
    if (usersPubkey) {
      getRelayMap(usersPubkey).then((newRelayMap) => {
        if (!compareObjects(relayMap, newRelayMap.map)) {
          dispatch(setRelayMapAction(newRelayMap.map))
        }
      })
    }
  })

  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 publishRelayMap(
            relayMapCopy,
            usersPubkey,
            [relay]
          ).catch((err) => handlePublishRelayMapError(err))

          if (relayMapPublishingRes) {
            toast.success(relayMapPublishingRes)

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

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  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 publishRelayMap(
            relayMapCopy,
            usersPubkey
          ).catch((err) => handlePublishRelayMapError(err))

          if (relayMapPublishingRes) {
            toast.success(relayMapPublishingRes)

            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 relay = await relayController.connectRelay(relayURI)

      if (relay && relay.connected) {
        const relayMapCopy = JSON.parse(JSON.stringify(relayMap))

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

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

        if (relayMapPublishingRes) {
          setNewRelayURI('')

          dispatch(setRelayMapAction(relayMapCopy))

          getRelayInfo([relayURI])

          toast.success(relayMapPublishingRes)
        }

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

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

  return (
    <Container 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) => (
            <RelayItem
              key={relay}
              relayURI={relay}
              isWriteRelay={relayMap[relay].write}
              relayInfo={relaysInfo ? relaysInfo[relay] : undefined}
              handleLeaveRelay={handleLeaveRelay}
              handleRelayWriteChange={handleRelayWriteChange}
            />
          ))}
        </Box>
      )}
      <Footer />
    </Container>
  )
}

type RelayItemProp = {
  relayURI: string
  isWriteRelay: boolean
  relayInfo?: RelayInfo
  handleLeaveRelay: (relay: string) => void
  handleRelayWriteChange: (
    relay: string,
    event: React.ChangeEvent<HTMLInputElement>
  ) => Promise<void>
}

const RelayItem = ({
  relayURI,
  isWriteRelay,
  relayInfo,
  handleLeaveRelay,
  handleRelayWriteChange
}: RelayItemProp) => {
  const [relayConnectionStatus, setRelayConnectionStatus] =
    useState<RelayConnectionState>()

  const [displayRelayInfo, setDisplayRelayInfo] = useState(false)

  useDidMount(() => {
    relayController.connectRelay(relayURI).then((relay) => {
      if (relay && relay.connected) {
        setRelayConnectionStatus(RelayConnectionState.Connected)
      } else {
        setRelayConnectionStatus(RelayConnectionState.NotConnected)
      }
    })
  })

  return (
    <>
      <Box className={styles.relay}>
        <List>
          <ListItem>
            <span
              className={[
                styles.connectionStatus,
                relayConnectionStatus
                  ? relayConnectionStatus === RelayConnectionState.Connected
                    ? styles.connectionStatusConnected
                    : styles.connectionStatusNotConnected
                  : styles.connectionStatusUnknown
              ].join(' ')}
            />
            {relayInfo &&
              relayInfo.limitation &&
              relayInfo.limitation?.payment_required && (
                <Tooltip title="Paid Relay" arrow placement="top">
                  <ElectricBoltIcon
                    className={styles.lightningIcon}
                    color="warning"
                    onClick={() => setDisplayRelayInfo((prev) => !prev)}
                  />
                </Tooltip>
              )}

            <ListItemText primary={relayURI} />

            <Box
              className={styles.leaveRelayContainer}
              onClick={() => handleLeaveRelay(relayURI)}
            >
              <LogoutIcon />
              <span>Leave</span>
            </Box>
          </ListItem>
          <Divider className={styles.relayDivider} />
          <ListItem>
            <ListItemText
              primary="Publish to this relay?"
              secondary={
                relayInfo ? (
                  <span
                    onClick={() => setDisplayRelayInfo((prev) => !prev)}
                    className={styles.showInfo}
                  >
                    Show info{' '}
                    {displayRelayInfo ? (
                      <KeyboardArrowUpIcon className={styles.showInfoIcon} />
                    ) : (
                      <KeyboardArrowDownIcon className={styles.showInfoIcon} />
                    )}
                  </span>
                ) : (
                  ''
                )
              }
            />
            <Switch
              checked={isWriteRelay}
              onChange={(event) => handleRelayWriteChange(relayURI, event)}
            />
          </ListItem>
          {displayRelayInfo && (
            <>
              <Divider className={styles.relayDivider} />
              <ListItem>
                <Box className={styles.relayInfoContainer}>
                  {relayInfo &&
                    Object.keys(relayInfo).map((key: string) => {
                      const infoTitle = capitalizeFirstLetter(
                        key.replace('_', ' ')
                      )
                      // eslint-disable-next-line @typescript-eslint/no-explicit-any
                      let infoValue = (relayInfo as any)[key]

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

                          break

                        case 'limitation':
                          infoValue = (
                            <ul>
                              {Object.keys(infoValue).map((valueKey) => (
                                <li key={`${relayURI}_${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={`${relayURI}_${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={`${relayURI}_${key}_container`}>
                          <span className={styles.relayInfoTitle}>
                            {infoTitle}:
                          </span>{' '}
                          {infoValue}
                          {key === 'pubkey' ? (
                            <ContentCopyIcon
                              className={styles.copyItem}
                              onClick={() => {
                                navigator.clipboard.writeText(
                                  // eslint-disable-next-line @typescript-eslint/no-explicit-any
                                  hexToNpub((relayInfo as any)[key])
                                )

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