// @lifecycle canonical - Utility helpers for reading MCP tool configuration.
/**
 * Configuration Utilities for Safe Config Management
 *
 * Provides atomic config operations with backup/rollback capabilities
 * for secure configuration management in system_control tool.
 */

import { access, copyFile, readFile, writeFile } from 'node:fs/promises';

import { type ConfigManager, type Logger, Config } from '../../shared/types/index.js';

export const CONFIG_VALID_KEYS = [
  'server.name',
  'server.port',
  'server.transport',
  'logging.level',
  'logging.directory',
  'gates.enabled',
  'gates.methodologyGates',
  'execution.judge',
  'methodologies.enabled',
  'methodologies.dynamicToolDescriptions',
  'methodologies.systemPromptFrequency',
  'methodologies.styleGuidance',
  'verification.inContextAttempts',
  'verification.isolation.enabled',
  'verification.isolation.maxBudget',
  'verification.isolation.timeout',
  'analysis.semanticAnalysis.llmIntegration.enabled',
  'analysis.semanticAnalysis.llmIntegration.model',
  'analysis.semanticAnalysis.llmIntegration.maxTokens',
  'analysis.semanticAnalysis.llmIntegration.temperature',
] as const;

export type ConfigKey = (typeof CONFIG_VALID_KEYS)[number];

export const CONFIG_RESTART_REQUIRED_KEYS: ConfigKey[] = [
  'server.port',
  'server.transport',
  'analysis.semanticAnalysis.llmIntegration.enabled',
];

export interface ConfigInputValidationResult {
  valid: boolean;
  error?: string;
  convertedValue?: any;
  valueType?: 'string' | 'number' | 'boolean';
}

export function validateConfigInput(key: string, value: string): ConfigInputValidationResult {
  switch (key) {
    case 'server.port': {
      const port = parseInt(value, 10);
      if (isNaN(port) || port < 1024 || port > 65535) {
        return {
          valid: false,
          error: 'Port must be a number between 1024-65535',
        };
      }
      return { valid: true, convertedValue: port, valueType: 'number' };
    }

    case 'server.name':
    case 'server.version':
    case 'logging.directory': {
      const trimmed = value?.trim();
      if (!trimmed) {
        return {
          valid: false,
          error: 'Value cannot be empty',
        };
      }
      return { valid: true, convertedValue: trimmed, valueType: 'string' };
    }

    case 'server.transport': {
      const normalized = value.trim().toLowerCase();
      if (!['stdio', 'sse', 'both'].includes(normalized)) {
        return {
          valid: false,
          error: "Transport mode must be 'stdio', 'sse', or 'both'",
        };
      }
      return { valid: true, convertedValue: normalized, valueType: 'string' };
    }

    case 'gates.enabled':
    case 'gates.methodologyGates':
    case 'execution.judge':
    case 'methodologies.enabled':
    case 'methodologies.dynamicToolDescriptions':
    case 'methodologies.styleGuidance':
    case 'verification.isolation.enabled': {
      const boolValue = value.trim().toLowerCase();
      if (!['true', 'false'].includes(boolValue)) {
        return {
          valid: false,
          error: "Value must be 'true' or 'false'",
        };
      }
      return {
        valid: true,
        convertedValue: boolValue === 'true',
        valueType: 'boolean',
      };
    }

    case 'methodologies.systemPromptFrequency': {
      const freq = parseInt(value, 10);
      if (isNaN(freq) || freq < 1 || freq > 100) {
        return {
          valid: false,
          error: 'Frequency must be a number between 1-100',
        };
      }
      return { valid: true, convertedValue: freq, valueType: 'number' };
    }

    case 'verification.inContextAttempts': {
      const attempts = parseInt(value, 10);
      if (isNaN(attempts) || attempts < 1 || attempts > 10) {
        return {
          valid: false,
          error: 'In-context attempts must be a number between 1-10',
        };
      }
      return { valid: true, convertedValue: attempts, valueType: 'number' };
    }

    case 'verification.isolation.maxBudget': {
      const budget = parseFloat(value);
      if (isNaN(budget) || budget < 0.01 || budget > 10) {
        return {
          valid: false,
          error: 'Max budget must be a number between 0.01-10',
        };
      }
      return { valid: true, convertedValue: budget, valueType: 'number' };
    }

    case 'verification.isolation.timeout': {
      const timeout = parseInt(value, 10);
      if (isNaN(timeout) || timeout < 30 || timeout > 3600) {
        return {
          valid: false,
          error: 'Timeout must be a number between 30-3600 seconds',
        };
      }
      return { valid: true, convertedValue: timeout, valueType: 'number' };
    }

    case 'logging.level': {
      const normalized = value.trim();
      if (!['debug', 'info', 'warn', 'error'].includes(normalized)) {
        return {
          valid: false,
          error: 'Log level must be: debug, info, warn, or error',
        };
      }
      return { valid: true, convertedValue: normalized, valueType: 'string' };
    }

    case 'analysis.semanticAnalysis.llmIntegration.enabled': {
      const boolValue = value.trim().toLowerCase();
      if (!['true', 'false'].includes(boolValue)) {
        return {
          valid: false,
          error: "Value must be 'true' or 'false'",
        };
      }
      return {
        valid: true,
        convertedValue: boolValue === 'true',
        valueType: 'boolean',
      };
    }

    case 'analysis.semanticAnalysis.llmIntegration.model': {
      const trimmed = value?.trim();
      if (!trimmed) {
        return {
          valid: false,
          error: 'Model name cannot be empty',
        };
      }
      return { valid: true, convertedValue: trimmed, valueType: 'string' };
    }

    case 'analysis.semanticAnalysis.llmIntegration.maxTokens': {
      const tokens = parseInt(value, 10);
      if (isNaN(tokens) || tokens < 1 || tokens > 4000) {
        return {
          valid: false,
          error: 'Max tokens must be a number between 1-4000',
        };
      }
      return { valid: true, convertedValue: tokens, valueType: 'number' };
    }

    case 'analysis.semanticAnalysis.llmIntegration.temperature': {
      const temp = parseFloat(value);
      if (isNaN(temp) || temp < 0 || temp > 2) {
        return {
          valid: false,
          error: 'Temperature must be a number between 0-2',
        };
      }
      return { valid: true, convertedValue: temp, valueType: 'number' };
    }

    default:
      return {
        valid: false,
        error: `Unknown configuration key: ${key}`,
      };
  }
}

