// The route manifest is the freeze that proves the domain split (docs/website/
// API_V2_PLAN.md § Phase 2) moves no URL. CI runs `npm run routes:manifest -- --check`,
// but that only fires on a pull request — this test makes the same drift visible on
// `npm test`, and adds the two structural invariants the manifest alone can't state.
//
// Point the pool at a closed port BEFORE requiring anything: the generator loads the
// real app, which pulls in every model and builds a mariadb pool at require time. No
// query is ever run here (the Express stack is introspected, not called).
process.env.DB_HOST = '127.0.0.1'
process.env.DB_PORT = '59999'

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

const manifestTool = require('../scripts/routeManifest')
const db = require('../src/utils/db')

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

const SERVER_ROOT = path.join(__dirname, '..')
const read = (file) => fs.readFileSync(path.join(SERVER_ROOT, file), 'utf8').replace(/\r\n/g, '\n')

const collected = manifestTool.collect()

test('routes.manifest.json is in sync with the live Express stack', () => {
  const generated = manifestTool.serialize(manifestTool.buildManifest(collected))
  assert.equal(
    read('routes.manifest.json'),
    generated,
    'The URL surface changed. If that was deliberate, run `npm run routes:manifest` and ' +
      'commit the result so the change is reviewed — do not smuggle a URL change into a ' +
      '"mechanical" refactor PR.',
  )
})

test('routes.guards.json is in sync with the live Express stack', () => {
  const generated = manifestTool.serialize(manifestTool.buildGuards(collected))
  assert.equal(read('routes.guards.json'), generated, 'Run `npm run routes:manifest`.')
})

test('the manifest only inventories API surface, never static mounts', () => {
  // The SPA catch-all, /uploads and /brand are filesystem-conditional, so including
  // them would make the manifest depend on whether the client had been built.
  for (const route of collected.public) {
    assert.ok(
      route.path.startsWith('/api/') || route.path.startsWith('/.well-known/'),
      `unexpected non-API path in the manifest: ${route.method} ${route.path}`,
    )
  }
})

test('every /admin, /player and /settings route still sits behind the shared auth gate', () => {
  // Router-level `use()` gates do not appear in an individual route's own stack, so a
  // capability router extracted from admin.routes.js without re-applying the gate would
  // silently publish authenticated endpoints. Names are only a hint — `requireRole(...)`
  // returns an anonymous arrow and cannot be seen here — but a *missing* requireAuth is
  // unambiguous.
  const AUTHENTICATED_GROUPS = ['/api/v1/admin/', '/api/v1/player/', '/api/v1/settings/']
  const gated = collected.public.filter((r) => AUTHENTICATED_GROUPS.some((p) => r.path.startsWith(p)))
  // A floor, not a count: it exists so a filter that silently matches nothing
  // fails loudly rather than passing vacuously. It was >100 before Phase 3 moved
  // 70 UO routes into module-uo, and there is no value in tracking core's exact
  // total here — the per-route assertion below is what actually guards the gate.
  assert.ok(gated.length > 50, 'expected the gated surface to be found')
  for (const route of gated) {
    assert.ok(
      route.gates.includes('requireAuth'),
      `${route.method} ${route.path} is missing requireAuth`,
    )
  }
})
