/**
 * Server Groups Management
 * Manages groups of servers for batch operations
 */

import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { logger } from './logger.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Default groups file location
const GROUPS_FILE = path.join(__dirname, '..', '.server-groups.json');

// Group execution strategies
export const EXECUTION_STRATEGIES = {
  PARALLEL: 'parallel',      // Execute on all servers at once
  SEQUENTIAL: 'sequential',  // Execute one by one
  ROLLING: 'rolling'        // Execute with delay between servers
};

class ServerGroups {
  constructor() {
    this.groups = this.loadGroups();
  }

  /**
   * Load groups from file
   */
  loadGroups() {
    try {
      if (fs.existsSync(GROUPS_FILE)) {
        const data = fs.readFileSync(GROUPS_FILE, 'utf8');
        return JSON.parse(data);
      }
    } catch (error) {
      logger.warn('Failed to load server groups', { error: error.message });
    }

    // Return default groups
    return {
      all: {
        description: 'All configured servers',
        servers: [],
        dynamic: true  // Will be populated from server config
      },
      production: {
        description: 'Production servers',
        servers: [],
        strategy: EXECUTION_STRATEGIES.ROLLING,
        delay: 5000  // 5 seconds between servers
      },
      staging: {
        description: 'Staging/test servers',
        servers: [],
        strategy: EXECUTION_STRATEGIES.PARALLEL
      },
      development: {
        description: 'Development servers',
        servers: [],
        strategy: EXECUTION_STRATEGIES.PARALLEL
      }
    };
  }

  /**
   * Save groups to file
   */
  saveGroups() {
    try {
      // Don't save dynamic groups
      const groupsToSave = {};
      for (const [name, group] of Object.entries(this.groups)) {
        if (!group.dynamic) {
          groupsToSave[name] = group;
        }
      }

      fs.writeFileSync(GROUPS_FILE, JSON.stringify(groupsToSave, null, 2));
      logger.info('Server groups saved', { count: Object.keys(groupsToSave).length });
      return true;
    } catch (error) {
      logger.error('Failed to save server groups', { error: error.message });
      return false;
    }
  }

  /**
   * Get a group by name
   */
  getGroup(name) {
    const group = this.groups[name.toLowerCase()];

    if (!group) {
      throw new Error(`Group '${name}' not found`);
    }

    // For 'all' group, return all configured servers
    if (name.toLowerCase() === 'all' && group.dynamic) {
      return {
        ...group,
        servers: this.getAllServers()
      };
    }

    return group;
  }

  /**
   * Get all configured servers
   */
  getAllServers() {
    // This will be populated from the main server config
    const servers = [];

    for (const [key, value] of Object.entries(process.env)) {
      if (key.startsWith('SSH_SERVER_') && key.endsWith('_HOST')) {
        const serverName = key.replace('SSH_SERVER_', '').replace('_HOST', '').toLowerCase();
        servers.push(serverName);
      }
    }

    return servers;
  }

  /**
   * Create a new group
   */
  createGroup(name, servers = [], options = {}) {
    const groupName = name.toLowerCase();

    if (this.groups[groupName] && !options.overwrite) {
      throw new Error(`Group '${name}' already exists`);
    }

    this.groups[groupName] = {
      description: options.description || `Group: ${name}`,
      servers: servers,
      strategy: options.strategy || EXECUTION_STRATEGIES.PARALLEL,
      delay: options.delay || 0,
      stopOnError: options.stopOnError || false,
      created: new Date().toISOString()
    };

    this.saveGroups();

    logger.info('Server group created', {
      name: groupName,
      servers: servers.length,
      strategy: this.groups[groupName].strategy
    });

    return this.groups[groupName];
  }

  /**
   * Update a group
   */
  updateGroup(name, updates) {
    const groupName = name.toLowerCase();
    const group = this.groups[groupName];

    if (!group) {
      throw new Error(`Group '${name}' not found`);
    }

    if (group.dynamic) {
      throw new Error(`Cannot update dynamic group '${name}'`);
    }

    // Update group properties
    if (updates.servers !== undefined) {
      group.servers = updates.servers;
    }
    if (updates.description !== undefined) {
      group.description = updates.description;
    }
    if (updates.strategy !== undefined) {
      group.strategy = updates.strategy;
    }
    if (updates.delay !== undefined) {
      group.delay = updates.delay;
    }
    if (updates.stopOnError !== undefined) {
      group.stopOnError = updates.stopOnError;
    }

    group.updated = new Date().toISOString();

    this.saveGroups();

    logger.info('Server group updated', {
      name: groupName,
      updates: Object.keys(updates)
    });

    return group;
  }

  /**
   * Delete a group
   */
  deleteGroup(name) {
    const groupName = name.toLowerCase();
    const group = this.groups[groupName];

    if (!group) {
      throw new Error(`Group '${name}' not found`);
    }

    if (group.dynamic) {
      throw new Error(`Cannot delete dynamic group '${name}'`);
    }

    delete this.groups[groupName];
    this.saveGroups();

    logger.info('Server group deleted', { name: groupName });

    return true;
  }

