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

const DAY_MS = 24 * 60 * 60 * 1000

const CHALLENGE_TITLES: Record<string, { title: string; durationDays: number }> = {
  gratitude_week: { title: 'Gratitude Week', durationDays: 7 },
  appreciation_notes: { title: 'Appreciation Notes', durationDays: 7 },
  quality_time: { title: 'Quality Time', durationDays: 7 },
  deep_conversations: { title: 'Deep Conversations', durationDays: 7 },
}

type NotificationType = 'memory_capsule_unlocked' | 'challenge_day_ready'

interface QueuedNotification {
  userId: string
  type: NotificationType
  title: string
  body: string
  data: Record<string, string>
}

export const unlockDueMemoryCapsules = functions.pubsub
  .schedule('every 1 hours')
  .onRun(async () => {
    const db = admin.firestore()
    const messaging = admin.messaging()
    const now = Date.now()

    const snapshot = await db
      .collectionGroup('capsules')
      .where('status', '==', 'sealed')
      .where('unlockAt', '<=', now)
      .limit(100)
      .get()

    const notifications: QueuedNotification[] = []

    for (const capsuleDoc of snapshot.docs) {
      const capsuleRef = capsuleDoc.ref
      const coupleRef = capsuleRef.parent.parent
      if (!coupleRef) continue

      const capsuleNotifications = await db.runTransaction(async (tx) => {
        const freshCapsule = await tx.get(capsuleRef)
        const capsule = freshCapsule.data() ?? {}
        if (capsule.status !== 'sealed' || Number(capsule.unlockAt ?? 0) > now) {
          return [] as QueuedNotification[]
        }

        const coupleDoc = await tx.get(coupleRef)
        const userIds = (coupleDoc.data()?.userIds ?? []) as string[]
        if (userIds.length === 0) return [] as QueuedNotification[]

        tx.update(capsuleRef, {
          status: 'unlocked',
          unlockedAt: now,
          unlockNotifiedAt: admin.firestore.FieldValue.serverTimestamp(),
        })

        const capsuleId = capsuleRef.id
        const coupleId = coupleRef.id
        const title = typeof capsule.title === 'string' && capsule.title.trim().length > 0
          ? capsule.title.trim()
          : 'A memory capsule'

        return userIds.map((userId) => ({
          userId,
          type: 'memory_capsule_unlocked' as const,
          title: 'Your memory capsule opened',
          body: `${title} is ready to read together.`,
          data: { couple_id: coupleId, capsule_id: capsuleId },
        }))
      })

      notifications.push(...capsuleNotifications)
    }

    await Promise.all(notifications.map((notification) => sendNotification(db, messaging, notification)))
    console.log(`[unlockDueMemoryCapsules] unlocked ${snapshot.size}; notified ${notifications.length}`)
  })

export const sendChallengeDayReminders = functions.pubsub
  .schedule('every 24 hours')
  .onRun(async () => {
    const db = admin.firestore()
    const messaging = admin.messaging()
    const now = Date.now()

    const snapshot = await db
      .collectionGroup('challenges')
      .where('status', '==', 'active')
      .limit(100)
      .get()

    const notifications: QueuedNotification[] = []

    for (const challengeDoc of snapshot.docs) {
      const challengeRef = challengeDoc.ref
      const coupleRef = challengeRef.parent.parent
      if (!coupleRef) continue

      const challengeNotifications = await db.runTransaction(async (tx) => {
        const freshChallenge = await tx.get(challengeRef)
        const challenge = freshChallenge.data() ?? {}
        if (challenge.status !== 'active') return [] as QueuedNotification[]

        const startedAt = Number(challenge.startedAt ?? 0)
        if (startedAt <= 0 || startedAt > now) return [] as QueuedNotification[]

        const challengeId = typeof challenge.challengeId === 'string'
          ? challenge.challengeId
          : challengeRef.id
        const catalogEntry = CHALLENGE_TITLES[challengeId] ?? {
          title: 'Connection Challenge',
          durationDays: 7,
        }
        const day = Math.floor((now - startedAt) / DAY_MS) + 1
        if (day < 1 || day > catalogEntry.durationDays) return [] as QueuedNotification[]

        const coupleDoc = await tx.get(coupleRef)
        const userIds = (coupleDoc.data()?.userIds ?? []) as string[]
        if (userIds.length === 0) return [] as QueuedNotification[]

        const completions = (challenge.completions ?? {}) as Record<string, number[]>
        const reminderSent = (challenge.challengeReminderSent ?? {}) as Record<string, boolean>
        const dueUserIds = userIds.filter((userId) => {
          const completedDays = completions[userId] ?? []
          const alreadyCompleted = completedDays.map(Number).includes(day)
          const alreadySent = reminderSent[reminderKey(userId, day)] === true
          return !alreadyCompleted && !alreadySent
        })

        if (dueUserIds.length === 0) return [] as QueuedNotification[]

        const updates: Record<string, unknown> = {
          lastChallengeReminderAt: admin.firestore.FieldValue.serverTimestamp(),
        }
        dueUserIds.forEach((userId) => {
          updates[`challengeReminderSent.${reminderKey(userId, day)}`] = true
        })
        tx.update(challengeRef, updates)

        const coupleId = coupleRef.id
        return dueUserIds.map((userId) => ({
          userId,
          type: 'challenge_day_ready' as const,
          title: `Day ${day} is ready`,
          body: `${catalogEntry.title}: today's connection prompt is waiting.`,
          data: { couple_id: coupleId, challenge_id: challengeId, day: String(day) },
        }))
      })

      notifications.push(...challengeNotifications)
    }

    await Promise.all(notifications.map((notification) => sendNotification(db, messaging, notification)))
    console.log(`[sendChallengeDayReminders] scanned ${snapshot.size}; notified ${notifications.length}`)
  })

function reminderKey(userId: string, day: number): string {
  return `${userId.replace(/[^\w-]/g, '_')}_${day}`
}

async function sendNotification(
  db: admin.firestore.Firestore,
  messaging: admin.messaging.Messaging,
  notification: QueuedNotification
): Promise<void> {
  await db
    .collection('users')
    .doc(notification.userId)
    .collection('notification_queue')
    .add({
      type: notification.type,
      title: notification.title,
      body: notification.body,
      read: false,
      createdAt: admin.firestore.FieldValue.serverTimestamp(),
    })

  const tokens = await getUserTokens(db, notification.userId)
  if (tokens.length === 0) {
    console.log(`[sendNotification] no FCM tokens for ${notification.userId}`)
    return
  }

  const message: admin.messaging.Message = {
    token: tokens[0],
    notification: {
      title: notification.title,
      body: notification.body,
    },
    data: {
      type: notification.type,
      ...notification.data,
    },
  }

  const sendResults = await Promise.allSettled(
    tokens.map((token) => messaging.send({ ...message, token }))
  )

  const failures: string[] = []
  sendResults.forEach((result, index) => {
    if (result.status === 'rejected') {
      failures.push(`${tokens[index]}: ${String(result.reason)}`)
    }
  })

  if (failures.length > 0) {
    console.error(`[sendNotification] some notifications failed:`, failures)
  }
}

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

  const tokenSnapshot = await db
    .collection('users')
    .doc(userId)
    .collection('fcmTokens')
    .get()

  tokenSnapshot.docs.forEach((doc) => {
    const token = doc.data()?.token
    if (typeof token === 'string' && token.length > 0 && !tokens.includes(token)) {
      tokens.push(token)
    }
  })

  return tokens
}
