const fs = require('fs').promises;
const path = require('path');
const { REQUIRED_DIRS } = require('../shared/constants');

// Import crypto utilities for secure credential storage
let cryptoUtils;
try {
  // Try to load from dashcaddy-api if available
  cryptoUtils = require('../../../src/security/crypto-utils');
} catch {
  // Fallback: create minimal crypto implementation
  const crypto = require('crypto');
  const ALGORITHM = 'aes-256-gcm';
  const KEY_LENGTH = 32;
  const IV_LENGTH = 16;
  
  let encryptionKey = null;
  
  function loadOrCreateKey() {
    if (encryptionKey) return encryptionKey;
    if (process.env.DASHCADDY_ENCRYPTION_KEY) {
      encryptionKey = Buffer.from(process.env.DASHCADDY_ENCRYPTION_KEY, 'hex');
      return encryptionKey;
    }
    encryptionKey = crypto.randomBytes(KEY_LENGTH);
    console.warn('[ConfigManager] Using ephemeral encryption key - credentials will not persist across restarts');
    return encryptionKey;
  }
  
  cryptoUtils = {
    encrypt: (data) => {
      const key = loadOrCreateKey();
      const iv = crypto.randomBytes(IV_LENGTH);
      const plaintext = typeof data === 'object' ? JSON.stringify(data) : String(data);
      const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
      let encrypted = cipher.update(plaintext, 'utf8', 'base64');
      encrypted += cipher.final('base64');
      const authTag = cipher.getAuthTag();
      return `${iv.toString('base64')}:${authTag.toString('base64')}:${encrypted}`;
    },
    decrypt: (encryptedData) => {
      const key = loadOrCreateKey();
      const parts = encryptedData.split(':');
      if (parts.length !== 3) throw new Error('Invalid encrypted data format');
      const iv = Buffer.from(parts[0], 'base64');
      const authTag = Buffer.from(parts[1], 'base64');
      const ciphertext = parts[2];
      const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
      decipher.setAuthTag(authTag);
      let decrypted = decipher.update(ciphertext, 'base64', 'utf8');
      decrypted += decipher.final('utf8');
      return decrypted;
    },
    isEncrypted: (data) => {
      if (typeof data !== 'string') return false;
      const parts = data.split(':');
      return parts.length === 3;
    }
  };
}

