/**
 * Persist operator Sentinel limits to disk (survives Gateway restart).
 * File: {4Runr data dir}/config/sentinel-limits.json
 *
 * Boot order: env defaults (packages/sentinel) → overlay saved file when present.
 * Explicit env vars for a field are not overwritten by the saved file.
 */

import * as fs from 'fs';
import * as path from 'path';
import { get4RunrConfigDir } from '@4runr/shared';
import { Sentinel, type SentinelConfig } from '@4runr/sentinel';

const FILE_NAME = 'sentinel-limits.json';

const ENV_FIELD_KEYS: Array<{
  field: keyof SentinelConfig;
  envKeys: string[];
}> = [
  { field: 'enabled', envKeys: ['SENTINEL_ENABLED'] },
  { field: 'runMaxDurationMs', envKeys: ['RUN_MAX_DURATION_MS'] },
  { field: 'runMaxTokens', envKeys: ['RUN_MAX_TOKENS'] },
  { field: 'runIdleMs', envKeys: ['SENTINEL_IDLE_MS', 'RUN_IDLE_MS'] },
  { field: 'loopWindow', envKeys: ['SENTINEL_LOOP_WINDOW'] },
  { field: 'loopMax', envKeys: ['SENTINEL_LOOP_MAX'] },
  { field: 'runMaxCost', envKeys: ['RUN_MAX_COST', 'SENTINEL_MAX_COST'] },
];

export function getSentinelLimitsFilePath(): string {
  return path.join(get4RunrConfigDir(), FILE_NAME);
}

function envDefinesField(field: keyof SentinelConfig): boolean {
  const entry = ENV_FIELD_KEYS.find((e) => e.field === field);
  if (!entry) return false;
  return entry.envKeys.some((k) => {
    const v = process.env[k];
    return v !== undefined && v.trim() !== '';
  });
}

function parseSavedPayload(raw: unknown): SentinelConfig | null {
  if (!raw || typeof raw !== 'object') return null;
  const o = raw as Record<string, unknown>;
  const num = (key: string): number | undefined => {
    const v = o[key];
    if (typeof v === 'number' && Number.isFinite(v)) return v;
    return undefined;
  };
  const enabled = o['enabled'];
  if (typeof enabled !== 'boolean') return null;
  const runMaxDurationMs = num('runMaxDurationMs');
  const runMaxTokens = num('runMaxTokens');
  const runIdleMs = num('runIdleMs');
  const loopWindow = num('loopWindow');
  const loopMax = num('loopMax');
  if (
    runMaxDurationMs === undefined ||
    runMaxTokens === undefined ||
    runIdleMs === undefined ||
    loopWindow === undefined ||
    loopMax === undefined
  ) {
    return null;
  }
  const runMaxCost = num('runMaxCost');
  return {
    enabled,
    runMaxDurationMs,
    runMaxTokens,
    runIdleMs,
    loopWindow,
    loopMax,
    ...(runMaxCost !== undefined ? { runMaxCost } : {}),
  };
}

export function loadSentinelLimitsFromDisk(): SentinelConfig | null {
  const filePath = getSentinelLimitsFilePath();
  try {
    if (!fs.existsSync(filePath)) return null;
    const text = fs.readFileSync(filePath, 'utf8');
    const parsed = JSON.parse(text) as unknown;
    const inner =
      parsed && typeof parsed === 'object' && 'config' in (parsed as object)
        ? (parsed as { config: unknown }).config
        : parsed;
    return parseSavedPayload(inner);
  } catch {
    return null;
  }
}

export function saveSentinelLimitsToDisk(config: SentinelConfig): void {
  const filePath = getSentinelLimitsFilePath();
  const payload = {
    version: 1,
    updatedAt: new Date().toISOString(),
    config: {
      enabled: config.enabled,
      runMaxDurationMs: config.runMaxDurationMs,
      runMaxTokens: config.runMaxTokens,
      runIdleMs: config.runIdleMs,
      loopWindow: config.loopWindow,
      loopMax: config.loopMax,
      runMaxCost: config.runMaxCost ?? 1.0,
    },
  };
  fs.writeFileSync(filePath, JSON.stringify(payload, null, 2), { mode: 0o600 });
}

/** Merge saved limits from disk into the live Sentinel singleton (env wins per field). */
export function mergePersistedSentinelConfig(sentinel: Sentinel): boolean {
  const saved = loadSentinelLimitsFromDisk();
  if (!saved) return false;

  const current = sentinel.getConfig();
  const merged: SentinelConfig = { ...current };

  for (const { field } of ENV_FIELD_KEYS) {
    if (envDefinesField(field)) continue;
    (merged as any)[field] = saved[field];
  }

  try {
    sentinel.updateConfig(merged);
    return true;
  } catch {
    return false;
  }
}

/**
 * Apply saved limits on Gateway boot. Env-defined fields keep env values.
 */
export function applyPersistedSentinelConfig(sentinel: Sentinel, logger?: {
  info: (msg: string, meta?: Record<string, unknown>) => void;
  warn: (msg: string, meta?: Record<string, unknown>) => void;
}): boolean {
  const filePath = getSentinelLimitsFilePath();
  const saved = loadSentinelLimitsFromDisk();
  
  if (!saved) {
    logger?.info('No Sentinel limits file found on disk (will use env defaults)', {
      expectedPath: filePath,
    });
    return false;
  }

  // Check which env vars are set and will override the disk values
  const envOverrides: string[] = [];
  const diskApplied: string[] = [];
  
  for (const { field, envKeys } of ENV_FIELD_KEYS) {
    if (envDefinesField(field)) {
      const activeEnv = envKeys.find(k => process.env[k] !== undefined && process.env[k]?.trim() !== '');
      if (activeEnv) {
        envOverrides.push(`${field} (via ${activeEnv}=${process.env[activeEnv]})`);
      }
    } else {
      diskApplied.push(`${field}=${saved[field]}`);
    }
  }

  const ok = mergePersistedSentinelConfig(sentinel);
  
  if (ok) {
    const finalConfig = sentinel.getConfig();
    logger?.info('✅ Sentinel limits loaded from disk and merged', {
      diskPath: filePath,
      diskValues: saved,
      envOverrides: envOverrides.length > 0 ? envOverrides : 'none',
      diskAppliedFields: diskApplied.length > 0 ? diskApplied : 'none (all env-overridden)',
      finalActiveConfig: finalConfig,
    });
  } else {
    logger?.warn('Failed to apply Sentinel limits from disk', {
      diskPath: filePath,
      savedConfig: saved,
    });
  }
  
  return ok;
}

/** Call before returning policy config to clients so long-lived Gateway picks up disk saves. */
export function hydrateSentinelConfigFromDisk(sentinel: Sentinel): void {
  mergePersistedSentinelConfig(sentinel);
}

export function persistSentinelConfigAfterApply(
  sentinel: Sentinel,
  config: SentinelConfig,
  logger?: {
    info: (msg: string, meta?: Record<string, unknown>) => void;
  }
): string {
  const filePath = getSentinelLimitsFilePath();
  saveSentinelLimitsToDisk(config);
  
  logger?.info('✅ Sentinel limits saved to disk', {
    path: filePath,
    config: config,
    note: 'Survives Gateway restart. Env vars in docker-compose override individual fields when set.',
  });
  
  return filePath;
}
