/**
 * System Settings Service
 *
 * Manages system configuration stored in the database with in-memory caching
 * Settings include Gemini API credentials, PayPal configuration, pricing, etc.
 */

import { PrismaClient } from '@prisma/client';
import { logger } from '../utils/logger';

const prisma = new PrismaClient();

/**
 * Setting keys enum for type safety
 */
export enum SettingKey {
  // Google Gemini API
  GEMINI_API_KEY = 'gemini_api_key',

  // PayPal
  PAYPAL_CLIENT_ID = 'paypal_client_id',
  PAYPAL_CLIENT_SECRET = 'paypal_client_secret',
  PAYPAL_MODE = 'paypal_mode',
  PAYPAL_WEBHOOK_ID = 'paypal_webhook_id',

  // PayPal Plan IDs
  PAYPAL_PLAN_STARTER_MONTHLY = 'paypal_plan_starter_monthly',
  PAYPAL_PLAN_STARTER_ANNUAL = 'paypal_plan_starter_annual',
  PAYPAL_PLAN_PROFESSIONAL_MONTHLY = 'paypal_plan_professional_monthly',
  PAYPAL_PLAN_PROFESSIONAL_ANNUAL = 'paypal_plan_professional_annual',
  PAYPAL_PLAN_ENTERPRISE_MONTHLY = 'paypal_plan_enterprise_monthly',
  PAYPAL_PLAN_ENTERPRISE_ANNUAL = 'paypal_plan_enterprise_annual',

  // Pricing (cost per 1K characters in USD)
  PRICING_PER_1K_CHARACTERS = 'pricing_per_1k_characters',

  // Credit Allocations
  CREDITS_STARTER = 'credits_starter',
  CREDITS_PROFESSIONAL = 'credits_professional',
  CREDITS_ENTERPRISE = 'credits_enterprise',

  // Rate Limits
  RATE_LIMIT_STARTER = 'rate_limit_starter',
  RATE_LIMIT_PROFESSIONAL = 'rate_limit_professional',
  RATE_LIMIT_ENTERPRISE = 'rate_limit_enterprise',
}

/**
 * Settings cache interface
 */
interface SettingsCache {
  data: Map<string, any>;
  lastRefresh: number;
  refreshInterval: number; // milliseconds
}

/**
 * In-memory settings cache (singleton)
 */
const cache: SettingsCache = {
  data: new Map(),
  lastRefresh: 0,
  refreshInterval: 5 * 60 * 1000, // 5 minutes
};

/**
 * System Settings Service
 */
class SettingsService {
  /**
   * Check if cache needs refresh
   */
  private needsRefresh(): boolean {
    const now = Date.now();
    return now - cache.lastRefresh > cache.refreshInterval;
  }

  /**
   * Refresh cache from database
   */
  private async refreshCache(): Promise<void> {
    try {
      logger.debug('Refreshing settings cache from database');

      const settings = await prisma.systemSetting.findMany({
        select: {
          key: true,
          value: true,
        },
      });

      // Clear existing cache
      cache.data.clear();

      // Populate cache with fresh data
      for (const setting of settings) {
        cache.data.set(setting.key, setting.value);
      }

      cache.lastRefresh = Date.now();

      logger.info('Settings cache refreshed', {
        settingsCount: settings.length,
        timestamp: new Date(cache.lastRefresh).toISOString(),
      });
    } catch (error) {
      logger.error('Failed to refresh settings cache', { error });
      throw new Error('Failed to refresh settings cache');
    }
  }

  /**
   * Get all system settings
   * Automatically refreshes cache if stale
   *
   * @returns Map of all settings
   */
  async getSystemSettings(): Promise<Map<string, any>> {
    if (cache.data.size === 0 || this.needsRefresh()) {
      await this.refreshCache();
    }

    return new Map(cache.data);
  }

  /**
   * Get a specific setting by key
   * Returns undefined if setting not found
   *
   * @param key Setting key
   * @returns Setting value or undefined
   */
  async getSetting<T = any>(key: string | SettingKey): Promise<T | undefined> {
    if (cache.data.size === 0 || this.needsRefresh()) {
      await this.refreshCache();
    }

    return cache.data.get(key) as T | undefined;
  }

  /**
   * Get a setting with a default fallback value
   *
   * @param key Setting key
   * @param defaultValue Default value if setting not found
   * @returns Setting value or default
   */
  async getSettingWithDefault<T = any>(key: string | SettingKey, defaultValue: T): Promise<T> {
    const value = await this.getSetting<T>(key);
    return value !== undefined ? value : defaultValue;
  }

