const path = require('path');
const StateManager = require('../managers/state-manager');
const crypto = require('crypto');

const AUDIT_LOG_FILE = process.env.AUDIT_LOG_FILE || path.join(__dirname, 'audit-log.json');
const MAX_ENTRIES = parseInt(process.env.AUDIT_MAX_ENTRIES || '1000', 10);

// Route path → readable action mapping
const ACTION_MAP = {
  'POST /api/v1/services/update': 'service.reorder',
  'POST /api/v1/services': 'service.create',
  'PUT /api/v1/services': 'service.update',
  'DELETE /api/v1/services/': 'service.delete',
  'POST /api/v1/site': 'caddy.add-site',
  'POST /api/v1/site/external': 'caddy.add-external',
  'DELETE /api/v1/site/': 'caddy.remove-site',
  'POST /api/v1/caddy/reload': 'caddy.reload',
  'POST /api/v1/dns/record': 'dns.add-record',
  'DELETE /api/v1/dns/record': 'dns.delete-record',
  'POST /api/v1/dns/credentials': 'dns.save-credentials',
  'DELETE /api/v1/dns/credentials': 'dns.delete-credentials',
  'POST /api/v1/dns/refresh-token': 'dns.refresh-token',
  'POST /api/v1/dns/update': 'dns.update-server',
  'POST /api/v1/containers/': 'container.action',
  'DELETE /api/v1/containers/': 'container.delete',
  'POST /api/v1/apps/deploy': 'container.deploy',
  'DELETE /api/v1/apps/': 'container.undeploy',
  'POST /api/v1/backups/execute': 'backup.execute',
  'POST /api/v1/backups/restore/': 'backup.restore',
  'POST /api/v1/backups/config': 'backup.config',
  'POST /api/v1/config': 'config.update',
  'DELETE /api/v1/config': 'config.reset',
  'POST /api/v1/notifications/config': 'config.notifications',
  'POST /api/v1/totp/setup': 'auth.totp-setup',
  'POST /api/v1/totp/verify-setup': 'auth.totp-activate',
  'POST /api/v1/totp/disable': 'auth.totp-disable',
  'POST /api/v1/totp/config': 'auth.totp-config',
  'POST /api/v1/credentials/rotate-key': 'config.rotate-key',
  'POST /api/v1/updates/update/': 'container.update',
  'POST /api/v1/updates/rollback/': 'container.rollback',
  'POST /api/v1/updates/auto-update/': 'container.auto-update',
  'POST /api/v1/updates/check': 'container.check-updates',
  'POST /api/v1/health-checks/': 'config.health-check',
  'DELETE /api/v1/health-checks/': 'config.health-check-delete',
  'POST /api/v1/monitoring/alerts/': 'config.monitoring-alert',
  'DELETE /api/v1/monitoring/alerts/': 'config.monitoring-alert-delete',
  'POST /api/v1/arr/smart-connect': 'service.arr-connect',
  'POST /api/v1/arr/credentials': 'config.arr-credentials',
  'DELETE /api/v1/arr/credentials/': 'config.arr-credentials-delete',
  'POST /api/v1/logo': 'config.logo-upload',
  'DELETE /api/v1/logo': 'config.logo-delete',
  'POST /api/v1/favicon': 'config.favicon-upload',
  'DELETE /api/v1/favicon': 'config.favicon-delete',
  'POST /api/v1/tailscale/config': 'config.tailscale',
  'POST /api/v1/tailscale/protect-service': 'config.tailscale-protect',
};

// Paths to skip logging (noisy or internal)
const SKIP_PATHS = [
  '/api/v1/totp/verify',
  '/api/v1/totp/check-session',
  '/api/v1/auth/gate/',
  '/api/v1/auth/app-token/',
  '/api/v1/audit-logs',
  '/api/v1/health',
  '/health',
  '/api/v1/notifications/test',
  '/api/v1/notifications/health-check',
];

class AuditLogger {
  constructor() {
    this.stateManager = new StateManager(AUDIT_LOG_FILE);
  }

  resolveAction(method, urlPath) {
    const key = `${method} ${urlPath}`;
    // Exact match first
    if (ACTION_MAP[key]) return ACTION_MAP[key];
    // Prefix match (for parameterized routes like /api/services/:id)
    for (const [pattern, action] of Object.entries(ACTION_MAP)) {
      if (key.startsWith(pattern)) return action;
    }
    // Fallback: derive from path
    const parts = urlPath.replace('/api/v1/', '').split('/');
    const category = parts[0] || 'unknown';
    return `${category}.${method.toLowerCase()}`;
  }

  extractResource(urlPath) {
    // Pull a meaningful resource identifier from the URL path
    const parts = urlPath.replace('/api/v1/', '').split('/');
    if (parts.length >= 2) return parts.slice(1).join('/');
    return parts[0] || '';
  }

  shouldSkip(method, urlPath) {
    if (method === 'GET') return true;
    for (const skip of SKIP_PATHS) {
      if (urlPath.startsWith(skip)) return true;
    }
    return false;
  }

  async log({ action, resource, details, outcome, ip }) {
    try {
      const entry = {
        id: crypto.randomUUID(),
        timestamp: new Date().toISOString(),
        ip: ip || '',
        action: action || '',
        resource: resource || '',
        details: details || {},
        outcome: outcome || 'unknown'
      };

      await this.stateManager.update(entries => {
        entries.unshift(entry);
        if (entries.length > MAX_ENTRIES) {
          entries.length = MAX_ENTRIES;
        }
        return entries;
      });
    } catch (e) {
      console.error('[AuditLogger] Failed to write entry:', e.message);
    }
  }

  async query({ limit = 50, offset = 0, action } = {}) {
    try {
      let entries = await this.stateManager.read();
      if (action) {
        entries = entries.filter(e => e.action && e.action.startsWith(action));
      }
      return entries.slice(offset, offset + limit);
    } catch (e) {
      console.error('[AuditLogger] Failed to read:', e.message);
      return [];
    }
  }

  async clear() {
    await this.stateManager.write([]);
  }

  middleware() {
    return (req, res, next) => {
      if (this.shouldSkip(req.method, req.path)) return next();

      const originalJson = res.json.bind(res);
      res.json = (data) => {
        // Log asynchronously — don't block the response
        const ip = req.ip || req.socket?.remoteAddress || '';
        const action = this.resolveAction(req.method, req.path);
        const resource = this.extractResource(req.path);
        const outcome = data && data.success === false ? 'failure' : 'success';

        // Sanitize details — don't log passwords or tokens
        const details = {};
        if (req.params && Object.keys(req.params).length) details.params = req.params;
        if (req.body) {
          const safe = { ...req.body };
          for (const key of ['password', 'token', 'secret', 'apikey', 'encryptionKey', 'code']) {
            if (safe[key]) safe[key] = '***';
          }
          details.body = safe;
        }

        this.log({ action, resource, details, outcome, ip }).catch(() => {});

        return originalJson(data);
      };
      next();
    };
  }
}

module.exports = new AuditLogger();