  /**
   * Add servers to a group
   */
  addServers(name, servers) {
    const groupName = name.toLowerCase();
    const group = this.groups[groupName];

    if (!group) {
      throw new Error(`Group '${name}' not found`);
    }

    if (group.dynamic) {
      throw new Error(`Cannot modify dynamic group '${name}'`);
    }

    // Add servers (avoid duplicates)
    const currentServers = new Set(group.servers);
    servers.forEach(server => currentServers.add(server.toLowerCase()));
    group.servers = Array.from(currentServers);

    this.saveGroups();

    logger.info('Servers added to group', {
      group: groupName,
      added: servers.length,
      total: group.servers.length
    });

    return group;
  }

  /**
   * Remove servers from a group
   */
  removeServers(name, servers) {
    const groupName = name.toLowerCase();
    const group = this.groups[groupName];

    if (!group) {
      throw new Error(`Group '${name}' not found`);
    }

    if (group.dynamic) {
      throw new Error(`Cannot modify dynamic group '${name}'`);
    }

    // Remove servers
    const toRemove = new Set(servers.map(s => s.toLowerCase()));
    group.servers = group.servers.filter(s => !toRemove.has(s));

    this.saveGroups();

    logger.info('Servers removed from group', {
      group: groupName,
      removed: servers.length,
      remaining: group.servers.length
    });

    return group;
  }

  /**
   * List all groups
   */
  listGroups() {
    const groups = [];

    for (const [name, group] of Object.entries(this.groups)) {
      // Populate dynamic groups
      if (group.dynamic && name === 'all') {
        group.servers = this.getAllServers();
      }

      groups.push({
        name,
        ...group,
        serverCount: group.servers.length
      });
    }

    return groups;
  }

  /**
   * Execute command on group with strategy
   */
  async executeOnGroup(groupName, executor, options = {}) {
    const group = this.getGroup(groupName);
    const results = [];
    const strategy = options.strategy || group.strategy || EXECUTION_STRATEGIES.PARALLEL;
    const delay = options.delay || group.delay || 0;
    const stopOnError = options.stopOnError !== undefined ? options.stopOnError : group.stopOnError;

    logger.info('Executing on server group', {
      group: groupName,
      servers: group.servers.length,
      strategy,
      delay
    });

    switch (strategy) {
    case EXECUTION_STRATEGIES.PARALLEL: {
      // Execute on all servers simultaneously
      const promises = group.servers.map(async (server) => {
        try {
          const result = await executor(server);
          return { server, success: true, result };
        } catch (error) {
          logger.error(`Execution failed on ${server}`, { error: error.message });
          return { server, success: false, error: error.message };
        }
      });

      const parallelResults = await Promise.all(promises);
      results.push(...parallelResults);
      break;
    }

    case EXECUTION_STRATEGIES.SEQUENTIAL:
    case EXECUTION_STRATEGIES.ROLLING:
      // Execute one by one
      for (const server of group.servers) {
        try {
          const result = await executor(server);
          results.push({ server, success: true, result });

          // Add delay for rolling strategy
          if (strategy === EXECUTION_STRATEGIES.ROLLING && delay > 0) {
            logger.debug(`Waiting ${delay}ms before next server`);
            await new Promise(resolve => setTimeout(resolve, delay));
          }
        } catch (error) {
          logger.error(`Execution failed on ${server}`, { error: error.message });
          results.push({ server, success: false, error: error.message });

          // Stop on error if configured
          if (stopOnError) {
            logger.warn('Stopping execution due to error', { server });
            break;
          }
        }
      }
      break;

    default:
      throw new Error(`Unknown execution strategy: ${strategy}`);
    }

    // Summary
    const successful = results.filter(r => r.success).length;
    const failed = results.filter(r => !r.success).length;

    logger.info('Group execution completed', {
      group: groupName,
      successful,
      failed,
      total: results.length
    });

    return {
      group: groupName,
      strategy,
      results,
      summary: {
        total: results.length,
        successful,
        failed
      }
    };
  }
}

// Export singleton instance
export const serverGroups = new ServerGroups();

// Export convenience functions
export const getGroup = (name) => serverGroups.getGroup(name);
export const createGroup = (name, servers, options) => serverGroups.createGroup(name, servers, options);
export const updateGroup = (name, updates) => serverGroups.updateGroup(name, updates);
export const deleteGroup = (name) => serverGroups.deleteGroup(name);
export const addServersToGroup = (name, servers) => serverGroups.addServers(name, servers);
export const removeServersFromGroup = (name, servers) => serverGroups.removeServers(name, servers);
export const listGroups = () => serverGroups.listGroups();
export const executeOnGroup = (name, executor, options) => serverGroups.executeOnGroup(name, executor, options);

export default serverGroups;
