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

/**
 * Cloud Function: submitOutcomeCallable
 *
 * Manual test flow:
 *  1. Pair two test users via acceptInviteCallable.
 *  2. From one member, call submitOutcomeCallable with:
 *       { dayKey: "day_0", scores: { connection: 7, communication: 6, intimacy: 8, happiness: 9 } }
 *  3. Verify Firestore doc couples/{coupleId}/outcomes/day_0 exists with the payload.
 *  4. Verify users/{callerUid}/outcomes/day_0 mirror doc exists.
 *  5. Submit from the partner; verify answeredBy contains both UIDs.
 *  6. Try invalid inputs (day_30 without day_0, scores out of range, non-member) and confirm rejection.
 *
 * Auth required; caller must be a member of the couple. Writes are performed by the
 * Admin SDK so they bypass Firestore rules — direct client writes to the outcomes
 * subcollection are denied in firestore.rules.
 *
 * Schema for couples/{coupleId}/outcomes/{dayKey}:
 *   dayKey       "day_0" | "day_30" | "day_60" | "day_90"
 *   baseline     { connection, communication, intimacy, happiness } (day_0 only)
 *   scores       { connection, communication, intimacy, happiness } (day_30/60/90 only)
 *   delta        { connection, communication, intimacy, happiness } (day_30/60/90 only)
 *   answeredBy   [uid1, uid2] — deduplicated, appends on each call
 *   createdAt    server timestamp
 *   updatedAt    server timestamp
 *
 * Per-user mirror at users/{uid}/outcomes/{dayKey}:
 *   dayKey, baseline/scores, delta, createdAt, updatedAt
 * This supports "your progress across all relationships" later.
 */

export type OutcomeDayKey = 'day_0' | 'day_30' | 'day_60' | 'day_90'
export type ScoreKey = 'connection' | 'communication' | 'intimacy' | 'happiness'

const DAY_KEYS: OutcomeDayKey[] = ['day_0', 'day_30', 'day_60', 'day_90']
const SCORE_KEYS: ScoreKey[] = ['connection', 'communication', 'intimacy', 'happiness']
const MIN_SCORE = 1
const MAX_SCORE = 10

function isValidDayKey(value: unknown): value is OutcomeDayKey {
  return typeof value === 'string' && (DAY_KEYS as string[]).includes(value)
}

function isValidScoreMap(value: unknown): value is Record<ScoreKey, number> {
  if (typeof value !== 'object' || value === null || Array.isArray(value)) return false
  const map = value as Record<string, unknown>
  for (const key of SCORE_KEYS) {
    const num = map[key]
    if (typeof num !== 'number' || Number.isNaN(num)) return false
    if (num < MIN_SCORE || num > MAX_SCORE) return false
  }
  return true
}

export const submitOutcomeCallable = functions.https.onCall(async (data: any, 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 coupleId = data?.coupleId
  if (typeof coupleId !== 'string' || coupleId.length === 0) {
    throw new functions.https.HttpsError('invalid-argument', 'coupleId is required.')
  }

  const dayKey = data?.dayKey
  if (!isValidDayKey(dayKey)) {
    throw new functions.https.HttpsError(
      'invalid-argument',
      `dayKey must be one of: ${DAY_KEYS.join(', ')}.`
    )
  }

  const scores = data?.scores
  if (!isValidScoreMap(scores)) {
    throw new functions.https.HttpsError(
      'invalid-argument',
      `scores must contain ${SCORE_KEYS.join(', ')} with values ${MIN_SCORE}-${MAX_SCORE}.`
    )
  }

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

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

  const now = admin.firestore.Timestamp.now()
  const outcomeRef = coupleRef.collection('outcomes').doc(dayKey)

  const result = await db.runTransaction(async (tx) => {
    const existing = await tx.get(outcomeRef)
    const existingData = existing.exists ? (existing.data() ?? {}) : {}

    // If this is a follow-up (non-baseline), a baseline must exist to compute delta.
    if (dayKey !== 'day_0') {
      const baselineRef = coupleRef.collection('outcomes').doc('day_0')
      const baselineSnap = await tx.get(baselineRef)
      if (!baselineSnap.exists) {
        throw new functions.https.HttpsError(
          'failed-precondition',
          'Baseline (day_0) outcome must be submitted before follow-up outcomes.'
        )
      }
    }

    const answeredBy = (existingData.answeredBy ?? []) as string[]
    if (!answeredBy.includes(callerId)) {
      answeredBy.push(callerId)
    }

    const payload: Record<string, any> = {
      dayKey,
      answeredBy,
      updatedAt: now,
      createdAt: (existingData.createdAt as admin.firestore.Timestamp | undefined) ?? now,
    }

    if (dayKey === 'day_0') {
      payload.baseline = scores
    } else {
      payload.scores = scores
    }

    // Delta is recalculated every time so repeated submissions stay consistent.
    if (dayKey !== 'day_0') {
      const baselineRef = coupleRef.collection('outcomes').doc('day_0')
      const baselineSnap = await tx.get(baselineRef)
      if (baselineSnap.exists) {
        const baseline = (baselineSnap.data()?.baseline ?? {}) as Record<string, number>
        const delta: Record<string, number> = {}
        for (const key of SCORE_KEYS) {
          delta[key] = (scores[key] ?? 0) - (baseline[key] ?? 0)
        }
        payload.delta = delta
      }
    }

    tx.set(outcomeRef, payload, { merge: true })

    // Per-user mirror for cross-relationship stats.
    const userOutcomeRef = db.collection('users').doc(callerId).collection('outcomes').doc(dayKey)
    tx.set(userOutcomeRef, {
      dayKey,
      coupleId,
      ...(dayKey === 'day_0' ? { baseline: scores } : { scores, delta: payload.delta }),
      answeredBy: [callerId],
      createdAt: (existingData.createdAt as admin.firestore.Timestamp | undefined) ?? now,
      updatedAt: now,
    }, { merge: true })

    return { dayKey, answeredBy }
  })

  console.log(`[submitOutcomeCallable] ${callerId} submitted ${dayKey} for couple ${coupleId}`)

  return { success: true, ...result }
})
