const { query } = require('../../utils/db')

// ── Online players ─────────────────────────────────────────────────────────
const ONLINE_COLS =
  'serial, name, acct, web_id, map, x, y, z, hits, hits_max, mana, mana_max, stam, stam_max, str, dex, `int`, updated_at'

// Upsert one online player. `fields` already prepared by the model (only the
// columns it wants to write); serial is required and is the primary key.
async function upsertOnline(serial, fields) {
  const cols = Object.keys(fields)
  const allCols = ['serial', ...cols]
  const insertCols = allCols.map((c) => `\`${c}\``).join(', ')
  const placeholders = allCols.map(() => '?').join(', ')
  // Never overwrite an existing column with NULL on refresh (a char.vitals frame
  // that omits acct/name shouldn't blank what mob.login set) — COALESCE keeps the
  // prior value when the incoming one is NULL.
  const updates = cols.map((c) => `\`${c}\` = COALESCE(VALUES(\`${c}\`), \`${c}\`)`).join(', ')
  await query(
    `INSERT INTO shard_online (${insertCols}) VALUES (${placeholders})
     ON DUPLICATE KEY UPDATE ${updates}`,
    [serial, ...cols.map((c) => fields[c])],
  )
}

const removeOnline = (serial) => query('DELETE FROM shard_online WHERE serial = ?', [serial])
const clearOnline = () => query('DELETE FROM shard_online')

async function countOnline() {
  const rows = await query('SELECT COUNT(*) AS n FROM shard_online')
  return rows[0] ? Number(rows[0].n) : 0
}

const listOnline = () =>
  query(`SELECT ${ONLINE_COLS} FROM shard_online ORDER BY name ASC`)

// Online players on any of the given game accounts (admin: a user's linked
// accounts). Empty list short-circuits so we never emit `IN ()`.
const listOnlineByAccounts = (accounts) =>
  accounts.length === 0
    ? Promise.resolve([])
    : query(
        `SELECT ${ONLINE_COLS} FROM shard_online
          WHERE acct IN (${accounts.map(() => '?').join(', ')})
          ORDER BY name ASC`,
        accounts,
      )

// Staff roles whose online presence is shown on the public Shard page. Players
// who link an account are NOT surfaced publicly — only staff opt into visibility
// by virtue of being staff.
const PUBLIC_ONLINE_ROLES = ['admin', 'editor', 'moderator']

// Online players whose game account is linked to a STAFF website user. Joined
// against shard_account_links (not the sidecar-supplied web_id) so a link takes
// effect immediately, regardless of whether the player has re-logged since
// linking, then through to users so only staff roles are surfaced publicly.
const listOnlineLinked = () =>
  query(
    `SELECT ${ONLINE_COLS.split(', ').map((c) => `o.${c}`).join(', ')}
       FROM shard_online o
       JOIN shard_account_links l ON l.account = o.acct
       JOIN users u ON u.id = l.user_id
      WHERE u.role IN (${PUBLIC_ONLINE_ROLES.map(() => '?').join(', ')})
      ORDER BY o.name ASC`,
    PUBLIC_ONLINE_ROLES,
  )

// ── Economy supply series ────────────────────────────────────────────────
const insertEconomy = ({ accounts, gold, t }) =>
  query('INSERT INTO shard_economy (accounts, gold, t) VALUES (?, ?, ?)', [
    accounts ?? null,
    gold ?? null,
    t,
  ])

const listEconomy = (limit) =>
  query('SELECT accounts, gold, t FROM shard_economy ORDER BY t DESC LIMIT ?', [limit])

async function latestEconomy() {
  const rows = await query('SELECT accounts, gold, t FROM shard_economy ORDER BY t DESC LIMIT 1')
  return rows[0] || null
}

// ── Houses / IDOC ────────────────────────────────────────────────────────
const HOUSE_COLS =
  'serial, stage, map, x, y, z, region, name, owner_serial, owner_acct, built_on, last_refreshed, is_idoc, updated_at'

async function upsertHouse(serial, fields) {
  const cols = Object.keys(fields)
  const allCols = ['serial', ...cols]
  const insertCols = allCols.map((c) => `\`${c}\``).join(', ')
  const placeholders = allCols.map(() => '?').join(', ')
  const updates = cols.map((c) => `\`${c}\` = VALUES(\`${c}\`)`).join(', ')
  await query(
    `INSERT INTO shard_houses (${insertCols}) VALUES (${placeholders})
     ON DUPLICATE KEY UPDATE ${updates}`,
    [serial, ...cols.map((c) => fields[c])],
  )
}

const listIdocHouses = () =>
  query(`SELECT ${HOUSE_COLS} FROM shard_houses WHERE is_idoc = 1 ORDER BY updated_at DESC`)

