// Point the DB pool at a dead port before it's built; the db modules are
// monkeypatched below, and pool.close() at the end lets the process exit cleanly.
process.env.DB_HOST = '127.0.0.1'
process.env.DB_PORT = '59999'

const { test, beforeEach, afterEach, after } = require('node:test')
const assert = require('node:assert/strict')
const pool = require('../src/utils/db')
after(() => pool.close())

// Unit-test the moderation MODEL's orchestration (the pure helpers are covered
// separately in moderation.test.js). What the model adds on top:
//   - summary() merges five independent window feeds (mod actions + member
//     joins/leaves + invite joins + filter/spam hits) into one windows object;
//   - userSummary() zero-fills per-type counts but total_actions sums EVERY row,
//     including action types not in the fixed count set;
//   - a missing/failing bot config degrades to "nothing automated", never throws.
const moderationDb = require('../src/model/moderation/moderation.db')
const botConfigDb = require('../src/model/botConfig/botConfig.db')
const moderation = require('../src/model/moderation/moderation.model')

const saved = {}
function patch(mod, name, fn) {
  saved[`${mod === moderationDb ? 'm' : 'b'}:${name}`] = mod[name]
  mod[name] = fn
}
afterEach(() => {
  for (const key of Object.keys(saved)) {
    const [tag, name] = key.split(':')
    const mod = tag === 'm' ? moderationDb : botConfigDb
    mod[name] = saved[key]
    delete saved[key]
  }
})

// ── summary() merges every feed into the three windows ──────────────────
test('summary folds mod-action counts and all four event feeds into each window', async () => {
  patch(moderationDb, 'countsByWindow', async () => [{ action_type: 'ban', d1: 1, d7: 2, d30: 3 }])
  patch(moderationDb, 'memberCountsByWindow', async () => [
    { event_type: 'join', d1: 5, d7: 6, d30: 7 },
    { event_type: 'leave', d1: 1, d7: 1, d30: 1 },
  ])
  patch(moderationDb, 'inviteJoinCountsByWindow', async () => ({ d1: 2, d7: 2, d30: 2 }))
  patch(moderationDb, 'tableCountsByWindow', async (table) =>
    table === 'filter_hits' ? { d1: 9, d7: 9, d30: 9 } : { d1: 4, d7: 4, d30: 4 },
  )

  const { windows } = await moderation.summary()
  assert.equal(windows['24h'].ban, 1)
  assert.equal(windows['7d'].ban, 2)
  assert.equal(windows['24h'].joins, 5)
  assert.equal(windows['24h'].leaves, 1)
  assert.equal(windows['24h'].invite_joins, 2)
  assert.equal(windows['24h'].filter_hits, 9)
  assert.equal(windows['24h'].spam_hits, 4)
  assert.equal(windows['30d'].joins, 7)
})

test('summary zero-fills a feed that returned no rows for a window', async () => {
  patch(moderationDb, 'countsByWindow', async () => [])
  patch(moderationDb, 'memberCountsByWindow', async () => []) // no join/leave rows
  patch(moderationDb, 'inviteJoinCountsByWindow', async () => null)
  patch(moderationDb, 'tableCountsByWindow', async () => null)

  const { windows } = await moderation.summary()
  assert.equal(windows['24h'].joins, 0)
  assert.equal(windows['7d'].filter_hits, 0)
  assert.equal(windows['30d'].ban, 0)
})

// ── userSummary() count/total semantics ─────────────────────────────────
test('userSummary zero-fills known types but total_actions sums every row', async () => {
  patch(moderationDb, 'userCounts', async () => [
    { action_type: 'ban', c: '2' },
    { action_type: 'warn', c: 3 },
    { action_type: 'note', c: 5 }, // NOT in zeroCounts — excluded from counts, still in total
  ])
  patch(moderationDb, 'latestTag', async () => 'Griefer#1')
  patch(moderationDb, 'linkedAccount', async () => ({ id: 9, username: 'griefer' }))

  const out = await moderation.userSummary('123')
  assert.equal(out.counts.ban, 2)
  assert.equal(out.counts.warn, 3)
  assert.equal(out.counts.kick, 0) // zero-filled
  assert.equal(out.counts.note, undefined) // unknown type not surfaced as a count
  assert.equal(out.total_actions, 10) // 2 + 3 + 5 — total includes the unknown type
  assert.equal(out.tag, 'Griefer#1')
  assert.deepEqual(out.linked_account, { id: 9, username: 'griefer' })
})

// ── automated-action annotation depends on bot config, which may be absent ──
test('recent flags actions taken by the bot application id as automated', async () => {
  patch(botConfigDb, 'get', async () => ({ application_id: '999' }))
  patch(moderationDb, 'recentActions', async () => [
    { id: 1, staff_user_id: '999' }, // the bot
    { id: 2, staff_user_id: '42' }, // a human mod
  ])
  const rows = await moderation.recent({})
  assert.equal(rows[0].is_automated, true)
  assert.equal(rows[1].is_automated, false)
})

test('recent degrades to nothing-automated when bot config lookup throws', async () => {
  patch(botConfigDb, 'get', async () => {
    throw new Error('bot config table missing')
  })
  patch(moderationDb, 'recentActions', async () => [{ id: 1, staff_user_id: '999' }])
  const rows = await moderation.recent({})
  assert.equal(rows[0].is_automated, false) // appId resolved to null, not a crash
})
