import { Repository, ILike, FindOptionsWhere } from 'typeorm';
import { SystemSetting } from '../entities';
import { UpdateSystemSettingDto, CreateSystemSettingDto, SystemSettingsFiltersDto } from '../dto';
import { logger } from '../../../shared/utils/logger.js';

/**
 * Cache entry for system settings
 */
interface CacheEntry {
  value: any;
  expiresAt: number;
}

/**
 * SystemSettingsService
 *
 * Manages global system settings that apply across all tenants.
 * Implements in-memory caching for performance optimization.
 *
 * Features:
 * - CRUD operations for system settings
 * - In-memory cache with TTL
 * - Category-based filtering
 * - Reset to default values
 */
export class SystemSettingsService {
  private cache: Map<string, CacheEntry> = new Map();
  private cacheTtlMs: number = 5 * 60 * 1000; // 5 minutes default

  constructor(private readonly settingRepository: Repository<SystemSetting>) {}

  /**
   * Sets the cache TTL in milliseconds
   */
  setCacheTtl(ttlMs: number): void {
    this.cacheTtlMs = ttlMs;
  }

  /**
   * Clears the entire cache
   */
  clearCache(): void {
    this.cache.clear();
    logger.debug('System settings cache cleared');
  }

  /**
   * Clears a specific key from cache
   */
  clearCacheKey(key: string): void {
    this.cache.delete(key);
    this.cache.delete(`all:${key}`);
    logger.debug('System settings cache key cleared', { key });
  }

  /**
   * Get all system settings with optional filtering
   */
  async getAll(filters: SystemSettingsFiltersDto = {}): Promise<SystemSetting[]> {
    const { category, isPublic, isEditable, search } = filters;

    const where: FindOptionsWhere<SystemSetting>[] = [];
    const baseWhere: FindOptionsWhere<SystemSetting> = {};

    if (category) {
      baseWhere.category = category;
    }

    if (isPublic !== undefined) {
      baseWhere.isPublic = isPublic;
    }

    if (isEditable !== undefined) {
      baseWhere.isEditable = isEditable;
    }

    if (search) {
      where.push(
        { ...baseWhere, key: ILike(`%${search}%`) },
        { ...baseWhere, description: ILike(`%${search}%`) }
      );
    } else {
      where.push(baseWhere);
    }

    const settings = await this.settingRepository.find({
      where: where.length > 0 ? where : undefined,
      order: { category: 'ASC', key: 'ASC' },
    });

    return settings;
  }

  /**
   * Get all public system settings (for unauthenticated access)
   */
  async getPublicSettings(): Promise<SystemSetting[]> {
    const cacheKey = 'public:all';
    const cached = this.getFromCache(cacheKey);
    if (cached !== undefined) {
      return cached;
    }

    const settings = await this.settingRepository.find({
      where: { isPublic: true },
      order: { category: 'ASC', key: 'ASC' },
    });

    this.setCache(cacheKey, settings);
    return settings;
  }

  /**
   * Get settings by category
   */
  async getByCategory(category: string): Promise<SystemSetting[]> {
    const cacheKey = `category:${category}`;
    const cached = this.getFromCache(cacheKey);
    if (cached !== undefined) {
      return cached;
    }

    const settings = await this.settingRepository.find({
      where: { category },
      order: { key: 'ASC' },
    });

    this.setCache(cacheKey, settings);
    return settings;
  }

  /**
   * Get a single setting by key
   */
  async getByKey(key: string): Promise<SystemSetting | null> {
    const cacheKey = `key:${key}`;
    const cached = this.getFromCache(cacheKey);
    if (cached !== undefined) {
      return cached;
    }

    const setting = await this.settingRepository.findOne({
      where: { key },
    });

    if (setting) {
      this.setCache(cacheKey, setting);
    }

    return setting;
  }

  /**
   * Get the value of a setting by key
   */
  async getValue(key: string): Promise<any> {
    const setting = await this.getByKey(key);
    return setting?.value ?? null;
  }

  /**
   * Update a system setting by key
   */
  async update(
    key: string,
    dto: UpdateSystemSettingDto,
    updatedBy?: string
  ): Promise<SystemSetting | null> {
    const setting = await this.settingRepository.findOne({ where: { key } });

    if (!setting) {
      logger.warn('System setting not found for update', { key });
      return null;
    }

    if (!setting.isEditable) {
      logger.warn('Attempted to update non-editable setting', { key });
      throw new Error(`Setting '${key}' is not editable`);
    }

    Object.assign(setting, {
      ...dto,
      updatedBy,
    });

    const updated = await this.settingRepository.save(setting);

    this.clearCacheKey(key);
    this.cache.delete('public:all');
    this.cache.delete(`category:${setting.category}`);

    logger.info('System setting updated', { key, updatedBy });

    return updated;
  }

  /**
   * Reset a setting to its default value
   */
  async reset(key: string, updatedBy?: string): Promise<SystemSetting | null> {
    const setting = await this.settingRepository.findOne({ where: { key } });

    if (!setting) {
      logger.warn('System setting not found for reset', { key });
      return null;
    }

    if (setting.defaultValue === null || setting.defaultValue === undefined) {
      logger.warn('Setting has no default value', { key });
      throw new Error(`Setting '${key}' has no default value`);
    }

    setting.value = setting.defaultValue;
    setting.updatedBy = updatedBy ?? null;

    const updated = await this.settingRepository.save(setting);

    this.clearCacheKey(key);
    this.cache.delete('public:all');
    this.cache.delete(`category:${setting.category}`);

    logger.info('System setting reset to default', { key, updatedBy });

    return updated;
  }

  /**
   * Create a new system setting (admin only)
   */
  async create(dto: CreateSystemSettingDto, createdBy?: string): Promise<SystemSetting> {
    const existing = await this.settingRepository.findOne({ where: { key: dto.key } });
    if (existing) {
      throw new Error(`Setting with key '${dto.key}' already exists`);
    }

    const setting = this.settingRepository.create({
      ...dto,
      updatedBy: createdBy,
    });

    const created = await this.settingRepository.save(setting);

    this.clearCache();
    logger.info('System setting created', { key: dto.key, createdBy });

    return created;
  }

  /**
   * Get all unique categories
   */
  async getCategories(): Promise<string[]> {
    const cacheKey = 'categories:all';
    const cached = this.getFromCache(cacheKey);
    if (cached !== undefined) {
      return cached;
    }

    const result = await this.settingRepository
      .createQueryBuilder('setting')
      .select('DISTINCT setting.category', 'category')
      .orderBy('setting.category', 'ASC')
      .getRawMany();

    const categories = result.map((r: { category: string }) => r.category);
    this.setCache(cacheKey, categories);

    return categories;
  }

  /**
   * Cache helper: get from cache if not expired
   */
  private getFromCache(key: string): any {
    const entry = this.cache.get(key);
    if (entry && entry.expiresAt > Date.now()) {
      return entry.value;
    }
    if (entry) {
      this.cache.delete(key);
    }
    return undefined;
  }

  /**
   * Cache helper: set value in cache with TTL
   */
  private setCache(key: string, value: any): void {
    this.cache.set(key, {
      value,
      expiresAt: Date.now() + this.cacheTtlMs,
    });
  }
}
