import {chunk} from '@crumbs/core/collections'
import {inArray} from 'drizzle-orm'

import {db, schema} from '../db/client'

const EXPO_PUSH_URL = 'https://exp.host/--/api/v2/push/send'
const CHUNK_SIZE = 100

// keep push bodies glanceable; the full text lives in the app
export const PUSH_PREVIEW_MAX = 80

export type PushMessage = {
  to: string
  title: string
  body: string
  data?: Record<string, unknown>
}

type PushTicket = {
  status: 'ok' | 'error'
  details?: {error?: string}
}

// returns tokens expo reports as dead so callers can prune them
const sendPushMessages = async (
  messages: PushMessage[],
): Promise<{invalidTokens: string[]}> => {
  const invalidTokens: string[] = []
  for (const batch of chunk(messages, CHUNK_SIZE)) {
    const res = await fetch(EXPO_PUSH_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Accept: 'application/json',
      },
      body: JSON.stringify(batch),
    }).catch((err) => {
      console.error('push send failed', err)
      return null
    })
    if (!res?.ok) {
      console.error('push send failed', res?.status)
      continue
    }
    // a non-json response must not abort the remaining batches
    const {data} = (await res.json().catch((err: unknown) => {
      console.error('push response parse failed', err)
      return {}
    })) as {data?: PushTicket[]}
    data?.forEach((ticket, i) => {
      if (
        ticket.status === 'error' &&
        ticket.details?.error === 'DeviceNotRegistered'
      )
        invalidTokens.push(batch[i]!.to)
    })
  }
  return {invalidTokens}
}

// send and prune tokens expo reports dead — the one owner of invalid-token
// cleanup
export const dispatchPushMessages = async (
  messages: PushMessage[],
): Promise<void> => {
  if (!messages.length) return
  const {invalidTokens} = await sendPushMessages(messages)
  if (invalidTokens.length)
    await db
      .delete(schema.pushTokens)
      .where(inArray(schema.pushTokens.token, invalidTokens))
}

// fan a message out to every device of the given users
export const sendPushToUsers = async (
  userIds: string[],
  toMessage: (token: string) => PushMessage,
): Promise<void> => {
  if (!userIds.length) return
  const tokens = await db
    .select({token: schema.pushTokens.token})
    .from(schema.pushTokens)
    .where(inArray(schema.pushTokens.userId, userIds))
  await dispatchPushMessages(
    tokens.map(({token}) => toMessage(token)),
  )
}
