import fs from 'node:fs';
import path from 'node:path';
import type { ConfigLoader } from '@zyphon/config-loader';
import type { ApplicationRegistry } from '../core/application-registry.js';
import type { RoutingEngine } from '../core/routing-engine.js';
import type { EventBus } from '../realtime/event-bus.js';

const CLUSTER_FILES = new Set([
  'applications.yaml',
  'providers.yaml',
  'routing.yaml',
  'zyphon.yaml',
]);

const ANCILLARY_FILES = new Set([
  'dlt.yaml',
  'templates.yaml',
  'suppressions.yaml',
  'alerts.yaml',
  'team.yaml',
]);

export class ConfigWatcher {
  private configDir: string;
  private configLoader: ConfigLoader;
  private appRegistry: ApplicationRegistry;
  private routingEngine: RoutingEngine;
  private eventBus: EventBus;
  private watcher: fs.FSWatcher | null = null;
  private debounceTimer: NodeJS.Timeout | null = null;
  private pendingFiles = new Set<string>();

  constructor(deps: {
    configDir: string;
    configLoader: ConfigLoader;
    appRegistry: ApplicationRegistry;
    routingEngine: RoutingEngine;
    eventBus: EventBus;
  }) {
    this.configDir = path.resolve(deps.configDir);
    this.configLoader = deps.configLoader;
    this.appRegistry = deps.appRegistry;
    this.routingEngine = deps.routingEngine;
    this.eventBus = deps.eventBus;
  }

  public start(): void {
    if (!fs.existsSync(this.configDir)) return;

    try {
      this.watcher = fs.watch(this.configDir, (_eventType, filename) => {
        if (!filename || !filename.endsWith('.yaml') || filename.includes('.tmp.')) {
          return;
        }

        this.pendingFiles.add(filename);
        if (this.debounceTimer) clearTimeout(this.debounceTimer);
        this.debounceTimer = setTimeout(() => {
          const files = [...this.pendingFiles];
          this.pendingFiles.clear();
          this.handleConfigChange(files);
        }, 500);
      });
    } catch {
      // In environments where fs.watch is unsupported, graceful fallback
    }
  }

  /**
   * Reload cluster config (apps/routing) and validate ancillary YAML
   * (DLT, templates, suppressions, alerts, team). Invalid files keep
   * last-known-good in-memory state and emit config_reload_failed.
   */
  public handleConfigChange(changedFiles: string[] = []): boolean {
    const files = changedFiles.length
      ? changedFiles
      : [...CLUSTER_FILES, ...ANCILLARY_FILES];

    const touchesCluster = files.some((f) => CLUSTER_FILES.has(f));
    const touchesAncillary = files.some((f) => ANCILLARY_FILES.has(f));

    try {
      if (touchesAncillary || files.length === 0) {
        this.validateAncillaryConfigs(files);
      }

      if (touchesCluster || files.length === 0) {
        const newConfig = this.configLoader.loadClusterConfig();
        this.appRegistry.setApplications(newConfig.applications);
        this.routingEngine.updateConfig(newConfig.routing);

        this.eventBus.publish('config_reloaded', {
          timestamp: Date.now(),
          files,
          applicationsCount: newConfig.applications.length,
          providersCount: newConfig.providers.length,
        });
      } else {
        this.eventBus.publish('config_reloaded', {
          timestamp: Date.now(),
          files,
          applicationsCount: this.appRegistry.getAllApplications().length,
          ancillary: true,
        });
      }

      return true;
    } catch (err: unknown) {
      const message = err instanceof Error ? err.message : 'Config reload failed';
      this.eventBus.publish('config_reload_failed', {
        timestamp: Date.now(),
        files,
        message,
      });
      return false;
    }
  }

  private validateAncillaryConfigs(files: string[]): void {
    const shouldAll = files.length === 0;
    if (shouldAll || files.includes('dlt.yaml')) {
      this.configLoader.loadDltConfig();
    }
    if (shouldAll || files.includes('templates.yaml')) {
      this.configLoader.loadTemplates();
    }
    if (shouldAll || files.includes('suppressions.yaml')) {
      this.configLoader.loadSuppressions();
    }
    if (shouldAll || files.includes('alerts.yaml')) {
      this.configLoader.loadAlerts();
    }
    if (shouldAll || files.includes('team.yaml')) {
      this.configLoader.loadTeam();
    }
  }

  public stop(): void {
    if (this.debounceTimer) {
      clearTimeout(this.debounceTimer);
      this.debounceTimer = null;
    }
    this.pendingFiles.clear();
    if (this.watcher) {
      this.watcher.close();
      this.watcher = null;
    }
  }
}
