import { useCallback } from 'react'
import { useToast } from './toast'
import { Button } from 'react-bootstrap'
import { DEFAULT_CROSSPOSTING_RELAYS, crosspost } from '@/lib/nostr'
import { callWithTimeout } from '@/lib/time'
import { gql, useMutation, useQuery, useLazyQuery } from '@apollo/client'
import { SETTINGS } from '@/fragments/users'
import { ITEM_FULL_FIELDS, POLL_FIELDS } from '@/fragments/items'

function itemToContent (item, { includeTitle = true } = {}) {
  let content = includeTitle ? item.title : ''

  if (item.url) {
    content += `\n${item.url}`
  }

  if (item.text) {
    content += `\n\n${item.text}`
  }

  content += `\n\noriginally posted at https://stacker.news/items/${item.id}`

  return content.trim()
}

function discussionToEvent (item) {
  const createdAt = Math.floor(Date.now() / 1000)

  return {
    created_at: createdAt,
    kind: 30023,
    content: itemToContent(item, { includeTitle: false }),
    tags: [
      ['d', item.id.toString()],
      ['title', item.title],
      ['published_at', createdAt.toString()]
    ]
  }
}

function linkToEvent (item) {
  const createdAt = Math.floor(Date.now() / 1000)

  return {
    created_at: createdAt,
    kind: 1,
    content: itemToContent(item),
    tags: []
  }
}

function pollToEvent (item) {
  const createdAt = Math.floor(Date.now() / 1000)

  const expiresAt = createdAt + 86400

  return {
    created_at: createdAt,
    kind: 1,
    content: itemToContent(item),
    tags: [
      ['poll', 'single', expiresAt.toString(), item.title, ...item.poll.options.map(op => op?.option.toString())]
    ]
  }
}

function bountyToEvent (item) {
  const createdAt = Math.floor(Date.now() / 1000)

  return {
    created_at: createdAt,
    kind: 30402,
    content: itemToContent(item),
    tags: [
      ['d', item.id.toString()],
      ['title', item.title],
      ['location', `https://stacker.news/items/${item.id}`],
      ['price', item.bounty.toString(), 'SATS'],
      ['t', 'bounty'],
      ['published_at', createdAt.toString()]
    ]
  }
}

export default function useCrossposter () {
  const toaster = useToast()
  const { data } = useQuery(SETTINGS)
  const userRelays = data?.settings?.privates?.nostrRelays || []
  const relays = [...DEFAULT_CROSSPOSTING_RELAYS, ...userRelays]

  const [fetchItem] = useLazyQuery(
    gql`
      ${ITEM_FULL_FIELDS}
      ${POLL_FIELDS}
      query Item($id: ID!) {
        item(id: $id) {
          ...ItemFullFields
          ...PollFields
        }
      }`, {
      fetchPolicy: 'no-cache'
    }
  )

  const [updateNoteId] = useMutation(
    gql`
      mutation updateNoteId($id: ID!, $noteId: String!) {
        updateNoteId(id: $id, noteId: $noteId) {
          id
          noteId
        }
      }`
  )

  const relayError = (failedRelays) => {
    return new Promise(resolve => {
      const handleSkip = () => {
        resolve('skip')

        removeToast()
      }

      const removeToast = toaster.warning(
        <>
          Crossposting failed for {failedRelays.join(', ')} <br />
          <Button
            variant='link' onClick={() => {
              resolve('retry')
              setTimeout(() => {
                removeToast()
              }, 1000)
            }}
          >Retry
          </Button>
          {' | '}
          <Button
            variant='link' onClick={handleSkip}
          >Skip
          </Button>
        </>,
        {
          onClose: () => handleSkip(),
          autohide: false
        }
      )
    })
  }

  const crosspostError = (errorMessage) => {
    return toaster.warning(`crossposting failed: ${errorMessage}`)
  }

  async function handleEventCreation (item) {
    const determineItemType = (item) => {
      const typeMap = {
        url: 'link',
        bounty: 'bounty',
        pollCost: 'poll'
      }

      for (const [key, type] of Object.entries(typeMap)) {
        if (item[key]) {
          return type
        }
      }

      // Default
      return 'discussion'
    }

    const itemType = determineItemType(item)
    switch (itemType) {
      case 'discussion':
        return discussionToEvent(item)
      case 'link':
        return linkToEvent(item)
      case 'bounty':
        return bountyToEvent(item)
      case 'poll':
        return pollToEvent(item)
      default:
        return crosspostError('Unknown item type')
    }
  }

  const fetchItemData = async (itemId) => {
    try {
      const { data } = await fetchItem({ variables: { id: itemId } })

      return data?.item
    } catch (e) {
      console.error(e)
      return null
    }
  }

  const crosspostItem = async item => {
    let failedRelays
    let allSuccessful = false
    let noteId

    const event = await handleEventCreation(item)
    if (!event) return { allSuccessful, noteId }

    do {
      try {
        const result = await crosspost(event, failedRelays || relays)

        if (result.error) {
          failedRelays = []
          throw new Error(result.error)
        }

        noteId = result.noteId
        failedRelays = result?.failedRelays?.map(relayObj => relayObj.relay) || []

        if (failedRelays.length > 0) {
          const userAction = await relayError(failedRelays)

          if (userAction === 'skip') {
            toaster.success('Skipped failed relays.')
            // wait 2 seconds then break
            await new Promise(resolve => setTimeout(resolve, 2000))
            break
          }
        } else {
          allSuccessful = true
        }
      } catch (error) {
        await crosspostError(error.message)

        // wait 2 seconds to show error then break
        await new Promise(resolve => setTimeout(resolve, 2000))
        return { allSuccessful, noteId }
      }
    } while (failedRelays.length > 0)

    return { allSuccessful, noteId }
  }

  const handleCrosspost = useCallback(async (itemId) => {
    try {
      const pubkey = await callWithTimeout(() => window.nostr.getPublicKey(), 10000)
      if (!pubkey) throw new Error('failed to get pubkey')
    } catch (e) {
      throw new Error(`Nostr extension error: ${e.message}`)
    }

    let noteId

    try {
      if (itemId) {
        const item = await fetchItemData(itemId)

        const crosspostResult = await crosspostItem(item)
        noteId = crosspostResult?.noteId
        if (noteId) {
          await updateNoteId({
            variables: {
              id: itemId,
              noteId
            }
          })
        }
      }
    } catch (e) {
      console.error(e)
      await crosspostError(e.message)
    }
  }, [updateNoteId, relays, toaster])

  return handleCrosspost
}