/**
 * Joi-based request body validation middleware.
 *
 * Usage:
 *   const { validateBody, schemas } = require('../utilities/validate');
 *
 *   router.post('/schedule', validateBody(schemas.backupScheduleCreate), handler);
 *
 * The middleware validates req.body against the provided Joi schema.
 * On success it replaces req.body with the validated/stripped value.
 * On failure it throws a ValidationError (caught by asyncHandler → 400).
 *
 * Schemas for destructive routes live in the `schemas` export so they
 * can be unit-tested without spinning up Express.
 */

const Joi = require('joi');
const { ValidationError } = require('./errors');

/**
 * Factory: returns an Express middleware that validates req.body.
 * @param {Joi.Schema} schema
 * @param {{ stripUnknown?: boolean, abortEarly?: boolean }} [opts]
 */
function validateBody(schema, opts = {}) {
  return (req, _res, next) => {
    const { error, value } = schema.validate(req.body, {
      stripUnknown: opts.stripUnknown ?? true,
      abortEarly: opts.abortEarly ?? false,
      convert: true,
    });
    if (error) {
      const msg = error.details.map(d => d.message).join('; ');
      throw new ValidationError(msg);
    }
    req.body = value;
    next();
  };
}

// ─── Reusable schema fragments ───────────────────────────────

const scheduleSchema = Joi.alternatives().try(
  Joi.string().valid('hourly', 'daily', 'weekly', 'monthly'),
  Joi.string().pattern(/^\d+[mh]?$/, 'custom-interval (e.g. "30m", "6h")'),
).optional();

const ipOrCidr = Joi.alternatives().try(
  // Authoritative single-IP validation (Joi's built-in strict IPv4/IPv6 check)
  Joi.string().ip({ version: ['ipv4', 'ipv6'] }),
  // Authoritative CIDR validation (Joi's built-in strict CIDR check rejects malformed
  // addresses like "::::/64" that a permissive hex/colon regex would otherwise accept)
  Joi.string().ip({ version: ['ipv4', 'ipv6'], cidr: 'required' }),
);

// ─── Schemas for destructive routes ───────────────────────────

