import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'

const LOVE = 'love'

interface SwipeEntry {
  action?: string
  swipedAt?: number
}

/**
 * Creates a revealed date match when both partners have swiped LOVE on the
 * same date idea.
 *
 * Trigger: couples/{coupleId}/date_swipes/{dateIdeaId} (onWrite)
 *
 * The `date_matches` collection is server-write-only — Firestore rules deny all
 * client writes (`allow create, update, delete: if false`). This trigger is
 * therefore the single source of truth for match creation. The client only
 * records swipes and observes `date_matches` for the result.
 *
 * Idempotency: the match document id is the date idea id and creation runs in a
 * transaction, so repeated swipes on the same idea and concurrent invocations
 * never produce a duplicate match.
 */
export const createDateMatchOnMutualLove = functions.firestore
  .document('couples/{coupleId}/date_swipes/{dateIdeaId}')
  .onWrite(async (change, context) => {
    const after = change.after.data()
    if (!after) return // swipe document was deleted

    const actions = (after.actions ?? {}) as Record<string, SwipeEntry>
    const lovedBy = Object.entries(actions)
      .filter(([, entry]) => entry?.action === LOVE)
      .map(([uid]) => uid)
      .sort()

    // A match needs both partners to have loved the same idea.
    if (lovedBy.length < 2) return

    const { coupleId, dateIdeaId } = context.params as {
      coupleId: string
      dateIdeaId: string
    }

    const db = admin.firestore()
    const matchRef = db
      .collection('couples')
      .doc(coupleId)
      .collection('date_matches')
      .doc(dateIdeaId)

    await db.runTransaction(async (tx) => {
      const existing = await tx.get(matchRef)
      if (existing.exists) return
      tx.set(matchRef, {
        dateIdeaId,
        matchedBy: lovedBy,
        revealedAt: admin.firestore.FieldValue.serverTimestamp(),
        fcmNotified: false,
      })
    })

    // Atomically claim FCM send so concurrent trigger invocations don't double-send.
    const shouldSend = await db.runTransaction(async (tx) => {
      const doc = await tx.get(matchRef)
      if (!doc.exists || doc.data()?.fcmNotified === true) return false
      tx.update(matchRef, { fcmNotified: true })
      return true
    })

    if (shouldSend) {
      const coupleDoc = await db.collection('couples').doc(coupleId).get()
      const userIds = (coupleDoc.data()?.userIds ?? []) as string[]
      await Promise.all(userIds.map((uid) => notifyDateMatch(db, uid, coupleId, dateIdeaId)))
    }
  })

async function notifyDateMatch(
  db: admin.firestore.Firestore,
  userId: string,
  coupleId: string,
  dateIdeaId: string
): Promise<void> {
  await db.collection('users').doc(userId).collection('notification_queue').add({
    type: 'date_match',
    title: "It's a match!",
    body: "You both want to go on this date. Time to make it happen.",
    read: false,
    createdAt: admin.firestore.FieldValue.serverTimestamp(),
  })

  const tokens = await getUserTokens(db, userId)
  if (tokens.length === 0) return

  await Promise.allSettled(
    tokens.map((token) =>
      admin.messaging().send({
        token,
        notification: {
          title: "It's a match!",
          body: "You both want to go on this date. Time to make it happen.",
        },
        data: {
          type: 'date_match',
          couple_id: coupleId,
          date_idea_id: dateIdeaId,
        },
      })
    )
  )
}

async function getUserTokens(
  db: admin.firestore.Firestore,
  userId: string
): Promise<string[]> {
  const tokens: string[] = []
  const userDoc = await db.collection('users').doc(userId).get()
  const legacy = userDoc.data()?.fcmToken
  if (typeof legacy === 'string' && legacy.length > 0) tokens.push(legacy)

  const snap = await db.collection('users').doc(userId).collection('fcmTokens').get()
  snap.docs.forEach((doc) => {
    const t = doc.data()?.token
    if (typeof t === 'string' && t.length > 0 && !tokens.includes(t)) tokens.push(t)
  })
  return tokens
}
