/**
 * System Settings Service
 *
 * Manages global system configuration stored in the database.
 * Supports encryption for sensitive values and in-memory caching.
 *
 * Key categories:
 * - n8n: N8N webhook tokens, base URL
 * - integrations: Third-party API keys
 * - security: Rate limits, session settings
 * - general: App-wide configuration
 */

import { prisma } from '@/lib/db';
import { systemSettingsAdapter } from '@/lib/adapters/system';
import { encrypt, decrypt } from '@/lib/encryption';
import { logger } from '@/lib/logger';

// Cache for settings (avoids DB hit on every request)
const settingsCache = new Map<string, { value: string; expiresAt: number }>();
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes

// Well-known setting keys
export const SETTING_KEYS = {
  // N8N Settings
  N8N_WEBHOOK_AUTH_TOKEN: 'n8n.webhook_auth_token',
  N8N_BASE_URL: 'n8n.base_url',
  N8N_API_KEY: 'n8n.api_key',

  // WhatsApp Settings
  WHATSAPP_APP_SECRET: 'whatsapp.app_secret',
  WHATSAPP_VERIFY_TOKEN: 'whatsapp.verify_token',

  // ElevenLabs Settings
  ELEVENLABS_API_KEY: 'elevenlabs.api_key',

  // General
  MAINTENANCE_MODE: 'general.maintenance_mode',
} as const;

// Keys that should be encrypted
const ENCRYPTED_KEYS = new Set([
  SETTING_KEYS.N8N_WEBHOOK_AUTH_TOKEN,
  SETTING_KEYS.N8N_API_KEY,
  SETTING_KEYS.WHATSAPP_APP_SECRET,
  SETTING_KEYS.ELEVENLABS_API_KEY,
]);

export type SettingKey = (typeof SETTING_KEYS)[keyof typeof SETTING_KEYS];

interface SettingResult {
  key: string;
  value: string | null;
  description: string | null;
  category: string;
  encrypted: boolean;
  updatedAt: Date;
  updatedBy: string | null;
}

class SystemSettingsService {
  /**
   * Get a setting value by key
   * Returns null if not found
   */
  async get(key: string): Promise<string | null> {
    // Check cache first
    const cached = settingsCache.get(key);
    if (cached && cached.expiresAt > Date.now()) {
      return cached.value;
    }

    try {
      const setting = await systemSettingsAdapter.findUnique({
        where: { key },
      });

      if (!setting) {
        return null;
      }

      let value = setting.value;

      // Decrypt if encrypted
      if (setting.encrypted) {
        try {
          value = decrypt(value);
        } catch (error) {
          logger.error('Failed to decrypt setting', { key, error });
          return null;
        }
      }

      // Cache the decrypted value
      settingsCache.set(key, {
        value,
        expiresAt: Date.now() + CACHE_TTL_MS,
      });

      return value;
    } catch (error) {
      logger.error('Failed to get setting', { key, error });
      return null;
    }
  }

  /**
   * Get a setting with fallback to environment variable
   * Useful during migration from env-based to DB-based config
   */
  async getWithEnvFallback(key: string, envKey: string): Promise<string | null> {
    const dbValue = await this.get(key);
    if (dbValue) {
      return dbValue;
    }

    // Fall back to environment variable
    return process.env[envKey] || null;
  }

