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

const CST_OFFSET_HOURS = -6

/**
 * Scheduled function that assigns one daily question per couple every day.
 *
 * Schedule: 6:00 PM CST (America/Chicago) == 23:00 UTC.
 * Document path: couples/{coupleId}/daily_question/{date}
 *   - questionId: string
 *   - date: string (YYYY-MM-DD)
 *   - assignedAt: Timestamp
 *   - expiresAt: Timestamp (next day at 6 PM CST)
 *
 * Admin SDK bypasses Firestore rules; the `daily_question` doc is server-write
 * only (`allow write: if false` in rules).
 */
export const assignDailyQuestion = functions.pubsub
  .schedule('0 23 * * *')
  .timeZone('America/Chicago')
  .onRun(async () => {
    const db = admin.firestore()
    const today = cstDateString()
    const nextDay = nextCstDateString()

    const questionId = await pickRandomQuestionId()
    if (!questionId) {
      console.error('[assignDailyQuestion] no active questions available')
      return
    }

    const couplesSnap = await db.collection('couples').get()
    const assignedAt = admin.firestore.FieldValue.serverTimestamp()
    const expiresAt = timestampAt6PmCst(nextDay)

    const writes = couplesSnap.docs.map(async (coupleDoc) => {
      const coupleId = coupleDoc.id
      const docRef = db
        .collection('couples')
        .doc(coupleId)
        .collection('daily_question')
        .doc(today)

      try {
        await docRef.create({
          questionId,
          date: today,
          assignedAt,
          expiresAt,
        })
      } catch (err: any) {
        if (err?.code === 6 || err?.message?.includes('ALREADY_EXISTS')) {
          // Already assigned for today — idempotent.
          return
        }
        console.error(`[assignDailyQuestion] failed for ${coupleId}:`, err)
      }
    })

    await Promise.all(writes)
    console.log(
      `[assignDailyQuestion] assigned ${questionId} to ${couplesSnap.size} couples for ${today}`
    )
  })

/**
 * Callable function for immediate daily question assignment.
 *
 * Useful when a couple is newly created and should not wait for the next
 * scheduled run, or for manual admin/testing triggers.
 *
 * Body: { coupleId: string, date?: string }
 */
export const assignDailyQuestionCallable = functions.https.onCall(async (data: any, context) => {
  const callerId = context.auth?.uid
  if (!callerId) {
    throw new functions.https.HttpsError('unauthenticated', 'Caller must be authenticated.')
  }
  if (!context.app) {
    throw new functions.https.HttpsError('failed-precondition', 'App Check verification required.')
  }

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

  const db = admin.firestore()
  const coupleDoc = await db.collection('couples').doc(coupleId).get()
  if (!coupleDoc.exists) {
    throw new functions.https.HttpsError('not-found', 'Couple not found.')
  }

  // Caller must be a member of the couple.
  const userIds = (coupleDoc.data()?.userIds ?? []) as string[]
  if (!userIds.includes(callerId)) {
    throw new functions.https.HttpsError('permission-denied', 'Caller is not a couple member.')
  }

  // Security review Batch 2: constrain the client-supplied date. Only today's CST date
  // may be assigned on demand — this blocks creating arbitrary past/future daily_question
  // docs and, combined with create()'s ALREADY_EXISTS guard, caps it to one per day
  // (effective rate limit; repeat calls return already-exists).
  const today = cstDateString()
  const date = data?.date && typeof data.date === 'string' ? data.date : today
  if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
    throw new functions.https.HttpsError('invalid-argument', 'date must be YYYY-MM-DD.')
  }
  if (date !== today) {
    throw new functions.https.HttpsError('invalid-argument', 'Daily question can only be assigned for today.')
  }
  const questionId = await pickRandomQuestionId()
  if (!questionId) {
    throw new functions.https.HttpsError('internal', 'No active questions available.')
  }

  const nextDay = nextCstDateString(date)
  const docRef = db
    .collection('couples')
    .doc(coupleId)
    .collection('daily_question')
    .doc(date)

  try {
    await docRef.create({
      questionId,
      date,
      assignedAt: admin.firestore.FieldValue.serverTimestamp(),
      expiresAt: timestampAt6PmCst(nextDay),
    })
  } catch (err: any) {
    if (err?.code === 6 || err?.message?.includes('ALREADY_EXISTS')) {
      throw new functions.https.HttpsError('already-exists', `Daily question already assigned for ${date}.`)
    }
    throw new functions.https.HttpsError('internal', 'Failed to assign daily question.')
  }

  return { success: true, coupleId, date, questionId }
})

/**
 * Picks a random active free question from the Firestore `questions` pool.
 *
 * The pool is expected to be a top-level collection with documents:
 *   - id: string
 *   - active: boolean
 *   - isPremium: boolean
 *
 * If no Firestore pool exists yet, falls back to a deterministic placeholder
 * so the app still functions during rollout. In production, keep the local
 * Room database and Firestore pool in sync through the existing seed flow.
 */
async function pickRandomQuestionId(): Promise<string | null> {
  const db = admin.firestore()
  const snapshot = await db
    .collection('questions')
    .where('active', '==', true)
    .where('isPremium', '==', false)
    .get()

  if (snapshot.empty) {
    // Rollout fallback: keep the feature working until the questions pool is seeded.
    return 'q_default_daily'
  }

  const docs = snapshot.docs
  const chosen = docs[Math.floor(Math.random() * docs.length)]
  return chosen.id
}

/**
 * Returns today's date as YYYY-MM-DD in America/Chicago time.
 */
function cstDateString(): string {
  return formatCstDate(new Date())
}

/**
 * Returns tomorrow's date as YYYY-MM-DD relative to America/Chicago time.
 */
function nextCstDateString(from?: string): string {
  if (from) {
    const d = parseCstDate(from)
    d.setUTCDate(d.getUTCDate() + 1)
    return formatCstDate(d)
  }
  const d = new Date()
  const cst = applyCstOffset(d)
  cst.setUTCDate(cst.getUTCDate() + 1)
  return formatCstDate(cst)
}

function applyCstOffset(d: Date): Date {
  const utcMs = d.getTime()
  const cstMs = utcMs + CST_OFFSET_HOURS * 60 * 60 * 1000
  return new Date(cstMs)
}

function formatCstDate(d: Date): string {
  const cst = applyCstOffset(d)
  const y = cst.getUTCFullYear()
  const m = String(cst.getUTCMonth() + 1).padStart(2, '0')
  const day = String(cst.getUTCDate()).padStart(2, '0')
  return `${y}-${m}-${day}`
}

function parseCstDate(dateStr: string): Date {
  const [y, m, day] = dateStr.split('-').map(Number)
  // Build a UTC timestamp that represents midnight CST, then reverse the offset.
  const cstMidnightMs = Date.UTC(y, m - 1, day, 0, 0, 0, 0)
  return new Date(cstMidnightMs - CST_OFFSET_HOURS * 60 * 60 * 1000)
}

function timestampAt6PmCst(dateStr: string): admin.firestore.Timestamp {
  const d = parseCstDate(dateStr)
  const utcMs = d.getTime()
  // 6:00 PM CST == 18:00 CST == 00:00 UTC next day.
  // We already have midnight CST in UTC form; add 18 hours.
  const at6pmCstMs = utcMs + 18 * 60 * 60 * 1000
  return admin.firestore.Timestamp.fromMillis(at6pmCstMs)
}
