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

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

// Protocol 3.0 vendor.listing / vendor.listing.remove routing. Same shape as
// shardIngest.points.test.js: stubbed deps, asserting where the dispatcher sends
// the frame and whether it is appended to the event log.
function makeDeps() {
  const calls = { upserts: [], removes: [], appended: [], broadcast: [] }
  const noop = async () => {}
  return {
    calls,
    shardEvents: { append: async (row) => { calls.appended.push(row); return true } },
    shardState: {
      // Present so any stray routing is a harmless no-op rather than a crash.
      clearOnline: noop, upsertOnline: noop, setOffline: noop, upsertHouse: noop,
      addEconomySample: noop, setRuleset: noop, upsertPointsBoard: noop,
    },
    shardMarket: {
      upsertVendor: async (ev) => { calls.upserts.push(ev) },
      removeVendor: async (serial) => { calls.removes.push(serial) },
    },
    shardLinks: { removeByAccount: noop },
    uoLinkConfig: { recordStatus: noop },
    broadcast: (ev) => { calls.broadcast.push(ev) },
    pushDispatch: async () => {},
    log: { warn() {}, info() {}, error() {} },
  }
}

const FRAME = {
  kind: 'vendor.listing',
  t: 1000,
  serial: '0x40001234',
  shopName: "Darrow's Bargains",
  ownerSerial: '0x1A2B',
  ownerName: 'Darrow',
  location: { map: 'Trammel', x: 1421, y: 1699, z: 0, region: 'Britain', house: "Darrow's Villa" },
  count: 2,
  total: 2,
  truncated: false,
  items: [
    { serial: '0x40012ABC', itemId: 3922, hue: 0, amount: 1, price: 25000, name: null, cliloc: 1023721 },
    { serial: '0x40012ABD', itemId: 7026, hue: 1157, amount: 3, price: 500, name: 'a shard sigil', cliloc: 1041243 },
  ],
}

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

test('vendor.listing routes to the market model with the whole frame', async () => {
  const deps = makeDeps()
  await shardIngest.ingest(FRAME, deps)
  assert.equal(deps.calls.upserts.length, 1)
  const stored = deps.calls.upserts[0]
  assert.equal(stored.serial, '0x40001234')
  assert.equal(stored.location.region, 'Britain')
  assert.equal(stored.items.length, 2)
})

test('vendor.listing.remove routes to removeVendor with the serial', async () => {
  const deps = makeDeps()
  await shardIngest.ingest({ kind: 'vendor.listing.remove', t: 2000, serial: '0x40001234' }, deps)
  assert.deepEqual(deps.calls.removes, ['0x40001234'])
  assert.equal(deps.calls.upserts.length, 0)
})

// The market IS the state. One frame carries up to 250 listings and the sweep
// re-emits a shop on any price change, so logging would turn shard_events into a
// price history nobody reads — the strongest case of the three v3 kinds.
test('neither market kind is appended to the event log', async () => {
  const deps = makeDeps()
  const a = await shardIngest.ingest(FRAME, deps)
  const b = await shardIngest.ingest({ kind: 'vendor.listing.remove', serial: '0x40001234' }, deps)
  assert.equal(a.logged, false)
  assert.equal(b.logged, false)
  assert.equal(deps.calls.appended.length, 0)
  assert.equal(shardIngest.LOGGED_KINDS.has('vendor.listing'), false)
  assert.equal(shardIngest.LOGGED_KINDS.has('vendor.listing.remove'), false)
})

// Broadcast is unconditional at this layer — whether it actually reaches anyone
// is shardBroadcast's call, and the market feature ships with its stream off.
test('vendor.listing is handed to the broadcaster', async () => {
  const deps = makeDeps()
  await shardIngest.ingest(FRAME, deps)
  assert.equal(deps.calls.broadcast.length, 1)
  assert.equal(deps.calls.broadcast[0].kind, 'vendor.listing')
})

test('a backfilled vendor.listing still stores but does not broadcast', async () => {
  const deps = makeDeps()
  await shardIngest.ingest(FRAME, { ...deps, fromBackfill: true })
  assert.equal(deps.calls.upserts.length, 1)
  assert.equal(deps.calls.broadcast.length, 0)
})

// The reconnect backfill replays the whole index through this path, so a single
// bad vendor must not abort it.
test('an upsertVendor failure does not throw or stop the broadcast', async () => {
  const deps = makeDeps()
  deps.shardMarket.upsertVendor = async () => { throw new Error('db down') }
  const r = await shardIngest.ingest(FRAME, deps)
  assert.equal(r.logged, false)
  assert.equal(deps.calls.broadcast.length, 1)
})

// Each vendor is its own row; the frame is authoritative for that vendor only.
test('two vendors are stored independently', async () => {
  const deps = makeDeps()
  await shardIngest.ingest(FRAME, deps)
  await shardIngest.ingest({ ...FRAME, serial: '0x40009999', shopName: 'Second Shop' }, deps)
  assert.deepEqual(deps.calls.upserts.map((v) => v.serial), ['0x40001234', '0x40009999'])
})
