/**
 * Periodic flush scheduler — drains IPC buffers and forwards to platform-service.
 *
 * Responsibilities:
 * - Poll feature flags from platform-service → update local registry + IPC bridge
 * - Flush audit events from Rust IPC → POST /audit
 * - Flush telemetry events from Rust IPC → POST /telemetry/events
 * - Flush budget records from Rust IPC → POST /usage
 *
 * All operations are best-effort: failures are logged but never crash the service.
 */

import { type FastifyBaseLogger } from 'fastify';
import { getIpcBridge } from './ipc-bridge.js';
import { setFlag } from './feature-flags.js';
import {
  pollFlags,
  postAuditEvents,
  postTelemetryEvents,
  postUsageRecords,
  type AuditEntry,
  type TelemetryEvent,
  type UsageRecord,
} from './platform-client.js';

export interface FlushSchedulerOptions {
  /** Interval in ms between flush cycles. Default: 30_000 (30s). */
  flushIntervalMs?: number;
  /** Interval in ms between flag polls. Default: 60_000 (60s). */
  flagPollIntervalMs?: number;
  /** Logger instance. */
  log: FastifyBaseLogger;
}

export class FlushScheduler {
  private flushTimer: ReturnType<typeof setInterval> | null = null;
  private flagTimer: ReturnType<typeof setInterval> | null = null;
  private readonly flushIntervalMs: number;
  private readonly flagPollIntervalMs: number;
  private readonly log: FastifyBaseLogger;
  private _running = false;

  constructor(opts: FlushSchedulerOptions) {
    this.flushIntervalMs = opts.flushIntervalMs ?? 30_000;
    this.flagPollIntervalMs = opts.flagPollIntervalMs ?? 60_000;
    this.log = opts.log;
  }

  get isRunning(): boolean {
    return this._running;
  }

  /** Start periodic flush and flag polling. */
  start(): void {
    if (this._running) return;
    this._running = true;

    this.flushTimer = setInterval(() => {
      this.flushAll().catch(err => this.log.error(err, 'flush cycle failed'));
    }, this.flushIntervalMs);

    this.flagTimer = setInterval(() => {
      this.pollAndSyncFlags().catch(err => this.log.error(err, 'flag poll failed'));
    }, this.flagPollIntervalMs);

    this.log.info(
      { flushIntervalMs: this.flushIntervalMs, flagPollIntervalMs: this.flagPollIntervalMs },
      'flush scheduler started',
    );
  }

  /** Stop all timers. */
  stop(): void {
    if (this.flushTimer) clearInterval(this.flushTimer);
    if (this.flagTimer) clearInterval(this.flagTimer);
    this.flushTimer = null;
    this.flagTimer = null;
    this._running = false;
    this.log.info('flush scheduler stopped');
  }

  /** Run a final flush before shutdown. */
  async finalFlush(): Promise<void> {
    this.stop();
    await this.flushAll();
  }

  // ── Flush cycle ────────────────────────────────────────────

  /** Drain all IPC buffers and post to platform-service. */
  async flushAll(): Promise<FlushResult> {
    const bridge = getIpcBridge();
    if (!bridge.isRunning) {
      return { audit: null, telemetry: null, budget: null, skipped: true };
    }

    const svcAuth = { userId: 'system', role: 'admin' };
    const result: FlushResult = { audit: null, telemetry: null, budget: null, skipped: false };

    result.audit = await this.flushAudit(bridge, svcAuth);
    result.telemetry = await this.flushTelemetry(bridge, svcAuth);
    result.budget = await this.flushBudget(bridge, svcAuth);

    return result;
  }

