// Point the DB at a closed port BEFORE requiring anything that builds the pool,
// so any stray query fails fast instead of hanging the runner. These tests stub
// every model method the controller touches, so the DB is never actually hit.
process.env.DB_HOST = '127.0.0.1'
process.env.DB_PORT = '59999'

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

const ctrl = require('../src/router/v1/admin/usersShard.controller')
const users = require('../src/model/users/users.model')
const shardLinks = require('../src/model/shardLinks/shardLinks.model')
const shardState = require('../src/model/shardState/shardState.model')
const shardEvents = require('../src/model/shardEvents/shardEvents.model')
const { salesForAccounts } = require('../src/utils/shardSales')
const db = require('../src/utils/db')

after(() => db.close())

function mockRes() {
  return {
    statusCode: 200,
    body: null,
    status(c) {
      this.statusCode = c
      return this
    },
    json(b) {
      this.body = b
      return this
    },
  }
}

// Save/restore the originals so each test's monkeypatches don't leak.
const originals = {
  getById: users.getById,
  listForUser: shardLinks.listForUser,
  listHousesForAccounts: shardState.listHousesForAccounts,
  listOnlineForAccounts: shardState.listOnlineForAccounts,
  eventsList: shardEvents.list,
}
afterEach(() => {
  users.getById = originals.getById
  shardLinks.listForUser = originals.listForUser
  shardState.listHousesForAccounts = originals.listHousesForAccounts
  shardState.listOnlineForAccounts = originals.listOnlineForAccounts
  shardEvents.list = originals.eventsList
})

// ── salesForAccounts util ──────────────────────────────────────────────────
test('salesForAccounts returns [] for an empty account set without hitting the log', async () => {
  let called = false
  shardEvents.list = async () => {
    called = true
    return []
  }
  assert.deepEqual(await salesForAccounts([]), [])
  assert.equal(called, false)
})

test('salesForAccounts keeps only sales owned by the given accounts, newest 50', async () => {
  const events = []
  // 60 sales owned by "mine", plus some owned by "other".
  for (let i = 0; i < 60; i++) {
    events.push({ t: i, payload: { ownerAcct: 'mine', itemType: 'sword', amount: 1, price: 10, commission: 1 } })
  }
  events.push({ t: 999, payload: { ownerAcct: 'other', itemType: 'shield', amount: 1, price: 5 } })
  shardEvents.list = async () => events

  const rows = await salesForAccounts(['mine'])
  assert.equal(rows.length, 50) // capped
  assert.ok(rows.every((r) => r.ownerAcct === 'mine')) // never leaks "other"
  assert.deepEqual(Object.keys(rows[0]).sort(), ['amount', 'commission', 'itemType', 'ownerAcct', 'price', 't'])
})

// ── Controller: unknown user → 404 ─────────────────────────────────────────
for (const handler of ['getUser', 'listAccounts', 'getSales', 'getHouses', 'getOnline']) {
  test(`${handler} returns 404 when the user does not exist`, async () => {
    users.getById = async () => null
    const res = mockRes()
    await ctrl[handler]({ params: { id: '404' } }, res)
    assert.equal(res.statusCode, 404)
  })
}

// ── Controller: scoping to the user's accounts ─────────────────────────────
test('listAccounts returns the user’s linked accounts', async () => {
  users.getById = async () => ({ id: 7, username: 'bob', role: 'player' })
  shardLinks.listForUser = async (id) => {
    assert.equal(id, 7)
    return [{ account: 'acctA' }, { account: 'acctB' }]
  }
  const res = mockRes()
  await ctrl.listAccounts({ params: { id: '7' } }, res)
  assert.equal(res.statusCode, 200)
  assert.deepEqual(res.body, [{ account: 'acctA' }, { account: 'acctB' }])
})

test('getHouses passes exactly the user’s accounts to the model', async () => {
  users.getById = async () => ({ id: 7 })
  shardLinks.listForUser = async () => [{ account: 'acctA' }, { account: 'acctB' }]
  let received = null
  shardState.listHousesForAccounts = async (accounts) => {
    received = accounts
    return [{ serial: '0x1', isIdoc: true }]
  }
  const res = mockRes()
  await ctrl.getHouses({ params: { id: '7' } }, res)
  assert.deepEqual(received, ['acctA', 'acctB'])
  assert.deepEqual(res.body, [{ serial: '0x1', isIdoc: true }])
})

test('getOnline passes exactly the user’s accounts to the model', async () => {
  users.getById = async () => ({ id: 7 })
  shardLinks.listForUser = async () => [{ account: 'acctA' }]
  let received = null
  shardState.listOnlineForAccounts = async (accounts) => {
    received = accounts
    return [{ serial: '0x2', name: 'Zoe' }]
  }
  const res = mockRes()
  await ctrl.getOnline({ params: { id: '7' } }, res)
  assert.deepEqual(received, ['acctA'])
  assert.deepEqual(res.body, [{ serial: '0x2', name: 'Zoe' }])
})

test('a user with no linked accounts yields empty sales/houses/online', async () => {
  users.getById = async () => ({ id: 7 })
  shardLinks.listForUser = async () => []
  shardState.listHousesForAccounts = async (a) => (a.length ? [{}] : [])
  shardState.listOnlineForAccounts = async (a) => (a.length ? [{}] : [])
  shardEvents.list = async () => [{ payload: { ownerAcct: 'someoneElse' } }]

  const sales = mockRes()
  const houses = mockRes()
  const online = mockRes()
  await ctrl.getSales({ params: { id: '7' } }, sales)
  await ctrl.getHouses({ params: { id: '7' } }, houses)
  await ctrl.getOnline({ params: { id: '7' } }, online)

  assert.deepEqual(sales.body, [])
  assert.deepEqual(houses.body, [])
  assert.deepEqual(online.body, [])
})

test('getUser returns the sanitized user row', async () => {
  users.getById = async () => ({ id: 7, username: 'bob', role: 'player', status: 'active' })
  const res = mockRes()
  await ctrl.getUser({ params: { id: '7' } }, res)
  assert.equal(res.statusCode, 200)
  assert.equal(res.body.username, 'bob')
})
