import { NDKEvent, NDKSubscription } from '@nostr-dev-kit/ndk'
import { EventTemplate } from 'nostr-tools'
import { NostrController } from '../controllers'
import { setRelayInfoAction } from '../store/actions'
import { RelayInfoObject } from '../types'
import { compareObjects, unixNow } from '../utils'
import { useAppDispatch, useAppSelector } from './store'
import { useNDKContext } from './useNDKContext'

export const useDvm = () => {
  const dvmRelays = [
    'wss://relay.damus.io',
    'wss://relay.primal.net',
    'wss://relayable.org'
  ]

  const relayInfo = useAppSelector((state) => state.relays.info)

  const { ndk, publish } = useNDKContext()
  const dispatch = useAppDispatch()

  /**
   * Sets information about relays into relays.info app state.
   * @param relayURIs - relay URIs to get information about
   */
  const getRelayInfo = async (relayURIs: string[]) => {
    // initialize job request
    const jobEventTemplate: EventTemplate = {
      content: '',
      created_at: unixNow(),
      kind: 68001,
      tags: [
        ['i', `${JSON.stringify(relayURIs)}`],
        ['j', 'relay-info']
      ]
    }

    const nostrController = NostrController.getInstance()

    // sign job request event
    const jobSignedEvent = await nostrController.signEvent(jobEventTemplate)

    // publish job request
    const ndkEvent = new NDKEvent(ndk, jobSignedEvent)
    await publish(ndkEvent, dvmRelays)

    const subscribeWithTimeout = (
      subscription: NDKSubscription,
      timeoutMs: number
    ): Promise<string> => {
      return new Promise((resolve, reject) => {
        const eventHandler = (event: NDKEvent) => {
          subscription.stop()
          resolve(event.content)
        }

        subscription.on('event', eventHandler)

        // Set up a timeout to stop the subscription after a specified time
        const timeout = setTimeout(() => {
          subscription.stop() // Stop the subscription
          reject(new Error('Subscription timed out')) // Reject the promise with a timeout error
        }, timeoutMs)

        // Handle subscription close event
        subscription.on('close', () => clearTimeout(timeout))
      })
    }

    // filter for getting DVM job's result
    const sub = ndk.subscribe({
      kinds: [68002 as number],
      '#e': [jobSignedEvent.id],
      '#p': [jobSignedEvent.pubkey]
    })

    // asynchronously get relay info from dvm job with 20 seconds timeout
    const dvmJobResult = await subscribeWithTimeout(sub, 20000)

    if (!dvmJobResult) {
      return Promise.reject(`Relay(s) information wasn't received`)
    }

    let newRelaysInfo: RelayInfoObject

    try {
      newRelaysInfo = JSON.parse(dvmJobResult)
    } catch (error) {
      return Promise.reject(`Invalid relay(s) information.`)
    }

    if (newRelaysInfo && !compareObjects(relayInfo, newRelaysInfo)) {
      dispatch(setRelayInfoAction(newRelaysInfo))
    }
  }

  return { getRelayInfo }
}