  private async flushAudit(
    bridge: ReturnType<typeof getIpcBridge>,
    auth: Record<string, unknown>,
  ): Promise<{ drained: number; posted: number; errors: number } | null> {
    try {
      const resp = await bridge.flushAudit(auth);
      if (resp.error) {
        this.log.warn({ error: resp.error }, 'IPC flush_audit returned error');
        return null;
      }
      const r = resp.result as Record<string, unknown> | undefined;
      // Rust flush_audit returns { count, entries } — NOT "events"
      const entries: AuditEntry[] = (r?.entries as AuditEntry[]) ?? [];
      if (entries.length === 0) return { drained: 0, posted: 0, errors: 0 };

      const { posted, errors } = await postAuditEvents(entries);
      this.log.debug({ drained: entries.length, posted, errors }, 'audit flush');
      return { drained: entries.length, posted, errors };
    } catch (err) {
      this.log.error(err, 'audit flush failed');
      return null;
    }
  }

  private async flushTelemetry(
    bridge: ReturnType<typeof getIpcBridge>,
    auth: Record<string, unknown>,
  ): Promise<{ drained: number; accepted: number; rejected: number } | null> {
    try {
      const resp = await bridge.flushTelemetry(auth);
      if (resp.error) {
        this.log.warn({ error: resp.error }, 'IPC flush_telemetry returned error');
        return null;
      }
      const r = resp.result as Record<string, unknown> | undefined;
      const events: TelemetryEvent[] = (r?.events as TelemetryEvent[]) ?? [];
      if (events.length === 0) return { drained: 0, accepted: 0, rejected: 0 };

      const { accepted, rejected } = await postTelemetryEvents(events);
      this.log.debug({ drained: events.length, accepted, rejected }, 'telemetry flush');
      return { drained: events.length, accepted, rejected };
    } catch (err) {
      this.log.error(err, 'telemetry flush failed');
      return null;
    }
  }

  private async flushBudget(
    bridge: ReturnType<typeof getIpcBridge>,
    auth: Record<string, unknown>,
  ): Promise<{ drained: number; posted: number; errors: number } | null> {
    try {
      const resp = await bridge.flushBudget(auth);
      if (resp.error) {
        this.log.warn({ error: resp.error }, 'IPC flush_budget returned error');
        return null;
      }
      const r = resp.result as Record<string, unknown> | undefined;
      const records: UsageRecord[] = (r?.records as UsageRecord[]) ?? [];
      if (records.length === 0) return { drained: 0, posted: 0, errors: 0 };

      const { posted, errors } = await postUsageRecords(records);
      this.log.debug({ drained: records.length, posted, errors }, 'budget flush');
      return { drained: records.length, posted, errors };
    } catch (err) {
      this.log.error(err, 'budget flush failed');
      return null;
    }
  }

  // ── Flag polling ───────────────────────────────────────────

  /** Poll platform-service for flag updates and sync to local registry + IPC. */
  async pollAndSyncFlags(): Promise<{ updated: number } | null> {
    try {
      const { flags } = await pollFlags();
      let updated = 0;

      // Update local TS registry
      for (const [key, value] of Object.entries(flags)) {
        setFlag(key, value);
        updated++;
      }

      // Push to Rust IPC bridge if running
      const bridge = getIpcBridge();
      if (bridge.isRunning) {
        await bridge.updateFlags(flags, { userId: 'system', role: 'admin' });
      }

      this.log.debug({ updated }, 'flag poll sync');
      return { updated };
    } catch (err) {
      this.log.error(err, 'flag poll failed');
      return null;
    }
  }
}

export interface FlushResult {
  audit: { drained: number; posted: number; errors: number } | null;
  telemetry: { drained: number; accepted: number; rejected: number } | null;
  budget: { drained: number; posted: number; errors: number } | null;
  skipped?: boolean;
}

// ── Singleton ──

let _scheduler: FlushScheduler | null = null;

export function getFlushScheduler(log?: FastifyBaseLogger): FlushScheduler {
  if (!_scheduler) {
    if (!log) throw new Error('FlushScheduler not initialized — provide a logger');
    _scheduler = new FlushScheduler({ log });
  }
  return _scheduler;
}

export function setFlushScheduler(scheduler: FlushScheduler | null): void {
  _scheduler = scheduler;
}
