import { PlatformError, platformErrorPayload } from './errors.mjs';

const METHODS = new Set(['get', 'post', 'put', 'patch', 'delete']);
const ROUTE_ID = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+$/;

export class RouteRegistry {
  #routes = new Map();
  #policyEngine;
  #moduleRegistry;

  constructor({ policyEngine, moduleRegistry = null }) {
    this.#policyEngine = policyEngine;
    this.#moduleRegistry = moduleRegistry;
  }

  register(app, definition) {
    const method = String(definition.method || '').toLowerCase();
    const id = String(definition.id || '');
    const path = String(definition.path || '');
    if (!ROUTE_ID.test(id)) throw new PlatformError(`Ungültige Route-ID: ${id}`, { code: 'ROUTE_ID_INVALID' });
    if (!METHODS.has(method)) throw new PlatformError(`Ungültige HTTP-Methode für ${id}.`, { code: 'ROUTE_METHOD_INVALID' });
    if (!path.startsWith('/')) throw new PlatformError(`Ungültiger Routenpfad für ${id}.`, { code: 'ROUTE_PATH_INVALID' });
    if (!definition.policy) throw new PlatformError(`Route ${id} benötigt eine explizite Policy.`, { code: 'ROUTE_POLICY_REQUIRED' });
    if (!definition.owner) throw new PlatformError(`Route ${id} benötigt einen Modulbesitzer.`, { code: 'ROUTE_OWNER_REQUIRED' });
    if (typeof definition.handler !== 'function') throw new PlatformError(`Route ${id} benötigt einen Handler.`, { code: 'ROUTE_HANDLER_REQUIRED' });
    const key = `${method.toUpperCase()} ${path}`;
    if (this.#routes.has(key)) throw new PlatformError(`Route bereits registriert: ${key}`, { code: 'ROUTE_DUPLICATE' });

    const contract = Object.freeze({
      id,
      method: method.toUpperCase(),
      path,
      owner: String(definition.owner),
      policy: String(definition.policy),
      audit: String(definition.audit || ''),
      input: definition.input ? 'validated' : 'none',
      rateLimit: String(definition.rateLimit || 'default'),
      csrf: definition.csrf !== false,
      stability: String(definition.stability || 'internal'),
    });
    this.#routes.set(key, contract);

    app[method](path, ...(definition.middleware || []), async (req, res, next) => {
      try {
        if (this.#moduleRegistry && !this.#moduleRegistry.isEnabled(contract.owner)) {
          throw new PlatformError('Dieses Modul ist deaktiviert.', {
            code: 'MODULE_DISABLED', status: 503, expose: true,
            details: { module: contract.owner },
          });
        }
        const decision = await this.#policyEngine.evaluate(contract.policy, { req, user: req.user, route: contract });
        if (!decision.allowed) {
          throw new PlatformError('Keine Berechtigung für diese Aktion.', {
            code: 'POLICY_DENIED', status: 403, expose: true,
            details: { policy: contract.policy, reason: decision.reason },
          });
        }
        if (typeof definition.input === 'function') req.validatedBody = definition.input(req.body);
        await definition.handler(req, res, { route: contract, decision });
      } catch (error) {
        if (res.headersSent) return next(error);
        const status = Number.isInteger(error?.status) ? error.status : 500;
        res.status(status).json(platformErrorPayload(error, req.requestId));
      }
    });
    return contract;
  }

  list() {
    return [...this.#routes.values()].map(route => ({ ...route }));
  }
}
