const cloud = require('wx-server-sdk')

cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()
const _ = db.command

// Only people within this radius are "nearby". Online = location reported recently.
const RADIUS_METERS = 5000
const ONLINE_WINDOW_MS = 5 * 60 * 1000
// How many visible locations to scan in memory. No geo index is required this way
// (geoNear would need a manually-created index in the cloud console); fine for an
// early-stage user base. Switch to geoNear on `locations.geo` if this grows large.
const MAX_SCAN = 500
const MAX_RESULTS = 50

// In WeChat 云开发, querying a collection that was never created throws
// ("collection not exists"). Treat that as an empty result so an unused
// collection (e.g. follows/matches before anyone follows/likes) can't break
// the whole nearby query.
function isMissingCollection(e) {
  if (!e) return false
  const msg = String(e.errMsg || e.message || '')
  return e.errCode === -502005 || /not exist/i.test(msg) || /CollectionNotExists/i.test(msg)
}

async function safeGet(query) {
  try {
    return await query.get()
  } catch (e) {
    if (isMissingCollection(e)) return { data: [] }
    throw e
  }
}

async function fetchByChunks(collection, field, values) {
  const out = []
  for (let i = 0; i < values.length; i += 100) {
    const chunk = values.slice(i, i + 100)
    const res = await safeGet(db.collection(collection).where({ [field]: _.in(chunk) }).limit(100))
    out.push(...res.data)
  }
  return out
}

// Great-circle distance in metres between two {latitude, longitude} points.
function distanceMeters(a, b) {
  if (!a || !b || typeof a.latitude !== 'number' || typeof b.latitude !== 'number') return Infinity
  const R = 6371000
  const dLat = ((b.latitude - a.latitude) * Math.PI) / 180
  const dLon = ((b.longitude - a.longitude) * Math.PI) / 180
  const s =
    Math.sin(dLat / 2) ** 2 +
    Math.cos((a.latitude * Math.PI) / 180) *
      Math.cos((b.latitude * Math.PI) / 180) *
      Math.sin(dLon / 2) ** 2
  return R * 2 * Math.atan2(Math.sqrt(s), Math.sqrt(1 - s))
}

function distanceText(meters) {
  if (!isFinite(meters)) return null
  const m = Math.round(meters)
  if (m < 1000) return `${m}m`
  return `${(m / 1000).toFixed(1)}km`
}

exports.main = async event => {
  const { OPENID } = cloud.getWXContext()
  const filter = event.filters?.type || 'all'
  const userLoc = event.location || null

  // No usable origin → nothing to anchor "nearby" to.
  if (!userLoc || typeof userLoc.latitude !== 'number' || typeof userLoc.longitude !== 'number') {
    return { list: [] }
  }

  // Identify the caller and their follow graph (for friend/following badges).
  let meId = ''
  let followingSet = new Set()
  let followerSet = new Set()
  if (OPENID) {
    const meRes = await safeGet(db.collection('users').where({ openid: OPENID }).limit(1))
    const me = meRes.data[0]
    if (me) {
      meId = me._id
      const [followingRes, followerRes] = await Promise.all([
        safeGet(db.collection('follows').where({ followerId: meId }).limit(500)),
        safeGet(db.collection('follows').where({ followeeId: meId }).limit(500))
      ])
      followingSet = new Set(followingRes.data.map(f => f.followeeId))
      followerSet = new Set(followerRes.data.map(f => f.followerId))
    }
  }

  // 1) Scan visible locations (paginated) and keep those inside the radius.
  const near = []
  let scanned = 0
  for (let skip = 0; skip < MAX_SCAN; skip += 100) {
    const res = await safeGet(db.collection('locations').where({ visible: true }).skip(skip).limit(100))
    scanned += res.data.length
    if (!res.data.length) break
    for (const rec of res.data) {
      if (!rec.openid || !rec.location) continue
      const dist = distanceMeters(userLoc, rec.location)
      if (dist <= RADIUS_METERS) near.push({ rec, dist })
    }
    if (res.data.length < 100) break
  }
  near.sort((a, b) => a.dist - b.dist)
  const top = near.slice(0, MAX_RESULTS)
  console.log('[nearbyPets] origin=', userLoc, 'visibleScanned=', scanned, 'inRadius=', near.length, 'nearestKm=', near[0] ? (near[0].dist / 1000).toFixed(2) : 'n/a')
  if (!top.length) return { list: [] }

  const distByOpenid = new Map()
  const recByOpenid = new Map()
  const openids = []
  top.forEach(({ rec, dist }) => {
    if (recByOpenid.has(rec.openid)) return
    distByOpenid.set(rec.openid, dist)
    recByOpenid.set(rec.openid, rec)
    openids.push(rec.openid)
  })

  // 2) Resolve those openids to user records.
  const users = await fetchByChunks('users', 'openid', openids)
  const userByOpenid = new Map(users.map(u => [u.openid, u]))
  const openidByUserId = new Map(users.map(u => [u._id, u.openid]))
  const ownerIds = users.map(u => u._id)
  if (!ownerIds.length) return { list: [] }

  // 3) Their pets (optionally narrowed by species).
  let pets = await fetchByChunks('pets', 'ownerId', ownerIds)
  if (filter === 'dog' || filter === 'cat') pets = pets.filter(p => p.species === filter)

  // 4) Which of these pets the caller already liked.
  const likedSet = new Set()
  const petIds = pets.map(p => p._id).filter(Boolean)
  if (OPENID && petIds.length) {
    const liked = await fetchByChunks('matches', 'petId', petIds).then(rows =>
      rows.filter(m => m.fromOpenid === OPENID)
    )
    liked.forEach(m => likedSet.add(m.petId))
  }

  const now = Date.now()
  const list = pets
    .map(pet => {
      const ownerOpenid = openidByUserId.get(pet.ownerId)
      const rec = ownerOpenid ? recByOpenid.get(ownerOpenid) : null
      const coord = rec && rec.location ? rec.location : null
      const dist = ownerOpenid != null && distByOpenid.has(ownerOpenid) ? distByOpenid.get(ownerOpenid) : Infinity
      const owner = userByOpenid.get(ownerOpenid)
      const isFollowing = followingSet.has(pet.ownerId)
      const isFollowedBy = followerSet.has(pet.ownerId)
      return {
        ...pet,
        ownerName: owner?.nickname || '',
        isMine: Boolean(meId && pet.ownerId === meId),
        isFollowing,
        isFriend: isFollowing && isFollowedBy,
        likedByMe: likedSet.has(pet._id),
        latitude: coord ? coord.latitude : undefined,
        longitude: coord ? coord.longitude : undefined,
        distanceText: distanceText(dist),
        online: rec ? now - (rec.updatedAt || 0) < ONLINE_WINDOW_MS : false,
        _dist: dist
      }
    })
    .sort((a, b) => a._dist - b._dist)

  list.forEach(p => delete p._dist)

  const finalList = filter === 'online' ? list.filter(p => p.online) : list
  console.log('[nearbyPets] nearbyUsers=', openids.length, 'matchedUsers=', users.length, 'pets=', pets.length, 'returned=', finalList.length)
  return { list: finalList }
}
