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

const logic = require('../src/model/announceJobs/announceJobs.logic')

// ── buildTownCrierText ───────────────────────────────────────────────────────
test('buildTownCrierText produces title, excerpt, and URL lines', () => {
  const lines = logic.buildTownCrierText(
    { id: 7, title: 'Server Update', excerpt: 'Big things afoot.', body: null },
    { baseUrl: 'https://uom.example' },
  )
  assert.deepEqual(lines, ['Server Update', 'Big things afoot.', 'https://uom.example/site/news'])
})

test('buildTownCrierText falls back to a stripped body when excerpt is empty', () => {
  const lines = logic.buildTownCrierText(
    { id: 1, title: 'T', excerpt: '', body: '<p>Hello <b>world</b></p>' },
    { baseUrl: 'https://uom.example' },
  )
  assert.equal(lines[1], 'Hello world')
})

test('buildTownCrierText clamps each line to the sidecar per-line cap', () => {
  const longTitle = 'x'.repeat(500)
  const lines = logic.buildTownCrierText(
    { id: 1, title: longTitle, excerpt: 'y'.repeat(500), body: null },
    { baseUrl: 'https://uom.example' },
  )
  for (const line of lines) assert.ok(line.length <= logic.MAX_LINE_LEN, `line too long: ${line.length}`)
  assert.ok(lines[0].endsWith('…'))
  assert.ok(lines.length <= logic.MAX_LINES)
})

test('buildTownCrierText omits the excerpt line when there is no excerpt or body', () => {
  const lines = logic.buildTownCrierText(
    { id: 1, title: 'Only a title', excerpt: null, body: null },
    { baseUrl: 'https://uom.example' },
  )
  assert.deepEqual(lines, ['Only a title', 'https://uom.example/site/news'])
})

// ── classifyTownCrier ────────────────────────────────────────────────────────
test('classifyTownCrier: 2xx is done', () => {
  assert.equal(logic.classifyTownCrier({ ok: true, status: 200 }).outcome, 'done')
})

test('classifyTownCrier: over-cap / auth / protocol errors are terminal (no retry)', () => {
  for (const status of [400, 401, 409]) {
    assert.equal(logic.classifyTownCrier({ ok: false, status }).outcome, 'terminal', `status ${status}`)
  }
})

test('classifyTownCrier: shard-transient and network errors retry', () => {
  for (const status of [503, 504, 500, 0]) {
    assert.equal(logic.classifyTownCrier({ ok: false, status }).outcome, 'retry', `status ${status}`)
  }
})

// ── classifyDiscord ──────────────────────────────────────────────────────────
test('classifyDiscord: ok is done, every failure retries', () => {
  assert.equal(logic.classifyDiscord({ ok: true, status: 200 }).outcome, 'done')
  for (const status of [400, 503, 0]) {
    assert.equal(logic.classifyDiscord({ ok: false, status }).outcome, 'retry', `status ${status}`)
  }
})

// ── scheduleAfter (backoff) ──────────────────────────────────────────────────
test('scheduleAfter returns increasing delays then null at the attempt cap', () => {
  const d1 = logic.scheduleAfter(1)
  const d2 = logic.scheduleAfter(2)
  assert.ok(d1 > 0 && d2 > d1, 'delays should grow')
  // Exhausted once attempts reach MAX_ATTEMPTS.
  assert.equal(logic.scheduleAfter(logic.MAX_ATTEMPTS), null)
  assert.equal(logic.scheduleAfter(logic.MAX_ATTEMPTS + 3), null)
})

// ── rollupStatus ─────────────────────────────────────────────────────────────
test('rollupStatus derives the parent status from the two legs', () => {
  assert.equal(logic.rollupStatus('done', 'done'), 'done')
  assert.equal(logic.rollupStatus('failed', 'failed'), 'failed')
  assert.equal(logic.rollupStatus('pending', 'pending'), 'pending')
  // One terminal, the other not matching → partial.
  assert.equal(logic.rollupStatus('done', 'pending'), 'partial')
  assert.equal(logic.rollupStatus('pending', 'failed'), 'partial')
  assert.equal(logic.rollupStatus('done', 'failed'), 'partial')
})