  /**
   * Set a setting value
   */
  async set(
    key: string,
    value: string,
    options?: {
      description?: string;
      category?: string;
      updatedBy?: string;
    }
  ): Promise<boolean> {
    try {
      const shouldEncrypt = ENCRYPTED_KEYS.has(key as SettingKey);
      const storedValue = shouldEncrypt ? encrypt(value) : value;

      await systemSettingsAdapter.upsert({
        where: { key },
        create: {
          key,
          value: storedValue,
          encrypted: shouldEncrypt,
          description: options?.description,
          category: options?.category || this.getCategoryFromKey(key),
          updatedBy: options?.updatedBy,
        },
        update: {
          value: storedValue,
          encrypted: shouldEncrypt,
          description: options?.description,
          category: options?.category || this.getCategoryFromKey(key),
          updatedBy: options?.updatedBy,
          updatedAt: new Date(),
        },
      });

      // Update cache
      settingsCache.set(key, {
        value,
        expiresAt: Date.now() + CACHE_TTL_MS,
      });

      logger.info('Setting updated', {
        key,
        encrypted: shouldEncrypt,
        updatedBy: options?.updatedBy,
      });

      return true;
    } catch (error) {
      logger.error('Failed to set setting', { key, error });
      return false;
    }
  }

  /**
   * Delete a setting
   */
  async delete(key: string): Promise<boolean> {
    try {
      await systemSettingsAdapter.delete({
        where: { key },
      });

      settingsCache.delete(key);

      logger.info('Setting deleted', { key });
      return true;
    } catch (error) {
      logger.error('Failed to delete setting', { key, error });
      return false;
    }
  }

  /**
   * Get all settings in a category (for admin UI)
   * Values are decrypted for display (masked in UI)
   */
  async getByCategory(category: string): Promise<SettingResult[]> {
    try {
      const settings = await systemSettingsAdapter.findMany({
        where: { category },
        orderBy: { key: 'asc' },
      });

      return settings.map((s) => ({
        key: s.key,
        value: s.encrypted ? this.maskValue(s.value) : s.value,
        description: s.description,
        category: s.category,
        encrypted: s.encrypted,
        updatedAt: s.updatedAt,
        updatedBy: s.updatedBy,
      }));
    } catch (error) {
      logger.error('Failed to get settings by category', { category, error });
      return [];
    }
  }

  /**
   * Get all settings (for admin UI)
   */
  async getAll(): Promise<SettingResult[]> {
    try {
      const settings = await systemSettingsAdapter.findMany({
        orderBy: [{ category: 'asc' }, { key: 'asc' }],
      });

      return settings.map((s) => ({
        key: s.key,
        value: s.encrypted ? this.maskValue(s.value) : s.value,
        description: s.description,
        category: s.category,
        encrypted: s.encrypted,
        updatedAt: s.updatedAt,
        updatedBy: s.updatedBy,
      }));
    } catch (error) {
      logger.error('Failed to get all settings', { error });
      return [];
    }
  }

  /**
   * Clear the cache (useful after bulk updates)
   */
  clearCache(): void {
    settingsCache.clear();
    logger.info('Settings cache cleared');
  }

  /**
   * Get N8N webhook token (convenience method)
   */
  async getN8NWebhookToken(): Promise<string | null> {
    return this.getWithEnvFallback(
      SETTING_KEYS.N8N_WEBHOOK_AUTH_TOKEN,
      'N8N_WEBHOOK_AUTH_TOKEN'
    );
  }

  /**
   * Get N8N base URL (convenience method)
   */
  async getN8NBaseUrl(): Promise<string | null> {
    return this.getWithEnvFallback(SETTING_KEYS.N8N_BASE_URL, 'N8N_BASE_URL');
  }

  /**
   * Validate N8N auth token from request
   */
  async validateN8NToken(providedToken: string): Promise<boolean> {
    const expectedToken = await this.getN8NWebhookToken();
    if (!expectedToken) {
      logger.warn('N8N webhook token not configured');
      return false;
    }
    return providedToken === expectedToken;
  }

  // Helper: Extract category from key
  private getCategoryFromKey(key: string): string {
    const parts = key.split('.');
    return parts[0] || 'general';
  }

  // Helper: Mask sensitive values for display
  private maskValue(value: string): string {
    if (value.length <= 8) {
      return '••••••••';
    }
    return value.substring(0, 4) + '••••••••' + value.substring(value.length - 4);
  }
}

export const systemSettingsService = new SystemSettingsService();
