const fsp = require('fs').promises;
const fs = require('fs');
const path = require('path');
const { CADDY } = require('../../src/utilities/constants');
const { exists } = require('../../src/utilities/fs-helpers');
const { ValidationError, AuthenticationError } = require('../../src/utilities/errors');
const platformPaths = require('../../platform-paths');
const { ok } = require('../../src/utils/responses');

/**
 * Config backup routes factory
 * @param {Object} deps - Explicit dependencies
 * @returns {express.Router}
 */
module.exports = function(deps) {
  const express = require('express');
  const router = express.Router();

  // Extract dependencies
  const { 
    configStateManager, servicesStateManager, asyncHandler, log,
    SERVICES_FILE, CONFIG_FILE, TOTP_CONFIG_FILE, TAILSCALE_CONFIG_FILE, NOTIFICATIONS_FILE,
    caddy, dns, fetchT, totpConfig, credentialManager, loadSiteConfig, loadNotificationConfig, session, saveTotpConfig
  } = deps;
  
  // Create ctx-like object for compatibility with existing code
  const ctx = {
    SERVICES_FILE, CONFIG_FILE, TOTP_CONFIG_FILE, TAILSCALE_CONFIG_FILE, NOTIFICATIONS_FILE,
    caddy, dns, fetchT, totpConfig, credentialManager, loadSiteConfig, loadNotificationConfig
  };

  const THEMES_DIR = process.env.THEMES_DIR || path.join(path.dirname(SERVICES_FILE), 'themes');

  function readAllThemes() {
    const themes = {};
    try {
      if (!fs.existsSync(THEMES_DIR)) return themes;
      const files = fs.readdirSync(THEMES_DIR).filter(f => f.endsWith('.json'));
      for (const file of files) {
        const slug = path.basename(file, '.json');
        themes[slug] = JSON.parse(fs.readFileSync(path.join(THEMES_DIR, file), 'utf8'));
      }
    } catch (e) { /* ignore */ }
    return themes;
  }

  // ===== BACKUP/RESTORE ENDPOINTS =====
  // Unified v2.0 backup — server config + encryption key + themes (browser state added client-side)

  // Export all configuration as a downloadable JSON bundle
  router.get('/backup/export', asyncHandler(async (req, res) => {
    const backup = {
      version: '2.0',
      exportedAt: new Date().toISOString(),
      dashcaddyVersion: '1.0.0',
      files: {},
      themes: {},
      assets: {}
    };

    // Collect all configuration files (encryption key now included for self-contained restore)
    const ENCRYPTION_KEY_FILE = process.env.ENCRYPTION_KEY_FILE || path.join(path.dirname(ctx.SERVICES_FILE), '.encryption-key');
    const filesToBackup = [
      { key: 'services', path: ctx.SERVICES_FILE, required: true },
      { key: 'caddyfile', path: ctx.caddy.filePath, required: true },
      { key: 'config', path: ctx.CONFIG_FILE, required: false },
      { key: 'dnsCredentials', path: ctx.dns.credentialsFile, required: false },
      { key: 'credentials', path: process.env.CREDENTIALS_FILE || path.join(__dirname, '..', '..', 'credentials.json'), required: false },
      { key: 'encryptionKey', path: ENCRYPTION_KEY_FILE, required: false },
      { key: 'totpConfig', path: ctx.TOTP_CONFIG_FILE, required: false },
      { key: 'tailscaleConfig', path: ctx.TAILSCALE_CONFIG_FILE, required: false },
      { key: 'notifications', path: ctx.NOTIFICATIONS_FILE, required: false }
    ];

    for (const file of filesToBackup) {
      try {
        if (await exists(file.path)) {
          const content = await fsp.readFile(file.path, 'utf8');
          // Try to parse as JSON, otherwise store as raw string
          try {
            backup.files[file.key] = {
              type: 'json',
              data: JSON.parse(content)
            };
          } catch {
            backup.files[file.key] = {
              type: 'text',
              data: content
            };
          }
        } else if (file.required) {
          backup.files[file.key] = { type: 'missing', data: null };
        }
      } catch (e) {
        log.warn('backup', `Could not backup ${file.key}`, { error: e.message });
      }
    }

    // Include TOTP QR code for authenticator app recovery
    if (ctx.totpConfig.isSetUp) {
      try {
        const secret = await ctx.credentialManager.retrieve('totp.secret');
        if (secret) {
          const { authenticator } = require('otplib');
          const QRCode = require('qrcode');
          const otpauth = authenticator.keyuri('user', 'DashCaddy', secret);
          const qrDataUrl = await QRCode.toDataURL(otpauth, {
            width: 256, margin: 2,
            color: { dark: '#000000', light: '#ffffff' }
          });
          backup.totp = { qrCode: qrDataUrl, issuer: 'DashCaddy' };
        }
      } catch (e) {
        log.warn('backup', 'Could not include TOTP QR in backup', { error: e.message });
      }
    }

    // Include custom assets (logo, favicon) as base64
    try {
      const assetsDir = platformPaths.resolveAssetsPath(process.env.ASSETS_DIR);
      const configData = backup.files.config?.data || {};
      const assetFiles = [configData.customLogo, configData.customFavicon]
        .filter(Boolean)
        .map(p => p.replace(/^\/assets\//, ''));
      for (const assetName of assetFiles) {
        const assetPath = path.join(assetsDir, assetName);
        if (await exists(assetPath)) {
          const data = await fsp.readFile(assetPath);
          backup.assets[assetName] = data.toString('base64');
        }
      }
    } catch (e) {
      log.warn('backup', 'Could not include assets in backup', { error: e.message });
    }

    // Include user-created themes
    try {
      backup.themes = readAllThemes();
    } catch (e) {
      log.warn('backup', 'Could not include themes in backup', { error: e.message });
    }

    // Set headers for file download
    const backupFilename = `dashcaddy-backup-${new Date().toISOString().split('T')[0]}.json`;
    res.setHeader('Content-Type', 'application/json');
    res.setHeader('Content-Disposition', `attachment; filename="${backupFilename}"`);

    res.json(backup);
    log.info('backup', 'Backup exported successfully');
  }, 'backup-export'));

  // Preview what will be restored (without making changes)
  router.post('/backup/preview', asyncHandler(async (req, res) => {
    const backup = req.body;

    if (!backup || !backup.version || !backup.files) {
      throw new ValidationError('Invalid backup file format');
    }

    const preview = {
      valid: true,
      version: backup.version,
      exportedAt: backup.exportedAt,
      files: {}
    };

    // Check each file in the backup
    const ENCRYPTION_KEY_FILE = process.env.ENCRYPTION_KEY_FILE || path.join(path.dirname(ctx.SERVICES_FILE), '.encryption-key');
    const fileMapping = {
      services: { path: ctx.SERVICES_FILE, description: 'Services list' },
      caddyfile: { path: ctx.caddy.filePath, description: 'Caddy configuration' },
      config: { path: ctx.CONFIG_FILE, description: 'DashCaddy settings' },
      dnsCredentials: { path: ctx.dns.credentialsFile, description: 'DNS credentials (legacy)' },
      credentials: { path: process.env.CREDENTIALS_FILE || path.join(__dirname, '..', '..', 'credentials.json'), description: 'Encrypted credentials' },
      encryptionKey: { path: ENCRYPTION_KEY_FILE, description: 'Encryption key (for credentials)' },
      totpConfig: { path: ctx.TOTP_CONFIG_FILE, description: 'TOTP authentication config' },
      tailscaleConfig: { path: ctx.TAILSCALE_CONFIG_FILE, description: 'Tailscale config' },
      notifications: { path: ctx.NOTIFICATIONS_FILE, description: 'Notification settings' }
    };

    for (const [key, value] of Object.entries(backup.files)) {
      if (value && value.type !== 'missing') {
        const mapping = fileMapping[key];
        const currentExists = mapping ? await exists(mapping.path) : false;

        preview.files[key] = {
          description: mapping?.description || key,
          inBackup: true,
          currentExists,
          action: currentExists ? 'overwrite' : 'create',
          type: value.type
        };
      }
    }

    // Count services if present
    if (backup.files.services?.data) {
      const services = Array.isArray(backup.files.services.data)
        ? backup.files.services.data
        : backup.files.services.data.services || [];
      preview.serviceCount = services.length;
    }

    // Count themes if present
    if (backup.themes && typeof backup.themes === 'object') {
      preview.themeCount = Object.keys(backup.themes).length;
    }

    // Count browser state items if present
    if (backup.browserState && typeof backup.browserState === 'object') {
      preview.browserStateCount = Object.keys(backup.browserState).length;
    }

    ok(res, { preview });
  }, 'backup-preview'));

  // Restore configuration from backup
  router.post('/backup/restore', asyncHandler(async (req, res) => {
    const { backup, options = {}, totpCode } = req.body;

    if (!backup || !backup.version || !backup.files) {
      throw new ValidationError('Invalid backup file format');
    }

    // Require TOTP verification for restores that include security-sensitive files
    const sensitiveKeys = ['credentials', 'totpConfig', 'encryptionKey'];
    const restoresSensitive = sensitiveKeys.some(key =>
      backup.files[key] && backup.files[key].type !== 'missing' && !(options.skip || []).includes(key)
    );
    if (restoresSensitive && ctx.totpConfig.enabled && ctx.totpConfig.isSetUp) {
      if (!totpCode || !/^\d{6}$/.test(totpCode)) {
        throw new ValidationError('TOTP code required for restoring security-sensitive files');
      }
      const { authenticator } = require('otplib');
      const secret = await ctx.credentialManager.retrieve('totp.secret');
      if (secret) {
        authenticator.options = { window: 1 };
        if (!authenticator.verify({ token: totpCode, secret })) {
          throw new AuthenticationError('[DC-111] Invalid TOTP code');
        }
      }
    }

    const results = {
      restored: [],
      skipped: [],
      errors: []
    };

    const ENCRYPTION_KEY_FILE = process.env.ENCRYPTION_KEY_FILE || path.join(path.dirname(ctx.SERVICES_FILE), '.encryption-key');
    const fileMapping = {
      services: ctx.SERVICES_FILE,
      caddyfile: ctx.caddy.filePath,
      config: ctx.CONFIG_FILE,
      dnsCredentials: ctx.dns.credentialsFile,
      credentials: process.env.CREDENTIALS_FILE || path.join(__dirname, '..', '..', 'credentials.json'),
      encryptionKey: ENCRYPTION_KEY_FILE,
      totpConfig: ctx.TOTP_CONFIG_FILE,
      tailscaleConfig: ctx.TAILSCALE_CONFIG_FILE,
      notifications: ctx.NOTIFICATIONS_FILE
    };

    // Restore each file
    for (const [key, value] of Object.entries(backup.files)) {
      if (!value || value.type === 'missing') {
        continue;
      }

      // Skip if user chose to skip certain files
      if (options.skip && options.skip.includes(key)) {
        results.skipped.push(key);
        continue;
      }

      const filePath = fileMapping[key];
      if (!filePath) {
        results.errors.push({ file: key, error: 'Unknown file type' });
        continue;
      }

      try {
        let content;
        if (value.type === 'json') {
          content = JSON.stringify(value.data, null, 2);
        } else {
          content = value.data;
        }

        // Create backup of existing file before overwriting
        if (await exists(filePath) && options.createBackup !== false) {
          const backupPath = `${filePath}.bak`;
          await fsp.copyFile(filePath, backupPath);
        }

        await fsp.writeFile(filePath, content, 'utf8');
        results.restored.push(key);
        log.info('backup', `Restored: ${key}`, { path: filePath });
      } catch (e) {
        results.errors.push({ file: key, error: e.message });
      }
    }

    // Reload Caddy if Caddyfile was restored
    if (results.restored.includes('caddyfile') && options.reloadCaddy !== false) {
      try {
        const caddyContent = await ctx.caddy.read();
        const loadResponse = await ctx.fetchT(`${ctx.caddy.adminUrl}/load`, {
          method: 'POST',
          headers: { 'Content-Type': CADDY.CONTENT_TYPE },
          body: caddyContent
        });

        if (loadResponse.ok) {
          results.caddyReloaded = true;
        } else {
          results.caddyReloadError = await loadResponse.text();
        }
      } catch (e) {
        results.caddyReloadError = e.message;
      }
    }

    // Reload DNS credentials if restored
    if (results.restored.includes('dnsCredentials')) {
      try {
        ctx.loadDnsCredentials();
        results.dnsReloaded = true;
      } catch (e) {
        results.dnsReloadError = e.message;
      }
    }

    // Reload notification config if restored
    if (results.restored.includes('notifications')) {
      try {
        await ctx.loadNotificationConfig();
        results.notificationsReloaded = true;
      } catch (e) {
        results.notificationsReloadError = e.message;
      }
    }

    // Reload site config if restored
    if (results.restored.includes('config')) {
      ctx.loadSiteConfig();
      results.configReloaded = true;
    }

    // Restore custom assets from base64
    if (backup.assets && typeof backup.assets === 'object') {
      const assetsDir = platformPaths.resolveAssetsPath(process.env.ASSETS_DIR);
      for (const [name, b64] of Object.entries(backup.assets)) {
        try {
          const safeName = path.basename(name); // prevent path traversal
          await fsp.writeFile(path.join(assetsDir, safeName), Buffer.from(b64, 'base64'));
          results.restored.push(`asset:${safeName}`);
        } catch (e) {
          results.errors.push({ file: `asset:${name}`, error: e.message });
        }
      }
    }

    // Restore themes
    if (backup.themes && typeof backup.themes === 'object' && Object.keys(backup.themes).length) {
      try {
        if (!fs.existsSync(THEMES_DIR)) fs.mkdirSync(THEMES_DIR, { recursive: true });
        for (const [slug, data] of Object.entries(backup.themes)) {
          if (/^[a-z0-9-]+$/.test(slug)) {
            fs.writeFileSync(path.join(THEMES_DIR, slug + '.json'), JSON.stringify(data, null, 2), 'utf8');
          }
        }
        results.restored.push(`themes:${Object.keys(backup.themes).length}`);
        log.info('backup', `Restored ${Object.keys(backup.themes).length} themes`);
      } catch (e) {
        results.errors.push({ file: 'themes', error: e.message });
      }
    }

    // Reload encryption key in memory if it was restored
    if (results.restored.includes('encryptionKey')) {
      try {
        // Clear the cached key so crypto-utils reloads from the new file on next use
        const cryptoUtils = require('../../src/security/crypto-utils');
        if (typeof cryptoUtils.clearCachedKey === 'function') {
          cryptoUtils.clearCachedKey();
        }
        results.encryptionKeyReloaded = true;
      } catch (e) {
        log.warn('backup', 'Could not reload encryption key', { error: e.message });
      }
    }

    const success = results.restored.length > 0 && results.errors.length === 0;

    if (success) {
      ok(res, {
        message: `Restored ${results.restored.length} file(s) successfully`,
        results
      });
    } else {
      ok(res, {
        message: `Restore completed with ${results.errors.length} error(s)`,
        results
      }, 200);
    }

    log.info('backup', 'Backup restore completed', { restored: results.restored.length, errors: results.errors.length });
  }, 'backup-restore'));

  return router;
};