const schemas = {
  /** POST /backups/config — update backup configuration */
  backupConfigUpdate: Joi.object({
    backups: Joi.object().pattern(
      Joi.string().max(100), // appId key
      Joi.object({ // per-app backup config
        enabled: Joi.boolean().optional(),
        schedule: scheduleSchema,
        retention: Joi.object({
          keep: Joi.number().integer().min(1).max(365).optional(),
          olderThan: [Joi.string().max(20).optional(), Joi.number().optional()],
        }).optional(),
        destination: Joi.string().valid('local', 'dropbox', 'webdav', 'sftp').optional(),
        destinationPath: Joi.string().max(512).optional(),
        maxStorageBytes: [Joi.number().integer().min(0).optional(), Joi.string().max(20).optional()],
        runImmediately: Joi.boolean().optional(),
        include: Joi.array().items(Joi.string().max(50)).optional(),
        destinations: Joi.array().items(Joi.object({
          type: Joi.string().valid('local', 'dropbox', 'webdav', 'sftp').optional(),
          path: Joi.string().max(512).optional(),
        }).unknown(false)).optional(),
      }).unknown(false)
    ).optional(),
    defaultRetention: Joi.object({
      keep: Joi.number().integer().min(1).max(365).optional(),
      olderThan: [Joi.string().max(20).optional(), Joi.number().optional()],
    }).optional(),
  }),

  /** POST /backups/schedule — create or update a scheduled backup */
  backupScheduleCreate: Joi.object({
    appId: Joi.string().min(1).max(100).required(),
    enabled: Joi.boolean().optional(),
    schedule: scheduleSchema,
    retention: Joi.object({
      keep: Joi.number().integer().min(1).max(365).optional(),
      olderThan: [Joi.string().max(20).optional(), Joi.number().optional()],
    }).optional(),
    runImmediately: Joi.boolean().optional(),
    destination: Joi.string().valid('local', 'dropbox', 'webdav', 'sftp').optional(),
    destinationPath: Joi.string().max(512).optional(),
    maxStorageBytes: [Joi.number().integer().min(0).optional(), Joi.string().max(20).optional()],
    // Legacy schedule route fields
    name: Joi.string().max(100).optional(),
  }),

  /** POST /backups/restore/:backupId — passes through to backupManager.restoreBackup */
  backupRestore: Joi.object({
    encryptionKey: Joi.string().max(512).optional(),
    restartContainers: Joi.boolean().optional(),
    // restoreBackup reads options from body; allow known control flags only
    services: Joi.boolean().optional(),
    config: Joi.boolean().optional(),
    credentials: Joi.boolean().optional(),
    volumes: Joi.boolean().optional(),
  }),

  /** POST /backups/restore-file/:filename */
  backupRestoreFile: Joi.object({
    encryptionKey: Joi.string().max(512).optional(),
    restartContainers: Joi.boolean().optional(),
  }),

  /** POST /apps/deploy */
  appDeploy: Joi.object({
    appId: Joi.string().min(1).max(100).required(),
    config: Joi.object({
      subdomain: Joi.string().min(1).max(63).required(),
      port: Joi.number().integer().min(1).max(65535).optional(),
      ip: Joi.string().max(45).optional(),
      useExisting: Joi.boolean().optional(),
      existingContainerId: Joi.string().max(200).optional(),
      existingPort: Joi.number().integer().min(1).max(65535).optional(),
      createDns: Joi.boolean().optional(),
      tailscaleOnly: Joi.boolean().optional(),
      allowedIPs: Joi.array().items(ipOrCidr).optional(),
      customVolumes: Joi.array().items(Joi.object({
        hostPath: Joi.string().max(500).required(),
        containerPath: Joi.string().max(500).required(),
      }).unknown(false)).optional(),
      mediaPath: Joi.string().max(500).optional(),
      // Template-specific config fields preserved from the live frontend
      sslType: Joi.string().valid('self-signed', 'tailscale', 'letsencrypt', 'none').optional(),
      dnsType: Joi.string().valid('private', 'public', 'none').optional(),
      plexClaimToken: Joi.string().max(500).optional(),
      resources: Joi.object({
        memory: Joi.number().min(32).max(65536).optional(),
        cpus: Joi.number().min(0.1).max(64).optional(),
      }).optional(),
    }).unknown(true), // Forward-compat: templates may accept additional fields
  }),

  /** POST /apps/:appId/restore — empty body, reject any input fields */
  appRestore: Joi.any().custom((value, helpers) => {
    if (value !== undefined && value !== null && (typeof value !== 'object' || Object.keys(value).length > 0)) {
      return helpers.error('object.empty');
    }
    return {};
  }, 'empty-body-guard').messages({
    'object.empty': '"body" must be empty (this endpoint accepts no input)',
  }),

  /** POST /apps/:appId/revert/:filename */
  appRevert: Joi.object({
    encryptionKey: Joi.string().max(512).optional(),
    restartContainers: Joi.boolean().optional(),
  }),

  /** POST /assets/upload */
  assetUpload: Joi.object({
    filename: Joi.string().min(1).max(255).required(),
    data: Joi.string().min(1).max(10 * 1024 * 1024).required(), // 10MB base64 cap
  }),

  /** POST /assets/logo */
  logoUpload: Joi.object({
    data: Joi.string().min(1).max(10 * 1024 * 1024).optional(),
    dataDark: Joi.string().min(1).max(10 * 1024 * 1024).optional(),
    dataLight: Joi.string().min(1).max(10 * 1024 * 1024).optional(),
    position: Joi.string().valid('left', 'center', 'right').optional(),
    dashboardTitle: Joi.string().max(50).allow('').optional(),
  }).min(1),
};

module.exports = { validateBody, schemas };
