// ── Public: shard live data ────────────────────────────────────────────────
//
// Same-origin, token-free read endpoints backed by the data the WS ingest
// pipeline persists (shard_online / shard_events / shard_economy / shard_houses)
// plus a live character round-trip to the sidecar. The browser never sees the
// sidecar URL or token — every sidecar call is server-side (uoLinkClient).
//
// The stored-data endpoints are cheap DB reads. The live /char endpoint hits the
// running shard, so it is briefly cached and degrades gracefully: a 503 (shard
// restarting) surfaces as a retry-able banner rather than an error.

const shardEvents = require('../../../model/shardEvents/shardEvents.model')
const shardState = require('../../../model/shardState/shardState.model')
const uoLinkConfig = require('../../../model/uoLinkConfig/uoLinkConfig.model')
const broadcast = require('../../../utils/shardBroadcast')
const auth = require('../../../utils/auth')

const log = require('../../../utils/logger')('public-shard')

// GET /public/shard/status — connection state + online count + latest economy.
async function getStatus(req, res) {
  try {
    const config = await uoLinkConfig.getSafe()
    const [online, economy] = await Promise.all([
      shardState.onlineCount(),
      shardState.latestEconomy(),
    ])
    return res.json({
      enabled: config.enabled,
      status: config.status,
      pluginConnected: config.pluginConnected,
      lastEventAt: config.lastEventAt,
      onlineCount: online,
      economy,
    })
  } catch (err) {
    log.error('shard.getStatus', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// GET /public/shard/feed?kind=&limit= — recent notable events from the log,
// restricted to the public-safe allowlist so staff audit / cheat / link events
// (which are stored for the admin channel) can never leak to the public.
async function getFeed(req, res) {
  try {
    const { kind, limit } = req.query
    let events
    if (kind) {
      // A specific kind is only served if it is itself public-safe.
      if (!broadcast.PUBLIC_KINDS.has(kind)) return res.json([])
      events = await shardEvents.list({ kind, limit })
    } else {
      events = await shardEvents.list({ kinds: [...broadcast.PUBLIC_KINDS], limit })
    }
    return res.json(events)
  } catch (err) {
    log.error('shard.getFeed', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// GET /public/shard/economy — gold-supply series, oldest → newest.
async function getEconomy(req, res) {
  try {
    return res.json(await shardState.listEconomy(req.query.limit))
  } catch (err) {
    log.error('shard.getEconomy', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// GET /public/shard/online — players online now whose account is linked to a
// STAFF website user (admin/editor/moderator). Everyone sees that a staff member
// is online (name + serial); their in-game location (map + coordinates) is only
// included for privileged viewers (admin/moderator) so it is never exposed to
// players or the public via the network tab. Non-staff players are never listed.
function canSeeStaffLocation(req) {
  const viewer = auth.getUserFromRequest(req)
  return !!viewer && (viewer.role === 'admin' || viewer.role === 'moderator')
}

async function getOnline(req, res) {
  try {
    const rows = await shardState.listOnlineLinked()
    const showLocation = canSeeStaffLocation(req)
    return res.json(
      rows.map((r) => {
        const entry = { serial: r.serial, name: r.name }
        if (showLocation) {
          entry.map = r.map
          entry.x = r.x
          entry.y = r.y
          entry.z = r.z
        }
        return entry
      }),
    )
  } catch (err) {
    log.error('shard.getOnline', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// GET /public/shard/idoc — houses currently in danger (stage IDOC).
async function getIdoc(req, res) {
  try {
    return res.json(await shardState.listIdoc())
  } catch (err) {
    log.error('shard.getIdoc', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// GET /public/shard/champs — the current champion-spawn board (all categories).
// Served from our own store; live deltas (champ.update / champ.remove) arrive on
// the public SSE stream so the page can update in place.
async function getChamps(req, res) {
  try {
    return res.json(await shardState.listChamps())
  } catch (err) {
    log.error('shard.getChamps', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// GET /public/shard/guilds — the current guild board. Served from our store;
// live via guild.update / guild.remove / guild.join on the public SSE stream.
async function getGuilds(req, res) {
  try {
    return res.json(await shardState.listGuilds())
  } catch (err) {
    log.error('shard.getGuilds', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// GET /public/shard/governors — the current town-governor board (empty on shards
// without City Loyalty). Live via city.update on the public SSE stream.
async function getGovernors(req, res) {
  try {
    return res.json(await shardState.listGovernors())
  } catch (err) {
    log.error('shard.getGovernors', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// GET /public/shard/governors/:city/history — the term ledger for one city
// (look-back: "who were all the governors of Britain?"), newest first.
async function getGovernorHistory(req, res) {
  try {
    return res.json(await shardState.listGovernorHistory(req.params.city, req.query.limit))
  } catch (err) {
    log.error('shard.getGovernorHistory', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// GET /public/shard/presence — the online-population aggregate (count + per-facet
// + per-region). Live via presence.online on the public SSE stream.
async function getPresence(req, res) {
  try {
    return res.json(await shardState.latestPresence())
  } catch (err) {
    log.error('shard.getPresence', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// GET /public/shard/houses — PUBLIC view: only houses in danger (IDOC), and only
// their location (name + region + map/coords). Owner, price, co-owners and decay
// detail are staff-only (see admin GET /admin/shard/houses). Live via house.decay
// on the public SSE stream. This is the "where are the falling houses" board.
async function getHouses(req, res) {
  try {
    const idoc = await shardState.listIdoc()
    const publicHouses = idoc.map((h) => ({
      serial: h.serial,
      name: h.name,
      region: h.region,
      map: h.map,
      x: h.x,
      y: h.y,
      z: h.z,
      isIdoc: true,
    }))
    return res.json(publicHouses)
  } catch (err) {
    log.error('shard.getHouses', err)
    return res.status(500).json({ message: 'Internal Server Error' })
  }
}

// GET /public/shard/stream — public live-event SSE channel (safe kinds only).
function stream(req, res) {
  broadcast.subscribe(req, res, 'public')
}

module.exports = {
  getStatus,
  getFeed,
  getEconomy,
  getOnline,
  getIdoc,
  getChamps,
  getGuilds,
  getGovernors,
  getGovernorHistory,
  getPresence,
  getHouses,
  stream,
}
