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

/**
 * HTTPS callable that mediates invite acceptance.
 *
 * Issue #9 fix: clients are no longer allowed to read invites directly, because
 * the 6-character document ID was enumerable. The invite is looked up by code
 * server-side, validated, and accepted atomically here.
 *
 * Request body: { code: string, recoveryPhrase?: string }
 * - code: the 6-character invite code the partner shared.
 * - recoveryPhrase: required if the invite was created with a wrapped couple key.
 *
 * Response: { coupleId: string }
 *
 * Operations (all via Admin SDK, so Firestore rules are bypassed):
 *  1. Verify caller is authenticated and not already paired.
 *  2. Look up the invite document by code.
 *  3. Validate status == 'pending' and not expired.
 *  4. Prevent self-acceptance.
 *  5. Create the couple document with the wrapped couple key from the invite.
 *  6. Update both user documents with the new coupleId.
 *  7. Mark the invite as accepted.
 */
export const acceptInviteCallable = functions.https.onCall(async (data: any, context) => {
  const callerId = context.auth?.uid
  if (!callerId) {
    throw new functions.https.HttpsError('unauthenticated', 'Must be signed in.')
  }

  const code = data?.code
  if (!code || typeof code !== 'string') {
    throw new functions.https.HttpsError('invalid-argument', 'code is required.')
  }

  const recoveryPhrase = data?.recoveryPhrase
  if (recoveryPhrase !== undefined && typeof recoveryPhrase !== 'string') {
    throw new functions.https.HttpsError('invalid-argument', 'recoveryPhrase must be a string.')
  }

  const db = admin.firestore()

  // Caller must not already be paired.
  const callerDoc = await db.collection('users').doc(callerId).get()
  if (callerDoc.exists && callerDoc.data()?.coupleId != null) {
    throw new functions.https.HttpsError('failed-precondition', 'Caller is already paired.')
  }

  const inviteRef = db.collection('invites').doc(code)
  const inviteDoc = await inviteRef.get()

  if (!inviteDoc.exists) {
    throw new functions.https.HttpsError('not-found', 'Invite not found.')
  }

  const invite = inviteDoc.data() ?? {}
  const inviterUserId = invite.inviterUserId as string | undefined
  const status = invite.status as string | undefined
  const expiresAt = invite.expiresAt as admin.firestore.Timestamp | undefined
  const wrappedCoupleKey = invite.wrappedCoupleKey as string | undefined
  const kdfSalt = invite.kdfSalt as string | undefined
  const kdfParams = invite.kdfParams as string | undefined

  if (status !== 'pending') {
    throw new functions.https.HttpsError('failed-precondition', 'Invite has already been used.')
  }

  const now = admin.firestore.Timestamp.now()
  if (expiresAt != null && expiresAt.toMillis() <= now.toMillis()) {
    throw new functions.https.HttpsError('failed-precondition', 'Invite has expired.')
  }

  if (!inviterUserId) {
    throw new functions.https.HttpsError('failed-precondition', 'Invite is missing inviterUserId.')
  }

  if (inviterUserId === callerId) {
    throw new functions.https.HttpsError('permission-denied', 'Cannot accept your own invite.')
  }

  // Recovery phrase is required whenever the invite carries a wrapped key.
  if (wrappedCoupleKey != null && (!recoveryPhrase || recoveryPhrase.length === 0)) {
    throw new functions.https.HttpsError('invalid-argument', 'recoveryPhrase is required for this invite.')
  }

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

  const batch = db.batch()

  batch.set(coupleRef, {
    id: coupleId,
    userIds: [inviterUserId, callerId],
    inviteCode: code,
    createdAt: admin.firestore.FieldValue.serverTimestamp(),
    streakCount: 0,
    encryptionVersion: 2,
    wrappedCoupleKey: wrappedCoupleKey ?? null,
    kdfSalt: kdfSalt ?? null,
    kdfParams: kdfParams ?? null,
  })

  batch.update(db.collection('users').doc(inviterUserId), { coupleId })
  batch.update(db.collection('users').doc(callerId), { coupleId })

  batch.update(inviteRef, {
    status: 'accepted',
    acceptedByUserId: callerId,
    acceptedAt: admin.firestore.FieldValue.serverTimestamp(),
    coupleId,
  })

  await batch.commit()

  console.log(`[acceptInviteCallable] ${callerId} accepted invite ${code}; created couple ${coupleId}`)

  return {
    coupleId,
    inviterUserId,
    wrappedCoupleKey: wrappedCoupleKey ?? null,
    kdfSalt: kdfSalt ?? null,
    kdfParams: kdfParams ?? null,
  }
})