/**
 * Configuration write result
 */
export interface ConfigWriteResult {
  success: boolean;
  message: string;
  backupPath?: string;
  error?: string;
  restartRequired?: boolean;
}

/**
 * Configuration backup information
 */
export interface ConfigBackup {
  backupPath: string;
  timestamp: number;
  originalConfig: Config;
}

/**
 * Safe Configuration Writer
 * Provides atomic config operations with automatic backup and rollback
 */
export class SafeConfigWriter {
  private logger: Logger;
  private configManager: ConfigManager;
  private configPath: string;

  constructor(logger: Logger, configManager: ConfigManager, configPath: string) {
    this.logger = logger;
    this.configManager = configManager;
    this.configPath = configPath;
  }

  /**
   * Safely update a configuration value with atomic operations
   */
  async updateConfigValue(
    key: string,
    value: string,
    options?: { createBackup?: boolean }
  ): Promise<ConfigWriteResult> {
    try {
      // Step 1: Validate the operation
      const validation = validateConfigInput(key, value);
      if (!validation.valid) {
        const errorMessage = validation.error ?? 'Unknown validation error';
        return {
          success: false,
          message: `Validation failed: ${errorMessage}`,
          ...(validation.error ? { error: validation.error } : {}),
        };
      }

      // Step 2: Create backup
      const shouldCreateBackup = options?.createBackup !== false;
      const backup = shouldCreateBackup ? await this.createConfigBackup() : undefined;
      if (backup) {
        this.logger.info(`Config backup created: ${backup.backupPath}`);
      }

      // Step 3: Load current config and apply changes
      const currentConfig = this.configManager.getConfig();
      const updatedConfig = this.applyConfigChange(currentConfig, key, validation.convertedValue);

      // Step 4: Validate the entire updated configuration
      const configValidation = this.validateFullConfig(updatedConfig);
      if (!configValidation.valid) {
        return {
          success: false,
          message: `Configuration validation failed: ${configValidation.error}`,
          ...(configValidation.error ? { error: configValidation.error } : {}),
          ...(backup?.backupPath ? { backupPath: backup.backupPath } : {}),
        };
      }

      // Step 5: Write the new configuration atomically
      await this.writeConfigAtomic(updatedConfig);

      // Step 6: Reload ConfigManager to use new config
      await this.configManager.loadConfig();

      return {
        success: true,
        message: `Configuration updated successfully: ${key} = ${value}`,
        ...(backup?.backupPath ? { backupPath: backup.backupPath } : {}),
        restartRequired: this.requiresRestart(key),
      };
    } catch (error) {
      this.logger.error(`Failed to update config ${key}:`, error);
      return {
        success: false,
        message: `Failed to update configuration: ${error}`,
        error: String(error),
      };
    }
  }

