import { EventTemplate } from 'nostr-tools'
import { MetadataController, NostrController } from '.'
import { appPrivateRoutes } from '../routes'
import {
  setAuthState,
  setMetadataEvent,
  setRelayMapAction
} from '../store/actions'
import store from '../store/store'
import { SignedEvent } from '../types'
import {
  base64DecodeAuthToken,
  base64EncodeSignedEvent,
  compareObjects,
  getAuthToken,
  getRelayMap,
  getVisitedLink,
  saveAuthToken,
  unixNow
} from '../utils'

export class AuthController {
  private nostrController: NostrController
  private metadataController: MetadataController

  constructor() {
    this.nostrController = NostrController.getInstance()
    this.metadataController = MetadataController.getInstance()
  }

  /**
   * Function will authenticate user by signing an auth event
   * which is done by calling the sign() function, where appropriate
   * method will be chosen (extension, nsecbunker or keys)
   *
   * @param pubkey of the user trying to login
   * @returns url to redirect if authentication successfull
   * or error if otherwise
   */
  async authAndGetMetadataAndRelaysMap(pubkey: string) {
    const emptyMetadata = this.metadataController.getEmptyMetadataEvent()

    this.metadataController
      .findMetadata(pubkey)
      .then((event) => {
        if (event) {
          store.dispatch(setMetadataEvent(event))
        } else {
          store.dispatch(setMetadataEvent(emptyMetadata))
        }
      })
      .catch((err) => {
        console.warn('Error occurred while finding metadata', err)

        store.dispatch(setMetadataEvent(emptyMetadata))
      })

    // Nostr uses unix timestamps
    const timestamp = unixNow()
    const { hostname } = window.location

    const authEvent: EventTemplate = {
      kind: 27235,
      tags: [],
      content: `${hostname}-${timestamp}`,
      created_at: timestamp
    }

    const signedAuthEvent = await this.nostrController.signEvent(authEvent)
    this.createAndSaveAuthToken(signedAuthEvent)

    store.dispatch(
      setAuthState({
        loggedIn: true,
        usersPubkey: pubkey
      })
    )

    const relayMap = await getRelayMap(pubkey)

    if (Object.keys(relayMap).length < 1) {
      // Navigate user to relays page if relay map is empty
      return Promise.resolve(appPrivateRoutes.relays)
    }

    if (store.getState().auth?.loggedIn) {
      if (!compareObjects(store.getState().relays?.map, relayMap.map))
        store.dispatch(setRelayMapAction(relayMap.map))
    }

    const currentLocation = window.location.hash.replace('#', '')

    if (!Object.values(appPrivateRoutes).includes(currentLocation)) {
      // User did change the location to one of the private routes
      const visitedLink = getVisitedLink()

      if (visitedLink) {
        const { pathname, search } = visitedLink

        return Promise.resolve(`${pathname}${search}`)
      } else {
        // Navigate user in
        return Promise.resolve(appPrivateRoutes.homePage)
      }
    }
  }

  checkSession() {
    const savedAuthToken = getAuthToken()

    if (savedAuthToken) {
      const signedEvent = base64DecodeAuthToken(savedAuthToken)

      store.dispatch(
        setAuthState({
          loggedIn: true,
          usersPubkey: signedEvent.pubkey
        })
      )

      return
    }

    store.dispatch(
      setAuthState({
        loggedIn: false,
        usersPubkey: undefined
      })
    )
  }

  private createAndSaveAuthToken(signedAuthEvent: SignedEvent) {
    const base64Encoded = base64EncodeSignedEvent(signedAuthEvent)

    // save newly created auth token (base64 nostr singed event) in local storage along with expiry time
    saveAuthToken(base64Encoded)
    return base64Encoded
  }
}
