const crypto = require('crypto');

const { getServiceSecret, isAuthRequired } = require('../config/serviceCredentials');

const usedSignatureNonces = new Map();

function stableValue(value) {
  if (Array.isArray(value)) return value.map(stableValue);
  if (value && typeof value === 'object') {
    return Object.keys(value).sort().reduce((out, key) => {
      out[key] = stableValue(value[key]);
      return out;
    }, {});
  }
  return value;
}

function timingSafeEqualString(a, b) {
  const left = Buffer.from(String(a || ''));
  const right = Buffer.from(String(b || ''));

  if (left.length !== right.length) {
    return false;
  }

  return crypto.timingSafeEqual(left, right);
}

function getSignatureMaxSkewMs() {
  return Math.max(1000, Number(process.env.AISFOR_CHAIN_SIGNATURE_MAX_SKEW_MS || 5 * 60 * 1000));
}

function cleanupNonceCache(now = Date.now()) {
  const maxAge = getSignatureMaxSkewMs() * 2;
  for (const [key, seenAt] of usedSignatureNonces.entries()) {
    if (now - Number(seenAt || 0) > maxAge) usedSignatureNonces.delete(key);
  }
}

function signPayload(serviceName, body, secret) {
  const payload = JSON.stringify(stableValue({
    serviceName,
    body
  }));

  return crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
}

function signRequestPayload(serviceName, body, secret, { timestamp, nonce, method = 'POST', path = '' } = {}) {
  const payload = JSON.stringify(stableValue({
    serviceName,
    timestamp: String(timestamp || ''),
    nonce: String(nonce || ''),
    method: String(method || '').toUpperCase(),
    path: String(path || ''),
    body
  }));

  return crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
}

function createAuthError(message, details = []) {
  const error = new Error(message);
  error.status = 401;
  error.code = 'SERVICE_AUTH_FAILED';
  error.details = details;
  return error;
}

function validateSignedRequest({ req, serviceName, signature, secret, details }) {
  const timestamp = String(req.get('X-AISFOR-TIMESTAMP') || '').trim();
  const nonce = String(req.get('X-AISFOR-NONCE') || '').trim();
  const hasReplayHeaders = Boolean(timestamp || nonce);

  if (!hasReplayHeaders) {
    const legacyExpected = signPayload(serviceName, req.body || {}, secret);
    if (!timingSafeEqualString(signature, legacyExpected)) {
      details.push({ field: 'X-AISFOR-SIGNATURE', message: 'signature mismatch' });
    }
    return 'hmac-signature-legacy';
  }

  if (!timestamp) {
    details.push({ field: 'X-AISFOR-TIMESTAMP', message: 'timestamp is required for signed requests' });
  }

  if (!nonce) {
    details.push({ field: 'X-AISFOR-NONCE', message: 'nonce is required for signed requests' });
  }

  const timestampMs = Number(timestamp);
  const now = Date.now();
  if (!Number.isFinite(timestampMs)) {
    details.push({ field: 'X-AISFOR-TIMESTAMP', message: 'timestamp must be unix milliseconds' });
  } else if (Math.abs(now - timestampMs) > getSignatureMaxSkewMs()) {
    details.push({ field: 'X-AISFOR-TIMESTAMP', message: 'timestamp is outside allowed clock skew' });
  }

  if (nonce && !/^[A-Za-z0-9._:-]{8,128}$/.test(nonce)) {
    details.push({ field: 'X-AISFOR-NONCE', message: 'nonce format is invalid' });
  }

  const nonceKey = `${serviceName}:${nonce}`;
  cleanupNonceCache(now);
  if (nonce && usedSignatureNonces.has(nonceKey)) {
    details.push({ field: 'X-AISFOR-NONCE', message: 'nonce was already used' });
  }

  const expected = signRequestPayload(serviceName, req.body || {}, secret, {
    timestamp,
    nonce,
    method: req.method,
    path: req.originalUrl || req.path || ''
  });

  if (!timingSafeEqualString(signature, expected)) {
    details.push({ field: 'X-AISFOR-SIGNATURE', message: 'signature mismatch' });
  }

  if (details.length === 0 && nonce) {
    usedSignatureNonces.set(nonceKey, now);
  }

  return 'hmac-signature-v2';
}

function requireServiceAuth(req, res, next) {
  if (req.serviceIdentity?.authenticated === true) return next();

  const serviceName = String(req.get('X-AISFOR-SERVICE') || req.body?.sourceService || '').trim().toUpperCase();
  const apiKey = String(req.get('X-AISFOR-API-KEY') || '').trim();
  const signature = String(req.get('X-AISFOR-SIGNATURE') || '').trim();
  const secret = getServiceSecret(serviceName);

  if (!isAuthRequired() && !apiKey && !signature) {
    req.serviceIdentity = {
      serviceName: serviceName || 'DEV_UNSIGNED',
      authenticated: false,
      authMode: 'dev-unsigned'
    };
    return next();
  }

  const details = [];
  let authMode = signature ? 'hmac-signature' : 'api-key';

  if (!serviceName) {
    details.push({ field: 'X-AISFOR-SERVICE', message: 'service header is required' });
  }

  if (!secret) {
    details.push({ field: 'X-AISFOR-SERVICE', message: 'service is not configured' });
  }

  if (apiKey && secret && !timingSafeEqualString(apiKey, secret)) {
    details.push({ field: 'X-AISFOR-API-KEY', message: 'api key mismatch' });
  }

  const requireHmacV2 = process.env.AISFOR_CHAIN_REQUIRE_HMAC_V2 === '1';
  const timestamp = String(req.get('X-AISFOR-TIMESTAMP') || '').trim();
  const nonce = String(req.get('X-AISFOR-NONCE') || '').trim();

  if (requireHmacV2 && !signature) {
    details.push({ field: 'X-AISFOR-SIGNATURE', message: 'HMAC v2 signature is required' });
  }

  if (requireHmacV2 && signature && (!timestamp || !nonce)) {
    if (!timestamp) details.push({ field: 'X-AISFOR-TIMESTAMP', message: 'timestamp is required for HMAC v2' });
    if (!nonce) details.push({ field: 'X-AISFOR-NONCE', message: 'nonce is required for HMAC v2' });
  } else if (signature && secret) {
    authMode = validateSignedRequest({ req, serviceName, signature, secret, details });
  }

  if (!apiKey && !signature && !requireHmacV2) {
    details.push({ field: 'authorization', message: 'api key or signature is required' });
  }

  if (req.body?.sourceService && serviceName && String(req.body.sourceService).trim().toUpperCase() !== serviceName) {
    details.push({ field: 'sourceService', message: 'body sourceService must match X-AISFOR-SERVICE' });
  }

  if (details.length > 0) {
    return next(createAuthError('Service authentication failed', details));
  }

  req.serviceIdentity = {
    serviceName,
    authenticated: true,
    authMode
  };

  return next();
}

module.exports = {
  requireServiceAuth,
  signPayload,
  signRequestPayload
};
