import CenteredSpinner from '@inject/shared/components/CenteredSpinner'
import { sessionUrl } from '@inject/shared/config'
import Keys from '@inject/shared/localstorage/keys'
import type { FC, ReactNode } from 'react'
import { useEffect, useState } from 'react'
import { version } from '../../package.json'
import useBackendVersion from '../utils/useBackendVersion'
import ErrorDialog from './ErrorDialog'
import LoginDialog from './LoginDialog'
import { useHost } from './host'
import { sessionid, useSessionid } from './sessionid'

interface ConnectionProps {
  children: ReactNode
}

enum ConnectionStatus {
  CONNECTED,
  FAILED,
  CONNECTING,
  NEED_AUTH,
}

const versionsCompatible = (backendVersion: string, frontendVersion: string) =>
  backendVersion.split('.').slice(0, 2).join('.') ===
  frontendVersion.split('.').slice(0, 2).join('.')

const Connection: FC<ConnectionProps> = ({ children }) => {
  const hostVar = useHost()
  const sessionId = useSessionid()
  const backendVersion = useBackendVersion()

  const [state, setState] = useState<ConnectionStatus>(
    ConnectionStatus.CONNECTING
  )

  useEffect(() => {
    if (state === ConnectionStatus.CONNECTED && !hostVar) {
      // resets back to connecting state if host or ws is not set
      setState(ConnectionStatus.CONNECTING)
      return
    }
    /*
     * If the state is connected, we don't need to do anything
     * If the state is connecting, we need to check if the host is set
     * If the state is failed, we need to show the dialog
     */
    switch (state) {
      case ConnectionStatus.CONNECTED:
        if (!sessionId) {
          setState(ConnectionStatus.NEED_AUTH)
        }
        break
      case ConnectionStatus.CONNECTING:
        /*
         * Connection initialization
         * checks if the server is reachable
         * sets the state to connected if successful
         * sets the state to failed if unsuccessful
         */
        fetch(sessionUrl(hostVar), {
          credentials: 'include',
          method: 'GET',
          headers: {
            'session-id': localStorage.getItem(Keys.SESSION_ID) || '',
          },
        })
          .then(res => res.json())
          .then((res: { sessionid: string | null }) => {
            if (res.sessionid) {
              sessionid(res.sessionid)
              setState(ConnectionStatus.CONNECTED)
            } else {
              sessionid('')
              setState(ConnectionStatus.NEED_AUTH)
            }
          })
          .catch(() => {
            sessionid('')
            setState(ConnectionStatus.FAILED)
          })
        break
      case ConnectionStatus.FAILED:
        /*
         * Connection failed
         * shows the dialog
         */
        break
      case ConnectionStatus.NEED_AUTH:
        if (sessionId) {
          setState(ConnectionStatus.CONNECTING)
        }
    }
  }, [state, hostVar, sessionId])

  if (
    state === ConnectionStatus.CONNECTED ||
    state === ConnectionStatus.NEED_AUTH
  ) {
    if (!backendVersion) {
      return <CenteredSpinner />
    }
    if (!versionsCompatible(backendVersion, version)) {
      return (
        <ErrorDialog title='Compatibility error'>
          <p>
            The backend server at <code>{hostVar}</code> is not compatible.
          </p>
          <p>
            The version of the backend server is <code>{backendVersion}</code>,
            and the version of the frontend client is <code>{version}</code>.
          </p>
        </ErrorDialog>
      )
    }
  }

  switch (state) {
    case ConnectionStatus.CONNECTING:
      return <CenteredSpinner />
    case ConnectionStatus.CONNECTED:
      return children
    case ConnectionStatus.FAILED:
      return (
        <ErrorDialog>
          <p>
            Unable to connect to a backend server at <code>{hostVar}</code>.
          </p>
          <p>Please, verify that the backend server is running.</p>
        </ErrorDialog>
      )
    case ConnectionStatus.NEED_AUTH:
      return <LoginDialog />
  }
}

export default Connection
