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

/**
 * HTTPS callable that atomically unlinks a couple.
 *
 * The client cannot do this directly because Firestore rules prevent a user
 * from writing the partner's user document. The Admin SDK bypasses those rules.
 *
 * Steps:
 *  1. Verify the caller is a member of their current couple.
 *  2. Clear coupleId on both user docs (batch — atomic).
 *  3. Recursively delete the couple doc and all its subcollections.
 *
 * The existing onCoupleLeave Firestore trigger fires after step 2 and handles
 * partner notification, so we don't duplicate that here.
 */
export const leaveCoupleCallable = functions.https.onCall(async (_data, context) => {
  const callerId = context.auth?.uid
  if (!callerId) {
    throw new functions.https.HttpsError('unauthenticated', 'Must be signed in.')
  }
  if (!context.app) {
    throw new functions.https.HttpsError('failed-precondition', 'App Check verification required.')
  }

  const db = admin.firestore()

  const userDoc = await db.collection('users').doc(callerId).get()
  const coupleId = userDoc.data()?.coupleId as string | undefined
  if (!coupleId) {
    // Already unpaired — idempotent success.
    return { success: true }
  }

  const coupleRef = db.collection('couples').doc(coupleId)

  // Security review Batch 2: do the membership check, member-clearing, and couple-doc
  // delete in one transaction so two partners leaving concurrently can't clobber state.
  // Critically, only clear a member's coupleId if it STILL points at this couple — a
  // stale concurrent call must never wipe a coupleId set by a fresh re-pair.
  const result = await db.runTransaction(async (tx) => {
    const coupleSnap = await tx.get(coupleRef)
    if (!coupleSnap.exists) {
      const callerRef = db.collection('users').doc(callerId)
      const callerSnap = await tx.get(callerRef)
      if (callerSnap.data()?.coupleId === coupleId) {
        tx.update(callerRef, { coupleId: null })
      }
      return { membership: true }
    }

    const userIds = (coupleSnap.data()?.userIds ?? []) as string[]
    if (!userIds.includes(callerId)) {
      return { membership: false }
    }

    // Reads must precede writes in a transaction: snapshot every member first.
    const memberSnaps = await Promise.all(
      userIds.map((uid) => tx.get(db.collection('users').doc(uid)))
    )
    memberSnaps.forEach((snap, i) => {
      if (snap.data()?.coupleId === coupleId) {
        tx.update(db.collection('users').doc(userIds[i]), { coupleId: null })
      }
    })
    tx.delete(coupleRef)
    return { membership: true }
  })

  if (!result.membership) {
    throw new functions.https.HttpsError('permission-denied', 'Not a member of this couple.')
  }

  // Couple doc is deleted in the transaction; sweep any subcollections left behind.
  // Idempotent if a concurrent caller already removed them.
  await db.recursiveDelete(coupleRef)

  console.log(`[leaveCoupleCallable] user ${callerId} left couple ${coupleId}`)
  return { success: true }
})