  /**
   * Update a setting value
   * Updates database and cache
   *
   * @param key Setting key
   * @param value Setting value (will be stored as JSON)
   * @param description Optional description
   * @param updatedBy Optional admin user ID
   */
  async updateSetting(
    key: string | SettingKey,
    value: any,
    description?: string,
    updatedBy?: string
  ): Promise<void> {
    try {
      logger.debug('Updating setting', { key, hasValue: value !== undefined });

      // Update in database
      await prisma.systemSetting.upsert({
        where: { key: String(key) },
        create: {
          key: String(key),
          value,
          description: description || `System setting: ${key}`,
          updated_by: updatedBy,
        },
        update: {
          value,
          ...(description && { description }),
          updated_by: updatedBy,
        },
      });

      // Update cache
      cache.data.set(String(key), value);

      logger.info('Setting updated successfully', {
        key,
        updatedBy,
        timestamp: new Date().toISOString(),
      });
    } catch (error) {
      logger.error('Failed to update setting', { key, error });
      throw new Error(`Failed to update setting: ${key}`);
    }
  }

  /**
   * Delete a setting
   * Removes from database and cache
   *
   * @param key Setting key
   */
  async deleteSetting(key: string | SettingKey): Promise<void> {
    try {
      logger.debug('Deleting setting', { key });

      // Delete from database
      await prisma.systemSetting.delete({
        where: { key: String(key) },
      });

      // Delete from cache
      cache.data.delete(String(key));

      logger.info('Setting deleted successfully', {
        key,
        timestamp: new Date().toISOString(),
      });
    } catch (error) {
      logger.error('Failed to delete setting', { key, error });
      throw new Error(`Failed to delete setting: ${key}`);
    }
  }

  async matchesSettingVersions(expectedVersions: Record<string, string>): Promise<boolean> {
    const keys = Object.keys(expectedVersions);
    const settings = await prisma.systemSetting.findMany({
      where: { key: { in: keys } },
      select: { key: true, updated_at: true },
    });

    return settings.length === keys.length && settings.every(
      (setting) => expectedVersions[setting.key] === setting.updated_at.toISOString()
    );
  }

  /**
   * Force cache refresh
   * Useful after bulk updates or when cache might be stale
   */
  async forceRefresh(): Promise<void> {
    await this.refreshCache();
  }

  /**
   * Clear cache (for testing purposes)
   */
  clearCache(): void {
    cache.data.clear();
    cache.lastRefresh = 0;
  }

  /**
   * Get Gemini API configuration
   */
  async getGeminiConfig(): Promise<{ apiKey: string } | undefined> {
    const apiKey = await this.getSetting<string>(SettingKey.GEMINI_API_KEY);

    if (!apiKey) {
      return undefined;
    }

    return { apiKey };
  }

  /**
   * Get PayPal configuration
   */
  async getPayPalConfig(): Promise<
    | {
        clientId: string;
        clientSecret: string;
        webhookId: string;
        mode: 'sandbox' | 'live';
      }
    | undefined
  > {
    const clientId = await this.getSetting<string>(SettingKey.PAYPAL_CLIENT_ID);
    const clientSecret = await this.getSetting<string>(SettingKey.PAYPAL_CLIENT_SECRET);
    const webhookId = await this.getSetting<string>(SettingKey.PAYPAL_WEBHOOK_ID);
    const mode = await this.getSetting<string>(SettingKey.PAYPAL_MODE);

    if (
      !clientId ||
      !clientSecret ||
      !webhookId ||
      (mode !== 'sandbox' && mode !== 'live')
    ) {
      return undefined;
    }

    return {
      clientId,
      clientSecret,
      webhookId,
      mode,
    };
  }

  /**
   * Get pricing configuration
   */
  async getPricingConfig(): Promise<{ per1kCharacters: number }> {
    const per1kCharacters = await this.getSettingWithDefault<number>(SettingKey.PRICING_PER_1K_CHARACTERS, 0.002);

    return { per1kCharacters };
  }

  /**
   * Get credit allocations
   */
  async getCreditAllocations(): Promise<{
    starter: number;
    professional: number;
    enterprise: number;
  }> {
    const starter = await this.getSettingWithDefault<number>(SettingKey.CREDITS_STARTER, 100000);
    const professional = await this.getSettingWithDefault<number>(
      SettingKey.CREDITS_PROFESSIONAL,
      500000
    );
    const enterprise = await this.getSettingWithDefault<number>(
      SettingKey.CREDITS_ENTERPRISE,
      2000000
    );

    return { starter, professional, enterprise };
  }

  /**
   * Get rate limits
   */
  async getRateLimits(): Promise<{ starter: number; professional: number; enterprise: number }> {
    const starter = await this.getSettingWithDefault<number>(SettingKey.RATE_LIMIT_STARTER, 60);
    const professional = await this.getSettingWithDefault<number>(
      SettingKey.RATE_LIMIT_PROFESSIONAL,
      120
    );
    const enterprise = await this.getSettingWithDefault<number>(SettingKey.RATE_LIMIT_ENTERPRISE, 0);

    return { starter, professional, enterprise };
  }
}

// Export singleton instance
export const settingsService = new SettingsService();