  /**
   * Create a timestamped backup of the current configuration
   */
  private async createConfigBackup(): Promise<ConfigBackup> {
    const timestamp = Date.now();
    const backupPath = `${this.configPath}.backup.${timestamp}`;

    try {
      await copyFile(this.configPath, backupPath);
      const originalConfig = this.configManager.getConfig();

      this.logger.debug(`Config backup created: ${backupPath}`);
      return {
        backupPath,
        timestamp,
        originalConfig,
      };
    } catch (error) {
      this.logger.error(`Failed to create config backup:`, error);
      throw new Error(`Backup creation failed: ${error}`);
    }
  }

  /**
   * Restore configuration from backup
   */
  async restoreFromBackup(backupPath: string): Promise<ConfigWriteResult> {
    try {
      // Verify backup exists
      await access(backupPath);

      // Restore the backup
      await copyFile(backupPath, this.configPath);

      // Reload configuration
      await this.configManager.loadConfig();

      this.logger.info(`Configuration restored from backup: ${backupPath}`);

      return {
        success: true,
        message: `Configuration successfully restored from backup`,
      };
    } catch (error) {
      this.logger.error(`Failed to restore from backup ${backupPath}:`, error);
      return {
        success: false,
        message: `Failed to restore configuration: ${error}`,
        error: String(error),
      };
    }
  }

  /**
   * Write configuration file atomically (write to temp file, then rename)
   */
  private async writeConfigAtomic(config: Config): Promise<void> {
    const tempPath = `${this.configPath}.tmp`;

    try {
      // Write to temporary file first
      const configJson = JSON.stringify(config, null, 2);
      await writeFile(tempPath, configJson, 'utf8');

      // Validate the written file can be parsed
      const testContent = await readFile(tempPath, 'utf8');
      JSON.parse(testContent); // Will throw if invalid

      // Atomic rename (this is the atomic operation)
      const fs = await import('fs');
      fs.renameSync(tempPath, this.configPath);

      this.logger.debug('Configuration written atomically');
    } catch (error) {
      // Clean up temp file if it exists
      try {
        const fs = await import('fs');
        if (fs.existsSync(tempPath)) {
          fs.unlinkSync(tempPath);
        }
      } catch (cleanupError) {
        this.logger.warn(`Failed to clean up temp file ${tempPath}:`, cleanupError);
      }
      throw error;
    }
  }

  /**
   * Apply a configuration change to a config object
   */
  private applyConfigChange(config: Config, key: string, value: any): Config {
    // Deep clone the config to avoid mutations
    const newConfig = JSON.parse(JSON.stringify(config));

    // Apply the change using dot notation
    const parts = key.split('.');
    let current: any = newConfig;

    // Navigate to the parent object
    for (let i = 0; i < parts.length - 1; i++) {
      const part = parts[i];
      if (!part) {
        continue;
      }
      if (!current[part]) {
        current[part] = {};
      }
      current = current[part];
    }

    // Set the final value
    const finalKey = parts[parts.length - 1];
    if (finalKey) {
      current[finalKey] = value;
    }

    return newConfig as Config;
  }

  /**
   * Validate the entire configuration object
   */
  private validateFullConfig(config: Config): {
    valid: boolean;
    error?: string;
  } {
    try {
      // Basic structure validation
      if (!config.server) {
        return {
          valid: false,
          error: 'Missing required server configuration section',
        };
      }

      // Server validation
      if (!config.server.name || !config.server.version || !config.server.port) {
        return { valid: false, error: 'Missing required server configuration' };
      }

      if (config.server.port < 1024 || config.server.port > 65535) {
        return { valid: false, error: 'Invalid server port range' };
      }

      // Transport validation
      if (config.transport && !['stdio', 'sse', 'both'].includes(config.transport)) {
        return {
          valid: false,
          error: "Invalid transport mode (must be 'stdio', 'sse', or 'both')",
        };
      }

      // Logging validation (if present)
      if (config.logging) {
        if (!config.logging.directory || !config.logging.level) {
          return {
            valid: false,
            error: 'Missing required logging configuration',
          };
        }

        if (!['debug', 'info', 'warn', 'error'].includes(config.logging.level)) {
          return { valid: false, error: 'Invalid logging level' };
        }
      }

      return { valid: true };
    } catch (error) {
      return {
        valid: false,
        error: `Configuration validation error: ${error}`,
      };
    }
  }

  /**
   * Check if a configuration key requires server restart
   */
  private requiresRestart(key: string): boolean {
    return CONFIG_RESTART_REQUIRED_KEYS.includes(key as ConfigKey);
  }

  /**
   * Get the configuration file path for debugging/info purposes
   */
  getConfigPath(): string {
    return this.configPath;
  }
}

/**
 * Create a SafeConfigWriter instance
 */
export function createSafeConfigWriter(
  logger: Logger,
  configManager: ConfigManager,
  configPath: string
): SafeConfigWriter {
  return new SafeConfigWriter(logger, configManager, configPath);
}