// Houses owned by any of the given game accounts (admin: a user's linked
// accounts). IDOC houses first, then newest-refreshed. Empty list short-circuits.
const listHousesByAccounts = (accounts) =>
  accounts.length === 0
    ? Promise.resolve([])
    : query(
        `SELECT ${HOUSE_REG_COLS} FROM shard_houses
          WHERE owner_acct IN (${accounts.map(() => '?').join(', ')})
          ORDER BY is_idoc DESC, updated_at DESC`,
        accounts,
      )

// ── House registry (Protocol 2.0 house.update / house.remove) ──────────────
// The registry columns extend HOUSE_COLS; a registry row is one we've seen via
// house.update (in_registry = 1), as opposed to a decay-only transition row.
const HOUSE_REG_COLS = `${HOUSE_COLS}, owner_name, co_owners, friends, price, decay, in_registry`

const removeHouse = (serial) => query('DELETE FROM shard_houses WHERE serial = ?', [serial])

// The full registered-house browser: every row we've seen via house.update.
const listRegistryHouses = () =>
  query(`SELECT ${HOUSE_REG_COLS} FROM shard_houses WHERE in_registry = 1 ORDER BY name ASC`)

// ── Champion spawns ────────────────────────────────────────────────────────
const CHAMP_COLS =
  'serial, category, type, name, status, active, map, x, y, z, boss_up, payload, t, updated_at'

async function upsertChamp(serial, fields) {
  const cols = Object.keys(fields)
  const allCols = ['serial', ...cols]
  const insertCols = allCols.map((c) => `\`${c}\``).join(', ')
  const placeholders = allCols.map(() => '?').join(', ')
  const updates = cols.map((c) => `\`${c}\` = VALUES(\`${c}\`)`).join(', ')
  await query(
    `INSERT INTO shard_champs (${insertCols}) VALUES (${placeholders})
     ON DUPLICATE KEY UPDATE ${updates}`,
    [serial, ...cols.map((c) => fields[c])],
  )
}

const removeChamp = (serial) => query('DELETE FROM shard_champs WHERE serial = ?', [serial])
const clearChamps = () => query('DELETE FROM shard_champs')
// Ordered by name (matches the sidecar's /champs ordering).
const listChamps = () => query(`SELECT ${CHAMP_COLS} FROM shard_champs ORDER BY name ASC`)

// ── Help-page (support) queue ──────────────────────────────────────────────
const PAGE_COLS =
  'page_id, type, sender_name, sender_acct, web_id, message, map, x, y, z, sent_ms, handled, handler, payload, updated_at'

async function upsertPage(pageId, fields) {
  const cols = Object.keys(fields)
  const allCols = ['page_id', ...cols]
  const insertCols = allCols.map((c) => `\`${c}\``).join(', ')
  const placeholders = allCols.map(() => '?').join(', ')
  const updates = cols.map((c) => `\`${c}\` = VALUES(\`${c}\`)`).join(', ')
  await query(
    `INSERT INTO shard_pages (${insertCols}) VALUES (${placeholders})
     ON DUPLICATE KEY UPDATE ${updates}`,
    [pageId, ...cols.map((c) => fields[c])],
  )
}

const removePage = (pageId) => query('DELETE FROM shard_pages WHERE page_id = ?', [pageId])
const clearPages = () => query('DELETE FROM shard_pages')
// Oldest-open first so the queue reads like a work list.
const listPages = () => query(`SELECT ${PAGE_COLS} FROM shard_pages ORDER BY sent_ms ASC`)

// ── Guild board (Protocol 2.0) ─────────────────────────────────────────────
const GUILD_COLS =
  'id, name, abbr, members, online, alliance, leader_serial, leader_name, leader_acct, leader_web_id, payload, t, updated_at'

async function upsertGuild(id, fields) {
  const cols = Object.keys(fields)
  const allCols = ['id', ...cols]
  const insertCols = allCols.map((c) => `\`${c}\``).join(', ')
  const placeholders = allCols.map(() => '?').join(', ')
  const updates = cols.map((c) => `\`${c}\` = VALUES(\`${c}\`)`).join(', ')
  await query(
    `INSERT INTO shard_guilds (${insertCols}) VALUES (${placeholders})
     ON DUPLICATE KEY UPDATE ${updates}`,
    [id, ...cols.map((c) => fields[c])],
  )
}

const removeGuild = (id) => query('DELETE FROM shard_guilds WHERE id = ?', [id])
const clearGuilds = () => query('DELETE FROM shard_guilds')
const listGuilds = () => query(`SELECT ${GUILD_COLS} FROM shard_guilds ORDER BY name ASC`)

// The guild an actor LEADS — matched on the current board (leader_serial or the
// linked leader_acct), so it reflects live state. Guild MEMBERSHIP for non-leaders
// is not modelled (the board carries only counts + leader), so we don't guess it.
const findGuildLedByActor = (serial, acct) =>
  query(
    `SELECT id, name, abbr, alliance, leader_name FROM shard_guilds
      WHERE leader_serial = ? OR (leader_acct IS NOT NULL AND leader_acct = ?)
      LIMIT 1`,
    [serial ?? null, acct ?? null],
  )

