/**
 * ╔═══════════════════════════════════════════════════════════════╗
 * ║  FeatureFlagsService — Singleton управления Feature Flags     ║
 * ╚═══════════════════════════════════════════════════════════════╝
 *
 * Принцип работы:
 *  - При старте считывает config.json → поле "features" (если есть)
 *  - Merge с DEFAULT_FLAGS (пакет-зависимые флаги приоритетнее)
 *  - Предоставляет isEnabled(flag) для Guard/Service/Interceptor
 *  - Поддерживает runtime-переключение через set(flag, value)
 *    которое вызывает /api/features PATCH-эндпоинт
 *
 * Почему @Global():
 *  Каждый модуль (AlarmModule, DeviceModule, …) проверяет флаги
 *  без дополнительных импортов — достаточно инжектировать сервис.
 */

import {
  Injectable, Global, Logger, OnModuleInit, BadRequestException,
} from '@nestjs/common';
import * as fs   from 'fs';
import * as path from 'path';
import { IFeatureFlags, buildDefaultFlags } from './feature-flags.config';

@Injectable()
export class FeatureFlagsService implements OnModuleInit {
  private readonly logger = new Logger(FeatureFlagsService.name);

  /** In-memory хранилище флагов */
  private flags: IFeatureFlags = buildDefaultFlags();

  /** Путь к config.json (из DATA_DIR или ./data/config.json) */
  private get configPath(): string {
    const dataDir = process.env.DATA_DIR ?? path.join(process.cwd(), 'data');
    return path.join(dataDir, 'config.json');
  }

  /**
   * onModuleInit — вызывается NestJS автоматически при старте модуля.
   * Читаем config.json и мержим флаги из него поверх дефолтов.
   * Это позволяет сохранять состояние флагов между перезапусками.
   */
  onModuleInit(): void {
    this.loadFromDisk();
  }

  // ─────────────────────────────────────────────────────────────
  // PUBLIC API
  // ─────────────────────────────────────────────────────────────

  /**
   * Проверить, включён ли флаг.
   * Основной метод — используется везде в коде.
   *
   * @example
   *   if (!this.flags.isEnabled('ALARMS')) return;
   */
  isEnabled(flag: keyof IFeatureFlags): boolean {
    return this.flags[flag] === true;
  }

  /**
   * Получить все флаги (для GET /api/features и /api/config).
   * Возвращает shallow-copy чтобы нельзя было мутировать напрямую.
   */
  getAll(): IFeatureFlags {
    return { ...this.flags };
  }

  /**
   * Переключить флаг в runtime.
   * Вызывается из FeaturesController (PATCH /api/features).
   *
   * @param flag  - имя флага (строка, валидируется)
   * @param value - новое значение (boolean)
   * @returns обновлённый набор флагов
   */
  set(flag: string, value: boolean): IFeatureFlags {
    if (!(flag in this.flags)) {
      throw new BadRequestException(`Неизвестный feature flag: ${flag}`);
    }
    (this.flags as Record<string, boolean>)[flag] = value;
    this.logger.log(`Feature flag изменён: ${flag} = ${value}`);
    // Асинхронно сохраняем на диск (не блокируем ответ)
    this.saveToDisk().catch(e =>
      this.logger.warn(`Не удалось сохранить флаги на диск: ${e.message}`)
    );
    return this.getAll();
  }

  // ─────────────────────────────────────────────────────────────
  // PRIVATE: персистентность
  // ─────────────────────────────────────────────────────────────

  /**
   * Читает config.json и мержит поле "features" поверх дефолтных значений.
   * Пакет-зависимые флаги (MODBUS, HISTORIAN, …) не перезаписываются
   * если пакет НЕ установлен — это OT-безопасность.
   */
  private loadFromDisk(): void {
    try {
      if (!fs.existsSync(this.configPath)) {
        this.logger.debug('config.json не найден, используем дефолтные флаги');
        return;
      }

      const raw     = fs.readFileSync(this.configPath, 'utf8');
      const config  = JSON.parse(raw) as Record<string, unknown>;
      const saved   = (config.features ?? {}) as Record<string, boolean>;
      const defaults= buildDefaultFlags();

      // Мерж: сохранённые значения применяем только для флагов
      // которые НЕ auto-определяются по пакету
      const autoFlags = new Set<string>([
        'HISTORIAN', 'MODBUS', 'OPCUA', 'MQTT_CLIENT',
        'REPORTS', 'SECURITY_HEADERS', 'COMPRESSION',
      ]);

      for (const [k, v] of Object.entries(saved)) {
        if (k in this.flags && !autoFlags.has(k)) {
          (this.flags as Record<string, boolean>)[k] = v;
        }
      }

      const enabledCount = Object.values(this.flags).filter(Boolean).length;
      this.logger.log(
        `Feature Flags загружены из config.json: ${enabledCount}/${Object.keys(this.flags).length} включено`
      );
    } catch (e) {
      this.logger.warn(`Ошибка загрузки флагов: ${(e as Error).message}, используем дефолты`);
    }
  }

  /**
   * Сохраняет текущие флаги в config.json.
   * Атомарная запись через временный файл.
   */
  private async saveToDisk(): Promise<void> {
    const filePath = this.configPath;
    const dir = path.dirname(filePath);
    fs.mkdirSync(dir, { recursive: true });

    let config: Record<string, unknown> = {};
    try {
      if (fs.existsSync(filePath)) {
        config = JSON.parse(fs.readFileSync(filePath, 'utf8'));
      }
    } catch (_) {}

    config.features = this.getAll();
    const tmp = filePath + '.tmp';
    fs.writeFileSync(tmp, JSON.stringify(config, null, 2), 'utf8');
    fs.renameSync(tmp, filePath);
  }
}
