/**
 * DC-080: Plugin/Extension system for DashCaddy
 *
 * Allows third-party extensions to register:
 * - Custom service types with health-check logic
 * - Custom notification providers
 * - Custom workflow actions
 * - Dashboard widgets (via manifest)
 *
 * Plugins are loaded from the data directory:
 *   {dataDir}/plugins/{plugin-name}/manifest.json
 *   {dataDir}/plugins/{plugin-name}/index.js
 *
 * The manifest.json describes capabilities and permissions.
 * The index.js exports hooks that DashCaddy calls at appropriate times.
 *
 * Security: plugins run in the same process (no sandbox yet). The manifest
 * declares required permissions, and the admin must approve on install.
 */

const fs = require('fs');
const path = require('path');
const EventEmitter = require('events');

const PLUGIN_DIR = process.env.PLUGIN_DIR || path.join(process.cwd(), 'data', 'plugins');

const HOOK_TYPES = [
  'service:health-check',     // Custom health check for a service type
  'notification:provider',    // Custom notification provider
  'workflow:action',          // Custom workflow action type
  'dashboard:widget',         // Custom dashboard widget manifest
  'container:pre-deploy',     // Hook before container deployment
  'container:post-deploy',    // Hook after container deployment
  'config:validate',          // Hook for config validation
];

class PluginManager extends EventEmitter {
  constructor({ dataDir, log }) {
    super();
    this.pluginDir = dataDir ? path.join(dataDir, 'plugins') : PLUGIN_DIR;
    this.log = log || console;
    this.plugins = new Map();          // name → { manifest, module, hooks }
    this.serviceTypes = new Map();     // typeName → pluginName
    this.notificationProviders = new Map();
    this.workflowActions = new Map();
    this.dashboardWidgets = new Map();
    this.loaded = false;
  }

  /**
   * Discover and load all plugins from the plugin directory.
   */
  async loadAll() {
    if (this.loaded) return;

    try {
      if (!fs.existsSync(this.pluginDir)) {
        fs.mkdirSync(this.pluginDir, { recursive: true });
        this.log.info('plugins', 'Plugin directory created', { dir: this.pluginDir });
        this.loaded = true;
        return;
      }

      const entries = fs.readdirSync(this.pluginDir, { withFileTypes: true });
      for (const entry of entries) {
        if (!entry.isDirectory()) continue;
        if (entry.name.startsWith('.')) continue;

        try {
          await this.loadOne(path.join(this.pluginDir, entry.name));
        } catch (err) {
          this.log.error('plugins', `Failed to load plugin: ${entry.name}`, { error: err.message });
        }
      }

      this.loaded = true;
      this.log.info('plugins', 'All plugins loaded', {
        count: this.plugins.size,
        serviceTypes: [...this.serviceTypes.keys()],
        notificationProviders: [...this.notificationProviders.keys()],
        workflowActions: [...this.workflowActions.keys()],
      });
    } catch (err) {
      this.log.error('plugins', err, null, { note: 'Failed to scan plugin directory' });
      this.loaded = true; // Don't crash — just run without plugins
    }
  }

  /**
   * Load a single plugin from its directory.
   */
  async loadOne(pluginPath) {
    const manifestPath = path.join(pluginPath, 'manifest.json');
    const indexPath = path.join(pluginPath, 'index.js');

    if (!fs.existsSync(manifestPath)) {
      throw new Error('manifest.json not found');
    }

    const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));

    // Validate manifest
    if (!manifest.name || !manifest.version) {
      throw new Error('manifest.json must have name and version');
    }

    if (this.plugins.has(manifest.name)) {
      throw new Error(`Plugin ${manifest.name} already loaded`);
    }

    // Load the plugin module if it exists
    let module = {};
    if (fs.existsSync(indexPath)) {
      delete require.cache[require.resolve(indexPath)];
      module = require(indexPath);
    }

    // Register hooks
    const hooks = {};
    if (module.hooks) {
      for (const [hookType, fn] of Object.entries(module.hooks)) {
        if (HOOK_TYPES.includes(hookType)) {
          hooks[hookType] = fn;
          this._registerHook(manifest.name, hookType, fn, manifest);
        }
      }
    }

    this.plugins.set(manifest.name, { manifest, module, hooks, path: pluginPath });
    this.emit('plugin-loaded', manifest);
    this.log.info('plugins', `Loaded plugin: ${manifest.name} v${manifest.version}`, {
      hooks: Object.keys(hooks),
    });
  }

  _registerHook(pluginName, hookType, fn, manifest) {
    switch (hookType) {
      case 'service:health-check':
        if (manifest.serviceType) {
          this.serviceTypes.set(manifest.serviceType, pluginName);
        }
        break;
      case 'notification:provider':
        if (manifest.providerName) {
          this.notificationProviders.set(manifest.providerName, { pluginName, fn });
        }
        break;
      case 'workflow:action':
        if (manifest.actionType) {
          this.workflowActions.set(manifest.actionType, { pluginName, fn });
        }
        break;
      case 'dashboard:widget':
        if (manifest.widget) {
          this.dashboardWidgets.set(manifest.name, { pluginName, manifest: manifest.widget });
        }
        break;
    }
  }

  /**
   * Unload a plugin by name.
   */
  unload(name) {
    const plugin = this.plugins.get(name);
    if (!plugin) return false;

    // Clean up registrations
    for (const [type, pName] of this.serviceTypes) {
      if (pName === name) this.serviceTypes.delete(type);
    }
    for (const [type, { pluginName }] of this.notificationProviders) {
      if (pluginName === name) this.notificationProviders.delete(type);
    }
    for (const [type, { pluginName }] of this.workflowActions) {
      if (pluginName === name) this.workflowActions.delete(type);
    }
    for (const [wName, { pluginName }] of this.dashboardWidgets) {
      if (pluginName === name) this.dashboardWidgets.delete(wName);
    }

    this.plugins.delete(name);
    this.emit('plugin-unloaded', name);
    this.log.info('plugins', `Unloaded plugin: ${name}`);
    return true;
  }

  /**
   * Execute a plugin hook for a specific type.
   */
  async executeHook(hookType, ...args) {
    // Try each plugin that registered this hook
    const results = [];
    for (const [name, plugin] of this.plugins) {
      if (plugin.hooks[hookType]) {
        try {
          const result = await plugin.hooks[hookType](...args);
          results.push({ plugin: name, result });
        } catch (err) {
          this.log.error('plugins', `Hook ${hookType} failed in ${name}`, { error: err.message });
          results.push({ plugin: name, error: err.message });
        }
      }
    }
    return results;
  }

  /**
   * Get list of loaded plugins with their manifests.
   */
  list() {
    return [...this.plugins.values()].map(p => ({
      name: p.manifest.name,
      version: p.manifest.version,
      description: p.manifest.description || '',
      hooks: Object.keys(p.hooks),
      permissions: p.manifest.permissions || [],
    }));
  }

  /**
   * Get dashboard widget manifests from plugins.
   */
  getWidgets() {
    return [...this.dashboardWidgets.values()].map(w => w.manifest);
  }

  /**
   * Get registered service types.
   */
  getServiceTypes() {
    return [...this.serviceTypes.keys()];
  }

  /**
   * Get registered workflow action types.
   */
  getWorkflowActions() {
    return [...this.workflowActions.keys()];
  }
}

module.exports = { PluginManager, HOOK_TYPES };