// Guilds led by any of the given game accounts (admin: a user's linked accounts).
const listGuildsLedByAccounts = (accounts) =>
  accounts.length === 0
    ? Promise.resolve([])
    : query(
        `SELECT id, name, abbr, alliance, leader_name FROM shard_guilds
          WHERE leader_acct IN (${accounts.map(() => '?').join(', ')})
          ORDER BY name ASC`,
        accounts,
      )

// ── Governor board + term history (Protocol 2.0) ───────────────────────────
const GOV_COLS =
  'city, governor_serial, governor_name, governor_acct, governor_web_id, elect_serial, elect_name, elect_acct, election_phase, candidates, auto_pick_at, payload, t, updated_at'

async function upsertGovernor(city, fields) {
  const cols = Object.keys(fields)
  const allCols = ['city', ...cols]
  const insertCols = allCols.map((c) => `\`${c}\``).join(', ')
  const placeholders = allCols.map(() => '?').join(', ')
  const updates = cols.map((c) => `\`${c}\` = VALUES(\`${c}\`)`).join(', ')
  await query(
    `INSERT INTO shard_governors (${insertCols}) VALUES (${placeholders})
     ON DUPLICATE KEY UPDATE ${updates}`,
    [city, ...cols.map((c) => fields[c])],
  )
}

const listGovernors = () => query(`SELECT ${GOV_COLS} FROM shard_governors ORDER BY city ASC`)

// Cities whose current governor is one of the given game accounts (cross-link:
// does this user hold a governorship?). Empty list short-circuits.
const listGovernorshipsByAccounts = (accounts) =>
  accounts.length === 0
    ? Promise.resolve([])
    : query(
        `SELECT ${GOV_COLS} FROM shard_governors
          WHERE governor_acct IN (${accounts.map(() => '?').join(', ')})
          ORDER BY city ASC`,
        accounts,
      )

// The single open term (ended_at IS NULL) for a city, if any.
async function currentGovernorTerm(city) {
  const rows = await query(
    'SELECT id, city, governor_serial, governor_name, governor_acct, governor_web_id, started_at, ended_at, votes FROM shard_governor_terms WHERE city = ? AND ended_at IS NULL ORDER BY started_at DESC LIMIT 1',
    [city],
  )
  return rows[0] || null
}

const closeGovernorTerm = (id, endedAt) =>
  query('UPDATE shard_governor_terms SET ended_at = ? WHERE id = ?', [endedAt, id])

const openGovernorTerm = ({ city, serial, name, acct, webId, startedAt }) =>
  query(
    `INSERT INTO shard_governor_terms
       (city, governor_serial, governor_name, governor_acct, governor_web_id, started_at)
     VALUES (?, ?, ?, ?, ?, ?)`,
    [city, serial ?? null, name ?? null, acct ?? null, webId ?? null, startedAt],
  )

const listGovernorTerms = (city, limit) =>
  query(
    'SELECT id, city, governor_serial, governor_name, governor_acct, governor_web_id, started_at, ended_at, votes FROM shard_governor_terms WHERE city = ? ORDER BY started_at DESC LIMIT ?',
    [city, limit],
  )

// ── Online-population snapshot (Protocol 2.0 presence.online) ───────────────
async function setPresence({ count, byFacet, byRegion, t }) {
  await query(
    `INSERT INTO shard_presence (id, count, by_facet, by_region, t) VALUES (1, ?, ?, ?, ?)
     ON DUPLICATE KEY UPDATE count = VALUES(count), by_facet = VALUES(by_facet),
       by_region = VALUES(by_region), t = VALUES(t)`,
    [
      Number.isFinite(count) ? count : 0,
      byFacet ? JSON.stringify(byFacet) : null,
      byRegion ? JSON.stringify(byRegion) : null,
      Number.isFinite(t) ? t : null,
    ],
  )
}

async function latestPresence() {
  const rows = await query('SELECT count, by_facet, by_region, t FROM shard_presence WHERE id = 1')
  return rows[0] || null
}

module.exports = {
  upsertOnline,
  removeOnline,
  clearOnline,
  countOnline,
  listOnline,
  listOnlineLinked,
  listOnlineByAccounts,
  insertEconomy,
  listEconomy,
  latestEconomy,
  upsertHouse,
  listIdocHouses,
  listHousesByAccounts,
  removeHouse,
  listRegistryHouses,
  upsertGuild,
  removeGuild,
  clearGuilds,
  listGuilds,
  findGuildLedByActor,
  listGuildsLedByAccounts,
  upsertGovernor,
  listGovernors,
  listGovernorshipsByAccounts,
  currentGovernorTerm,
  closeGovernorTerm,
  openGovernorTerm,
  listGovernorTerms,
  setPresence,
  latestPresence,
  upsertChamp,
  removeChamp,
  clearChamps,
  listChamps,
  upsertPage,
  removePage,
  clearPages,
  listPages,
}
