const { test, beforeEach } = require('node:test')
const assert = require('node:assert/strict')

const shardIngest = require('../src/utils/shardIngest')

// Build a set of stub deps that record the champ/page/state calls the dispatcher
// makes, plus a spy shardEvents.append and broadcast. Only the methods the tested
// kinds touch need to be real; the rest are no-op async so ingest() never throws.
function makeDeps() {
  const calls = { champUpsert: [], champRemove: [], pageUpsert: [], pageRemove: [], appended: [], broadcast: [] }
  const noop = async () => {}
  return {
    calls,
    shardEvents: { append: async (row) => { calls.appended.push(row); return true } },
    shardState: {
      upsertChamp: async (ev) => { calls.champUpsert.push(ev) },
      removeChamp: async (serial) => { calls.champRemove.push(serial) },
      upsertPage: async (ev) => { calls.pageUpsert.push(ev) },
      removePage: async (id) => { calls.pageRemove.push(id) },
      // Unused by these kinds but present so any stray routing is a no-op.
      clearOnline: noop, upsertOnline: noop, setOffline: noop, upsertHouse: noop, addEconomySample: noop,
    },
    uoLinkConfig: { recordStatus: noop },
    broadcast: (ev) => { calls.broadcast.push(ev) },
    log: { warn() {}, info() {}, error() {} },
  }
}

beforeEach(() => shardIngest.reset())

test('champ.update routes to shardState.upsertChamp and is not written to the event log', async () => {
  const deps = makeDeps()
  const ev = { kind: 'champ.update', serial: '0x1', category: 'champion', name: 'Abyss', status: 'active', t: 1 }
  const r = await shardIngest.ingest(ev, deps)
  assert.equal(deps.calls.champUpsert.length, 1)
  assert.equal(deps.calls.champUpsert[0].serial, '0x1')
  assert.equal(r.logged, false) // champ.* is state-only, not appended to shard_events
  assert.equal(deps.calls.appended.length, 0)
  assert.equal(deps.calls.broadcast.length, 1) // still broadcast live
})

test('champ.remove routes to shardState.removeChamp', async () => {
  const deps = makeDeps()
  await shardIngest.ingest({ kind: 'champ.remove', serial: '0x2', t: 2 }, deps)
  assert.deepEqual(deps.calls.champRemove, ['0x2'])
})

test('page.new and page.updated upsert the page; page.closed removes it', async () => {
  const deps = makeDeps()
  await shardIngest.ingest({ kind: 'page.new', pageId: '0x24C', type: 'Bug', sender: { name: 'Al' }, t: 3 }, deps)
  await shardIngest.ingest({ kind: 'page.updated', pageId: '0x24C', handled: true, t: 4 }, deps)
  await shardIngest.ingest({ kind: 'page.closed', pageId: '0x24C', t: 5 }, deps)
  assert.equal(deps.calls.pageUpsert.length, 2)
  assert.deepEqual(deps.calls.pageRemove, ['0x24C'])
})

test('admin.audit is appended to the event log (moderation history)', async () => {
  const deps = makeDeps()
  const r = await shardIngest.ingest({ kind: 'admin.audit', action: 'ban', actor: 'web:jane', target: 'griefer', t: 6 }, deps)
  assert.equal(r.logged, true)
  assert.equal(deps.calls.appended.length, 1)
  assert.equal(deps.calls.appended[0].kind, 'admin.audit')
})

test('champ.remove without a serial is a harmless no-op', async () => {
  const deps = makeDeps()
  await shardIngest.ingest({ kind: 'champ.remove', t: 7 }, deps)
  assert.deepEqual(deps.calls.champRemove, [undefined])
})
