import { EventTemplate, Filter, kinds, nip19 } from 'nostr-tools'
import { compareObjects, queryNip05, unixNow } from '.'
import {
  MetadataController,
  NostrController,
  relayController
} from '../controllers'
import { NostrJoiningBlock, RelayInfoObject } from '../types'
import NDK, { NDKEvent, NDKSubscription } from '@nostr-dev-kit/ndk'
import store from '../store/store'
import { setRelayInfoAction } from '../store/actions'

export const getNostrJoiningBlockNumber = async (
  hexKey: string
): Promise<NostrJoiningBlock | null> => {
  const metadataController = MetadataController.getInstance()

  const relaySet = await metadataController.findRelayListMetadata(hexKey)

  const userRelays: string[] = []

  // find user's relays
  if (relaySet.write.length > 0) {
    userRelays.push(...relaySet.write)
  } else {
    const metadata = await metadataController.findMetadata(hexKey)
    if (!metadata) return null

    const metadataContent =
      metadataController.extractProfileMetadataContent(metadata)

    if (metadataContent?.nip05) {
      const nip05Profile = await queryNip05(metadataContent.nip05)

      if (nip05Profile && nip05Profile.pubkey === hexKey) {
        userRelays.push(...nip05Profile.relays)
      }
    }
  }

  if (userRelays.length === 0) return null

  // filter for finding user's first kind 0 event
  const eventFilter: Filter = {
    kinds: [kinds.Metadata],
    authors: [hexKey]
  }

  // find user's kind 0 event published on user's relays
  const event = await relayController.fetchEvent(eventFilter, userRelays)

  if (event) {
    const { created_at } = event

    // initialize job request
    const jobEventTemplate: EventTemplate = {
      content: '',
      created_at: unixNow(),
      kind: 68001,
      tags: [
        ['i', `${created_at * 1000}`],
        ['j', 'blockChain-block-number']
      ]
    }

    const nostrController = NostrController.getInstance()

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

    const relays = [
      'wss://relay.damus.io',
      'wss://relay.primal.net',
      'wss://relayable.org'
    ]

    await relayController.publish(jobSignedEvent, relays).catch((err) => {
      console.error(
        'Error occurred in publish blockChain-block-number DVM job',
        err
      )
    })

    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))
      })
    }

    const dvmNDK = new NDK({
      explicitRelayUrls: relays
    })

    await dvmNDK.connect(2000)

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

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

    const encodedEventPointer = nip19.neventEncode({
      id: event.id,
      relays: userRelays,
      author: event.pubkey,
      kind: event.kind
    })

    return {
      block: parseInt(dvmJobResult),
      encodedEventPointer
    }
  }

  return null
}

/**
 * Sets information about relays into relays.info app state.
 * @param relayURIs - relay URIs to get information about
 */
export 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)

  const relays = [
    'wss://relay.damus.io',
    'wss://relay.primal.net',
    'wss://relayable.org'
  ]

  // publish job request
  await relayController.publish(jobSignedEvent, relays)

  console.log('jobSignedEvent :>> ', jobSignedEvent)

  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))
    })
  }

  const dvmNDK = new NDK({
    explicitRelayUrls: relays
  })

  await dvmNDK.connect(2000)

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

  // asynchronously get block number 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 relaysInfo: RelayInfoObject

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

  if (
    relaysInfo &&
    !compareObjects(store.getState().relays?.info, relaysInfo)
  ) {
    store.dispatch(setRelayInfoAction(relaysInfo))
  }
}
