// Admin · Modules — install, enable, disable, uninstall, purge and restart.
//
// Mounted at /api/v1/admin/modules by admin/index.js, which has already applied
// `noindex, isLoggedIn, staffOnly`. Every route here re-gates to `admin`: an
// editor or moderator has no business installing code into the server process,
// and the group gate alone would let them.
//
// Route order matters in one place. `/restart` and `/sources` are declared
// BEFORE the `/:id/...` routes, because express matches in declaration order and
// a module whose id was `restart` would otherwise shadow — or be shadowed by —
// the literal path. The id pattern below makes that unreachable in practice; the
// ordering makes it unreachable by construction.

const express = require('express')
const { body, param, query } = require('express-validator')

const controller = require('./modules.controller')
const { requireRole } = require('../../../utils/auth')
const validate = require('../../../middleware/validate')

const modulesRouter = express.Router()
const adminOnly = requireRole('admin')

// The loader's own id rule (MODULE_API.md §2.1). Applied at the edge so a
// traversal-shaped id never reaches a path join, even though install.js checks
// it again — this one produces a 400 with a readable message, that one is the
// guarantee.
const ID = /^[a-z][a-z0-9-]{1,31}$/

modulesRouter.get(
  '/',
  // #swagger.tags = ['Admin · Modules']
  // #swagger.summary = 'List installed modules, their live state, and the source allowlist'
  // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }]
  /* #swagger.responses[200] = { description: 'Modules and the install source allowlist', content: { "application/json": { schema: { type: "object", properties: { modules: { type: "array", items: { type: "object", additionalProperties: true } }, sourceHosts: { type: "array", items: { type: "string" } } } } } } } */
  adminOnly,
  controller.list,
)

modulesRouter.post(
  '/',
  // #swagger.tags = ['Admin · Modules']
  // #swagger.summary = 'Install or upgrade a module from a release install-manifest URL'
  // #swagger.description = 'Downloads the artifact the manifest names, verifies its sha256, inspects the archive in full and unpacks it onto the modules volume. The module mounts on the next restart.'
  // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }]
  /* #swagger.requestBody = { required: true, content: { "application/json": { schema: { type: "object", required: ["url"], properties: { url: { type: "string", description: "https URL of the release install manifest, on an allowed host" } } } } } } */
  /* #swagger.responses[201] = { description: 'Installed — restart to mount it', content: { "application/json": { schema: { type: "object", additionalProperties: true } } } } */
  /* #swagger.responses[400] = { description: 'The URL, the manifest, the hash or the archive was refused', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
  /* #swagger.responses[502] = { description: 'The source host could not be reached or answered badly', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
  adminOnly,
  body('url').isString().trim().isLength({ min: 1, max: 2048 }),
  validate,
  controller.create,
)

modulesRouter.put(
  '/sources',
  // #swagger.tags = ['Admin · Modules']
  // #swagger.summary = 'Replace the allowlist of hosts modules may be installed from'
  // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }]
  /* #swagger.requestBody = { required: true, content: { "application/json": { schema: { type: "object", required: ["hosts"], properties: { hosts: { type: "string", description: "Comma- or space-separated hostnames. An empty list forbids all installs." } } } } } } */
  /* #swagger.responses[200] = { description: 'The new allowlist', content: { "application/json": { schema: { type: "object", properties: { sourceHosts: { type: "array", items: { type: "string" } } } } } } } */
  /* #swagger.responses[400] = { description: 'One of the entries is not a hostname', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
  adminOnly,
  body('hosts').isString().isLength({ max: 2048 }),
  validate,
  controller.setSources,
)

modulesRouter.post(
  '/restart',
  // #swagger.tags = ['Admin · Modules']
  // #swagger.summary = 'Restart the server process so module changes take effect'
  // #swagger.description = 'Runs the same graceful shutdown a SIGTERM does. The process is brought back by the supervisor, which the shipped docker-compose.yml provides; a bare `npm start` will not come back.'
  // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }]
  /* #swagger.responses[202] = { description: 'Shutting down', content: { "application/json": { schema: { type: "object", properties: { restarting: { type: "boolean" } } } } } } */
  adminOnly,
  controller.restart,
)

modulesRouter.post(
  '/:id/enable',
  // #swagger.tags = ['Admin · Modules']
  // #swagger.summary = 'Enable a module (takes effect on the next restart)'
  // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }]
  // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'string' }, description: 'Module id.' }
  /* #swagger.responses[200] = { description: 'Enabled — restart to start it', content: { "application/json": { schema: { type: "object", additionalProperties: true } } } } */
  /* #swagger.responses[404] = { description: 'No such module', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
  adminOnly,
  param('id').matches(ID),
  validate,
  controller.enable,
)

modulesRouter.post(
  '/:id/disable',
  // #swagger.tags = ['Admin · Modules']
  // #swagger.summary = 'Stop a module now — runs its onShutdown, then its routes answer 404'
  // #swagger.description = 'The only module action that takes effect without a restart. Re-enabling needs one, because there is no onBoot re-dispatch.'
  // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }]
  // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'string' }, description: 'Module id.' }
  /* #swagger.responses[200] = { description: 'Disabled', content: { "application/json": { schema: { type: "object", additionalProperties: true } } } } */
  /* #swagger.responses[404] = { description: 'No such module', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
  adminOnly,
  param('id').matches(ID),
  validate,
  controller.disable,
)

modulesRouter.post(
  '/:id/purge',
  // #swagger.tags = ['Admin · Modules']
  // #swagger.summary = "Run a disabled module’s purge.sql, dropping its tables and data"
  // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }]
  // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'string' }, description: 'Module id.' }
  /* #swagger.responses[200] = { description: 'Purged', content: { "application/json": { schema: { type: "object", properties: { id: { type: "string" }, purged: { type: "integer" } } } } } } */
  /* #swagger.responses[400] = { description: 'The module ships no purge.sql', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
  /* #swagger.responses[409] = { description: 'The module must be disabled first', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
  adminOnly,
  param('id').matches(ID),
  validate,
  controller.purge,
)

modulesRouter.delete(
  '/:id',
  // #swagger.tags = ['Admin · Modules']
  // #swagger.summary = 'Uninstall a module, optionally deleting its data too'
  // #swagger.description = "Removes the module directory and leaves its row disabled. With purge=true it also runs purge.sql first — which is the only moment it can, since purge.sql lives inside the directory being deleted."
  // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }]
  // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'string' }, description: 'Module id.' }
  // #swagger.parameters['purge'] = { in: 'query', required: false, schema: { type: 'boolean' }, description: "Also run the module’s purge.sql and drop its row. Destructive and irreversible." }
  /* #swagger.responses[200] = { description: 'Uninstalled — restart to unmount it', content: { "application/json": { schema: { type: "object", additionalProperties: true } } } } */
  /* #swagger.responses[400] = { description: 'Purge was asked for and the module ships no purge.sql', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
  /* #swagger.responses[404] = { description: 'No such module', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
  adminOnly,
  param('id').matches(ID),
  query('purge').optional().isIn(['true', 'false', '1', '0']),
  validate,
  controller.remove,
)

module.exports = modulesRouter