class ConfigManager {
  /**
   * Saves installation configuration to disk
   * @param {Object} config - Configuration object
   * @param {string} installPath - Installation directory path
   * @returns {Promise<Object>} { success: boolean, path: string }
   */
  async saveConfig(config, installPath) {
    try {
      const configPath = path.join(installPath, 'config.json');

      // Add metadata
      const configWithMetadata = {
        ...config,
        version: config.version || '1.0.0',
        lastModified: new Date().toISOString()
      };
      
      // Write config file
      await fs.writeFile(
        configPath,
        JSON.stringify(configWithMetadata, null, 2),
        'utf8'
      );
      
      return {
        success: true,
        path: configPath
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }

  /**
   * Loads installation configuration from disk
   * @param {string} installPath - Installation directory path
   * @returns {Promise<Object>} { config: object, exists: boolean }
   */
  async loadConfig(installPath) {
    try {
      const configPath = path.join(installPath, 'config.json');

      // Check if config exists
      try {
        await fs.access(configPath);
      } catch {
        return {
          config: null,
          exists: false
        };
      }
      
      // Read and parse config
      const configData = await fs.readFile(configPath, 'utf8');
      const config = JSON.parse(configData);
      
      return {
        config: config,
        exists: true
      };
    } catch (error) {
      return {
        config: null,
        exists: false,
        error: error.message
      };
    }
  }

  /**
   * Creates required directory structure
   * @param {string} installPath - Installation directory path
   * @returns {Promise<Object>} { success: boolean, paths: array }
   */
  async createDirectories(installPath) {
    try {
      const createdPaths = [];
      
      // Create main installation directory
      await fs.mkdir(installPath, { recursive: true });
      createdPaths.push(installPath);
      
      // Create required subdirectories
      for (const dir of REQUIRED_DIRS) {
        const dirPath = path.join(installPath, dir);
        await fs.mkdir(dirPath, { recursive: true });
        createdPaths.push(dirPath);
      }
      
      return {
        success: true,
        paths: createdPaths
      };
    } catch (error) {
      return {
        success: false,
        error: error.message,
        paths: []
      };
    }
  }

  /**
   * Validates that a path is writable
   * @param {string} testPath - Path to validate
   * @returns {Promise<Object>} { valid: boolean, message: string }
   */
  async validatePath(testPath) {
    try {
      // Try to create the directory if it doesn't exist
      await fs.mkdir(testPath, { recursive: true });
      
      // Try to write a test file
      const testFile = path.join(testPath, '.dashcaddy-test');
      await fs.writeFile(testFile, 'test', 'utf8');
      
      // Clean up test file
      await fs.unlink(testFile);
      
      return {
        valid: true,
        message: 'Path is writable'
      };
    } catch (error) {
      let message = 'Path is not writable';
      
      if (error.code === 'EACCES') {
        message = 'Permission denied. Please choose a different location or run as administrator.';
      } else if (error.code === 'ENOENT') {
        message = 'Path does not exist and cannot be created.';
      } else if (error.code === 'ENOSPC') {
        message = 'Not enough disk space.';
      }
      
      return {
        valid: false,
        message: message,
        error: error.message
      };
    }
  }

  /**
   * Checks if an installation exists at the given path
   * @param {string} installPath - Path to check
   * @returns {Promise<boolean>} True if installation exists
   */
  async installationExists(installPath) {
    try {
      const configPath = path.join(installPath, 'config.json');
      await fs.access(configPath);
      return true;
    } catch {
      return false;
    }
  }

  /**
   * Gets disk space information for a path
   * @param {string} testPath - Path to check
   * @returns {Promise<Object>} Disk space info
   */
  async getDiskSpace(testPath) {
    try {
      const fsPromises = require('fs').promises;
      const pathModule = require('path');

      // Ensure directory exists
      await fsPromises.mkdir(testPath, { recursive: true });

      // Use statfs for true disk space (works on all filesystems: ext4, Btrfs, XFS, ZFS, APFS, NTFS)
      const stats = await fsPromises.statfs(testPath);

      const totalBytes = stats.blocks * stats.bsize;
      const freeBytes = stats.bfree * stats.bsize;
      const availableBytes = stats.bavail * stats.bsize; // Available to non-root users
      const usedBytes = totalBytes - freeBytes;

      return {
        available: true,
        path: testPath,
        total: totalBytes,
        used: usedBytes,
        free: freeBytes,
        availableBytes: availableBytes,
        usagePercent: parseFloat(((usedBytes / totalBytes) * 100).toFixed(2))
      };
    } catch (error) {
      return {
        available: false,
        path: testPath,
        error: error.message
      };
    }
  }

  /**
   * Saves DNS credentials securely with encryption
   * @param {Object} credentials - DNS credentials
   * @param {string} installPath - Installation directory path
   * @returns {Promise<Object>} { success: boolean, path: string }
   */
  async saveDNSCredentials(credentials, installPath) {
    try {
      const credPath = path.join(installPath, 'dns-credentials.json');

      // Encrypt sensitive fields
      const credentialsToSave = {
        server: credentials.server,
        username: credentials.username,
        // Encrypt password and token
        password: credentials.password ? cryptoUtils.encrypt(credentials.password) : null,
        token: credentials.token ? cryptoUtils.encrypt(credentials.token) : null,
        apiKey: credentials.apiKey ? cryptoUtils.encrypt(credentials.apiKey) : null,
        _encrypted: true,
        _encryptedFields: ['password', 'token', 'apiKey'],
        savedAt: new Date().toISOString()
      };
      
      await fs.writeFile(
        credPath,
        JSON.stringify(credentialsToSave, null, 2),
        'utf8'
      );
      
      // Set restrictive permissions (Unix-like systems)
      try {
        await fs.chmod(credPath, 0o600); // Read/write for owner only
      } catch {
        // Windows doesn't support chmod, that's okay
      }
      
      console.log('[ConfigManager] DNS credentials saved with encryption');
      
      return {
        success: true,
        path: credPath
      };
    } catch (error) {
      console.error('[ConfigManager] Failed to save DNS credentials:', error.message);
      return {
        success: false,
        error: error.message
      };
    }
  }

  /**
   * Loads DNS credentials and decrypts them
   * @param {string} installPath - Installation directory path
   * @returns {Promise<Object>} { credentials: object, exists: boolean }
   */
  async loadDNSCredentials(installPath) {
    try {
      const credPath = path.join(installPath, 'dns-credentials.json');

      // Check if credentials exist
      try {
        await fs.access(credPath);
      } catch {
        return {
          credentials: null,
          exists: false
        };
      }
      
      // Read and parse credentials
      const credData = await fs.readFile(credPath, 'utf8');
      const credentials = JSON.parse(credData);
      
      // Decrypt sensitive fields if encrypted
      if (credentials._encrypted) {
        const decrypted = {
          server: credentials.server,
          username: credentials.username,
          password: credentials.password && cryptoUtils.isEncrypted(credentials.password) 
            ? cryptoUtils.decrypt(credentials.password) 
            : credentials.password,
          token: credentials.token && cryptoUtils.isEncrypted(credentials.token)
            ? cryptoUtils.decrypt(credentials.token)
            : credentials.token,
          apiKey: credentials.apiKey && cryptoUtils.isEncrypted(credentials.apiKey)
            ? cryptoUtils.decrypt(credentials.apiKey)
            : credentials.apiKey,
          savedAt: credentials.savedAt
        };
        
        console.log('[ConfigManager] DNS credentials loaded and decrypted');
        
        return {
          credentials: decrypted,
          exists: true
        };
      }
      
      // Legacy plaintext credentials - migrate on next save
      console.warn('[ConfigManager] Found plaintext DNS credentials - will encrypt on next save');
      
      return {
        credentials: credentials,
        exists: true,
        needsMigration: true
      };
    } catch (error) {
      console.error('[ConfigManager] Failed to load DNS credentials:', error.message);
      return {
        credentials: null,
        exists: false,
        error: error.message
      };
    }
  }

  /**
   * Files that constitute "user settings" — preserved on uninstall if requested.
   * These are config/credential files the user would want to keep for a reinstall.
   */
  static get USER_SETTINGS_FILES() {
    return [
      'config.json',
      'dns-credentials.json',
      'credentials.json',
      'branding.json',
      'services.json',
      '.encryption-key',
      'notifications.json',
      'backup-config.json'
    ];
  }

  /**
   * CA certificate paths (relative to installPath) to preserve on uninstall.
   * Caddy's built-in PKI stores root + intermediate certs and keys here.
   * Regenerating these would invalidate every device that trusted the old root.
   */
  static get CA_CERT_PATHS() {
    return [
      'certs/pki/authorities/local/root.crt',
      'certs/pki/authorities/local/root.key',
      'certs/pki/authorities/local/intermediate.crt',
      'certs/pki/authorities/local/intermediate.key'
    ];
  }

  /**
   * Copies a list of files (relative paths) from installPath into backupDir,
   * recreating subdirectory structure as needed.
   * @param {string} installPath - Source root
   * @param {string} backupDir - Destination root
   * @param {string[]} relativePaths - Files to copy
   * @returns {Promise<string[]>} List of files that were actually copied
   */
  async _copyFilesToBackup(installPath, backupDir, relativePaths) {
    const copied = [];
    for (const relPath of relativePaths) {
      const src = path.join(installPath, relPath);
      const dest = path.join(backupDir, relPath);
      try {
        await fs.access(src);
        await fs.mkdir(path.dirname(dest), { recursive: true });
        await fs.copyFile(src, dest);
        copied.push(relPath);
      } catch {
        // File doesn't exist, skip
      }
    }
    return copied;
  }

  /**
   * Removes all installation files and directories
   * @param {string} installPath - Installation directory path
   * @param {Object} options - Uninstall options
   * @param {boolean} options.preserveSettings - Keep user config/credentials for reinstall
   * @param {boolean} options.preserveCA - Keep CA certificates (root + intermediate keys)
   * @returns {Promise<Object>} { success: boolean, message: string, settingsPath?: string }
   */
  async removeInstallation(installPath, options = {}) {
    try {
      // Verify this is a DashCaddy installation before removing
      const exists = await this.installationExists(installPath);

      if (!exists) {
        return {
          success: false,
          message: 'No DashCaddy installation found at this path'
        };
      }

      const needsBackup = options.preserveSettings || options.preserveCA;
      let settingsBackupPath = null;

      if (needsBackup) {
        // Back up to a temp location outside the install dir so rm -rf doesn't eat it
        const os = require('os');
        const tempBackupDir = path.join(os.tmpdir(), 'dashcaddy-settings-backup');
        // Clean any previous leftover
        try { await fs.rm(tempBackupDir, { recursive: true, force: true }); } catch {}
        await fs.mkdir(tempBackupDir, { recursive: true });

        if (options.preserveSettings) {
          await this._copyFilesToBackup(installPath, tempBackupDir, ConfigManager.USER_SETTINGS_FILES);

          // Also grab custom logos
          const assetsDir = path.join(installPath, 'sites', 'status', 'assets');
          try {
            const entries = await fs.readdir(assetsDir);
            const customLogos = entries.filter(f => f.startsWith('custom-logo'));
            await this._copyFilesToBackup(
              installPath,
              tempBackupDir,
              customLogos.map(f => path.join('sites', 'status', 'assets', f))
            );
          } catch {}
        }

        if (options.preserveCA) {
          await this._copyFilesToBackup(installPath, tempBackupDir, ConfigManager.CA_CERT_PATHS);
        }

        // Nuke the install directory
        await fs.rm(installPath, { recursive: true, force: true });

        // Recreate install dir and move preserved files back
        const settingsDir = path.join(installPath, '.dashcaddy-settings');
        await fs.mkdir(settingsDir, { recursive: true });

        // Recursively copy everything from temp back
        await this._restoreFromTemp(tempBackupDir, settingsDir);

        // Clean up temp
        await fs.rm(tempBackupDir, { recursive: true, force: true });
        settingsBackupPath = settingsDir;
      } else {
        // Remove everything
        await fs.rm(installPath, { recursive: true, force: true });
      }

      const parts = [];
      if (options.preserveSettings) parts.push('settings');
      if (options.preserveCA) parts.push('CA certificates');

      return {
        success: true,
        message: parts.length > 0
          ? `Installation removed. Preserved: ${parts.join(' and ')}.`
          : 'Installation removed successfully',
        settingsPath: settingsBackupPath
      };
    } catch (error) {
      return {
        success: false,
        message: 'Failed to remove installation',
        error: error.message
      };
    }
  }

  /**
   * Recursively copies all files from src directory to dest directory
   * @param {string} src - Source directory
   * @param {string} dest - Destination directory
   */
  async _restoreFromTemp(src, dest) {
    const entries = await fs.readdir(src, { withFileTypes: true });
    for (const entry of entries) {
      const srcPath = path.join(src, entry.name);
      const destPath = path.join(dest, entry.name);
      if (entry.isDirectory()) {
        await fs.mkdir(destPath, { recursive: true });
        await this._restoreFromTemp(srcPath, destPath);
      } else {
        await fs.mkdir(path.dirname(destPath), { recursive: true });
        await fs.copyFile(srcPath, destPath);
      }
    }
  }

  /**
   * Restores user settings and CA certs from a previous uninstall.
   * The backup in .dashcaddy-settings mirrors the original directory structure.
   * @param {string} installPath - Installation directory path
   * @returns {Promise<Object>} { success: boolean, restored: string[] }
   */
  async restoreUserSettings(installPath) {
    try {
      const settingsDir = path.join(installPath, '.dashcaddy-settings');
      try {
        await fs.access(settingsDir);
      } catch {
        return { success: false, restored: [], message: 'No saved settings found' };
      }

      const restored = [];

      // Recursively restore all files back to installPath
      const restoreDir = async (srcDir, destDir) => {
        const entries = await fs.readdir(srcDir, { withFileTypes: true });
        for (const entry of entries) {
          const srcPath = path.join(srcDir, entry.name);
          const destPath = path.join(destDir, entry.name);
          if (entry.isDirectory()) {
            await fs.mkdir(destPath, { recursive: true });
            await restoreDir(srcPath, destPath);
          } else {
            await fs.mkdir(path.dirname(destPath), { recursive: true });
            await fs.copyFile(srcPath, destPath);
            restored.push(path.relative(installPath, destPath));
          }
        }
      };

      await restoreDir(settingsDir, installPath);

      // Clean up the settings backup dir
      await fs.rm(settingsDir, { recursive: true, force: true });

      return { success: true, restored };
    } catch (error) {
      return { success: false, restored: [], error: error.message };
    }
  }

  /**
   * Lists all files in the installation directory
   * @param {string} installPath - Installation directory path
   * @returns {Promise<Array>} List of files
   */
  async listInstallationFiles(installPath) {
    try {
      const files = [];

      async function scanDirectory(dir) {
        const entries = await fs.readdir(dir, { withFileTypes: true });

        for (const entry of entries) {
          const fullPath = path.join(dir, entry.name);

          if (entry.isDirectory()) {
            await scanDirectory(fullPath);
          } else {
            files.push(fullPath);
          }
        }
      }

      await scanDirectory(installPath);
      return files;
    } catch (error) {
      return [];
    }
  }

  /**
   * Saves branding configuration
   * @param {Object} branding - Branding configuration
   * @param {string} installPath - Installation directory path
   * @returns {Promise<Object>} { success: boolean }
   */
  async saveBranding(branding, installPath) {
    try {
      const brandingPath = path.join(installPath, 'branding.json');
      const assetsPath = path.join(installPath, 'sites', 'status', 'assets');

      // Ensure directories exist
      await fs.mkdir(path.dirname(brandingPath), { recursive: true });
      await fs.mkdir(assetsPath, { recursive: true });

      // If a logo source path is provided, copy it to assets
      if (branding.logoSourcePath) {
        const logoExt = path.extname(branding.logoSourcePath);
        const logoDestName = `custom-logo${logoExt}`;
        const logoDestPath = path.join(assetsPath, logoDestName);

        await fs.copyFile(branding.logoSourcePath, logoDestPath);
        branding.logoUrl = `/assets/${logoDestName}`;
        delete branding.logoSourcePath; // Don't save source path
      }

      // Default branding values
      const brandingConfig = {
        name: branding.name || 'DashCaddy',
        title: branding.title || 'DashCaddy Dashboard',
        logoUrl: branding.logoUrl || '/assets/dashcaddy-logo.png',
        faviconUrl: branding.faviconUrl || '/assets/favicon.ico',
        primaryColor: branding.primaryColor || '#6366f1',
        showWatermark: branding.showWatermark !== false,
        updatedAt: new Date().toISOString()
      };

      await fs.writeFile(
        brandingPath,
        JSON.stringify(brandingConfig, null, 2),
        'utf8'
      );

      return {
        success: true,
        path: brandingPath,
        branding: brandingConfig
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }

  /**
   * Loads branding configuration
   * @param {string} installPath - Installation directory path
   * @returns {Promise<Object>} { branding: object, exists: boolean }
   */
  async loadBranding(installPath) {
    try {
      const brandingPath = path.join(installPath, 'branding.json');

      try {
        await fs.access(brandingPath);
      } catch {
        // Return default branding if file doesn't exist
        return {
          branding: {
            name: 'DashCaddy',
            title: 'DashCaddy Dashboard',
            logoUrl: '/assets/dashcaddy-logo.png',
            faviconUrl: '/assets/favicon.ico',
            primaryColor: '#6366f1',
            showWatermark: true
          },
          exists: false
        };
      }

      const brandingData = await fs.readFile(brandingPath, 'utf8');
      const branding = JSON.parse(brandingData);

      return {
        branding,
        exists: true
      };
    } catch (error) {
      return {
        branding: null,
        exists: false,
        error: error.message
      };
    }
  }

  /**
   * Creates seed JSON files that DashCaddy needs to start.
   * Called after createDirectories() during installation.
   * @param {string} installPath - Installation directory path
   * @returns {Promise<Object>} { success: boolean, files: string[] }
   */
  async seedFiles(installPath) {
    try {
      const crypto = require('crypto');
      const createdFiles = [];

      const seedData = {
        'services.json': '[]',
        'dns-credentials.json': '{}',
        'credentials.json': '{}',
        'notifications.json': '{}'
      };

      for (const [filename, content] of Object.entries(seedData)) {
        const filePath = path.join(installPath, filename);
        // Only create if doesn't already exist
        try {
          await fs.access(filePath);
        } catch {
          await fs.writeFile(filePath, content, 'utf8');
          createdFiles.push(filename);
        }
      }

      // Create .encryption-key (random 32-byte hex) if missing
      const keyPath = path.join(installPath, '.encryption-key');
      try {
        await fs.access(keyPath);
      } catch {
        const key = crypto.randomBytes(32).toString('hex');
        await fs.writeFile(keyPath, key, 'utf8');
        // Restrict permissions on key file
        try { await fs.chmod(keyPath, 0o600); } catch {}
        createdFiles.push('.encryption-key');
      }

      return { success: true, files: createdFiles };
    } catch (error) {
      return { success: false, error: error.message };
    }
  }

  /**
   * Copies default DashCaddy logo to assets folder
   * @param {string} installPath - Installation directory path
   * @param {string} sourceLogo - Path to source logo file
   * @returns {Promise<Object>} { success: boolean }
   */
  async setupDefaultBranding(installPath, sourceLogo) {
    try {
      const assetsPath = path.join(installPath, 'sites', 'status', 'assets');
      await fs.mkdir(assetsPath, { recursive: true });

      // If source logo provided, copy it
      if (sourceLogo) {
        const logoDestPath = path.join(assetsPath, 'dashcaddy-logo.png');
        await fs.copyFile(sourceLogo, logoDestPath);
      }

      // Create default branding config
      await this.saveBranding({
        name: 'DashCaddy',
        title: 'DashCaddy Dashboard',
        logoUrl: '/assets/dashcaddy-logo.png'
      }, installPath);

      return { success: true };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
}

module.exports = ConfigManager;
