/**
 * Service Generator
 * Generates infrastructure service configurations and routing logic
 *
 * This generator creates:
 * - Service client wrappers for infrastructure services (Redis, queues, storage, etc.)
 * - Routing middleware for context-based and load-balanced service selection
 * - Service configuration loaders
 * - Type definitions for service interfaces
 *
 * Note: This is a PATTERN generator - it creates generic scaffolds, not integration-specific code
 */

import { Generator, GeneratorContext, GenerationResult, GeneratedFile } from '../types';
import * as path from 'path';

export class ServiceGenerator implements Generator {
  id = 'service-infrastructure';
  name = 'Service Infrastructure Generator';
  description = 'Generates infrastructure service configurations and routing logic';

  generate(context: GeneratorContext): GenerationResult {
    const { workflow, outputDirectory } = context;
    const files: GeneratedFile[] = [];

    // Check if workflow has stages with infrastructure services
    if (!workflow.stages) {
      return { files: [] };
    }

    // Collect all infrastructure services across all stages
    const allServices = this.collectInfrastructureServices(workflow);

    if (Object.keys(allServices).length === 0) {
      return { files: [] };
    }

    // Generate service manager (main orchestrator)
    files.push(this.generateServiceManager(allServices, outputDirectory));

    // Generate service clients for each service type
    for (const [serviceType, configs] of Object.entries(allServices)) {
      files.push(this.generateServiceClient(serviceType, configs, outputDirectory));
    }

    // Generate routing logic from stack components
    if (workflow.stack) {
      files.push(this.generateRoutingMiddleware(workflow.stack, outputDirectory));
    }

    // Generate configuration loader
    files.push(this.generateConfigLoader(allServices, outputDirectory));

    // Generate type definitions
    files.push(this.generateServiceTypes(allServices, outputDirectory));

    return { files };
  }

  private collectInfrastructureServices(workflow: any): Record<string, any[]> {
    const services: Record<string, any[]> = {};

    for (const [stageName, stage] of Object.entries(workflow.stages || {})) {
      const stageConfig = stage as any;
      const infraServices = stageConfig.infrastructure?.services || {};

      for (const [serviceType, serviceConfig] of Object.entries(infraServices)) {
        if (!services[serviceType]) {
          services[serviceType] = [];
        }

        const config = serviceConfig as any;
        services[serviceType].push({
          stage: stageName,
          ...config,
        });
      }
    }

    return services;
  }

  private generateServiceManager(allServices: Record<string, any[]>, outputDir: string): GeneratedFile {
    const serviceTypes = Object.keys(allServices);

    const content = `/**
 * Service Manager
 * Central orchestrator for all infrastructure services
 * Auto-generated by Bangstack ServiceGenerator
 */

import { ServiceConfig } from './service-types';
${serviceTypes.map(type => `import { ${this.toPascalCase(type)}Client } from './${type}-client';`).join('\n')}

export class ServiceManager {
  private config: ServiceConfig;
  private clients: Map<string, any>;

  constructor(config: ServiceConfig) {
    this.config = config;
    this.clients = new Map();
  }

  /**
   * Initialize all service clients based on stage configuration
   */
  async initialize(): Promise<void> {
    const stage = this.config.stage || 'local';

${serviceTypes.map(type => `
    // Initialize ${type} if configured
    if (this.config.services.${type}) {
      const ${type}Client = new ${this.toPascalCase(type)}Client(this.config.services.${type});
      await ${type}Client.connect();
      this.clients.set('${type}', ${type}Client);
    }
`).join('')}
  }

  /**
   * Get a service client by type
   */
  getClient<T>(serviceType: string): T | undefined {
    return this.clients.get(serviceType);
  }

  /**
   * Check if a service is available
   */
  hasService(serviceType: string): boolean {
    return this.clients.has(serviceType);
  }

  /**
   * Disconnect all services
   */
  async shutdown(): Promise<void> {
    for (const [serviceName, client] of this.clients.entries()) {
      if (client.disconnect) {
        await client.disconnect();
      }
    }
    this.clients.clear();
  }

  /**
   * Health check for all services
   */
  async healthCheck(): Promise<Record<string, boolean>> {
    const health: Record<string, boolean> = {};

    for (const [serviceName, client] of this.clients.entries()) {
      try {
        if (client.healthCheck) {
          health[serviceName] = await client.healthCheck();
        } else {
          health[serviceName] = true; // Assume healthy if no health check method
        }
      } catch (error) {
        health[serviceName] = false;
      }
    }

    return health;
  }
}
`;

    return {
      path: path.join(outputDir, 'backend', 'src', 'services', 'service-manager.ts'),
      content,
      overwrite: true, // Always regenerate to stay in sync with YAML config
    };
  }

  private generateServiceClient(serviceType: string, configs: any[], outputDir: string): GeneratedFile {
    const className = this.toPascalCase(serviceType) + 'Client';

    const content = `/**
 * ${className}
 * Service client for ${serviceType}
 * Auto-generated by Bangstack ServiceGenerator
 *
 * This is a SCAFFOLD - implement actual ${serviceType} integration here
 *
 * NOTE: This file is NOT overwritten on regeneration to preserve your implementation.
 * If you remove this service from your workflow YAML, manually delete this file.
 */

export interface ${className}Config {
  provider: string;
  connection: string;
  config?: Record<string, any>;
}

export class ${className} {
  private config: ${className}Config;
  private client: any;

  constructor(config: ${className}Config) {
    this.config = config;
  }

  /**
   * Connect to ${serviceType}
   * TODO: Implement actual connection logic based on provider
   */
  async connect(): Promise<void> {
    // Scaffold: Implement connection logic here
    // Example providers seen in config: ${configs.map(c => c.provider).filter(Boolean).join(', ')}

    console.log(\`Connecting to ${serviceType} (provider: \${this.config.provider})...\`);

    // TODO: Add provider-specific connection logic
    // switch (this.config.provider) {
    //   case 'redis':
    //     this.client = await createRedisClient(this.config.connection);
    //     break;
    //   case 'upstash':
    //     this.client = await createUpstashClient(this.config.connection);
    //     break;
    //   default:
    //     throw new Error(\`Unknown ${serviceType} provider: \${this.config.provider}\`);
    // }
  }

  /**
   * Disconnect from ${serviceType}
   */
  async disconnect(): Promise<void> {
    if (this.client && this.client.disconnect) {
      await this.client.disconnect();
    }
  }

  /**
   * Health check for ${serviceType}
   */
  async healthCheck(): Promise<boolean> {
    // Scaffold: Implement health check logic
    return this.client ? true : false;
  }

  /**
   * Get the underlying client
   * Use this to access provider-specific methods
   */
  getClient(): any {
    return this.client;
  }

  // TODO: Add common methods for ${serviceType}
  // Example:
  // async get(key: string): Promise<any> { }
  // async set(key: string, value: any): Promise<void> { }
}
`;

    return {
      path: path.join(outputDir, 'backend', 'src', 'services', `${serviceType}-client.ts`),
      content,
      overwrite: false,
    };
  }

  private generateRoutingMiddleware(stack: Record<string, any>, outputDir: string): GeneratedFile {
    const routedComponents = Object.entries(stack)
      .filter(([_, component]: [string, any]) => {
        const comp = component as any;
        return comp.routing && comp.routing !== 'primary_only';
      });

    const content = `/**
 * Service Routing Middleware
 * Handles context-based and load-balanced service routing
 * Auto-generated by Bangstack ServiceGenerator
 */

import { Request, Response, NextFunction } from 'express';

export interface RoutingContext {
  component: string;
  operation: string;
  userId?: string;
  sessionId?: string;
  metadata?: Record<string, any>;
}

${routedComponents.map(([componentName, component]: [string, any]) => {
  const comp = component as any;
  const services = Object.keys(comp.services || {});

  return `
/**
 * Router for ${componentName}
 * Strategy: ${comp.routing}
 * Available services: ${services.join(', ')}
 */
export class ${this.toPascalCase(componentName)}Router {
  private services: Map<string, any>;
  private routing: '${comp.routing}';
  private primary: string | undefined;
  ${comp.routing === 'load_balanced' ? `
  // Load balancing state
  private roundRobinIndex: number = 0;
  private connectionCounts: Map<string, number> = new Map();
  private healthStatus: Map<string, boolean> = new Map();
  private lastHealthCheck: number = 0;
  private healthCheckInterval: number = 30000; // 30 seconds
  ` : ''}

  constructor(services: Record<string, any>) {
    this.services = new Map(Object.entries(services));
    this.routing = '${comp.routing}';
    this.primary = '${comp.primary || ''}';
    ${comp.routing === 'load_balanced' ? `
    // Initialize health status and connection counts
    for (const serviceName of this.services.keys()) {
      this.healthStatus.set(serviceName, true); // Assume healthy initially
      this.connectionCounts.set(serviceName, 0);
    }
    ` : ''}
  }
  ${comp.routing === 'load_balanced' ? `

  /**
   * Get healthy services only
   */
  private getHealthyServices(): [string, any][] {
    return Array.from(this.services.entries()).filter(([name, _]) =>
      this.healthStatus.get(name) !== false
    );
  }

  /**
   * Round-robin load balancing
   */
  private selectRoundRobin(): any {
    const healthyServices = this.getHealthyServices();
    if (healthyServices.length === 0) {
      throw new Error('No healthy services available');
    }

    const index = this.roundRobinIndex % healthyServices.length;
    this.roundRobinIndex++;
    return healthyServices[index][1];
  }

  /**
   * Weighted round-robin load balancing
   */
  private selectWeighted(): any {
    const healthyServices = this.getHealthyServices();
    if (healthyServices.length === 0) {
      throw new Error('No healthy services available');
    }

    // Calculate total weight
    let totalWeight = 0;
    for (const [_, service] of healthyServices) {
      totalWeight += service.weight || 1;
    }

    // Generate random number in range [0, totalWeight)
    let random = Math.random() * totalWeight;

    // Select service based on weight
    for (const [_, service] of healthyServices) {
      const weight = service.weight || 1;
      random -= weight;
      if (random <= 0) {
        return service;
      }
    }

    // Fallback to first service
    return healthyServices[0][1];
  }

  /**
   * Least-connections load balancing
   */
  private selectLeastConnections(): any {
    const healthyServices = this.getHealthyServices();
    if (healthyServices.length === 0) {
      throw new Error('No healthy services available');
    }

    // Find service with fewest connections
    let minConnections = Infinity;
    let selectedService = healthyServices[0][1];

    for (const [name, service] of healthyServices) {
      const connections = this.connectionCounts.get(name) || 0;
      if (connections < minConnections) {
        minConnections = connections;
        selectedService = service;
      }
    }

    return selectedService;
  }

  /**
   * Track connection start
   */
  trackConnection(serviceName: string): void {
    const current = this.connectionCounts.get(serviceName) || 0;
    this.connectionCounts.set(serviceName, current + 1);
  }

  /**
   * Track connection end
   */
  releaseConnection(serviceName: string): void {
    const current = this.connectionCounts.get(serviceName) || 0;
    this.connectionCounts.set(serviceName, Math.max(0, current - 1));
  }

  /**
   * Update health status for a service
   */
  updateHealth(serviceName: string, healthy: boolean): void {
    this.healthStatus.set(serviceName, healthy);
  }

  /**
   * Periodic health check (call this from your health check routine)
   */
  async performHealthCheck(): Promise<void> {
    const now = Date.now();
    if (now - this.lastHealthCheck < this.healthCheckInterval) {
      return;
    }

    this.lastHealthCheck = now;

    for (const [serviceName, service] of this.services.entries()) {
      try {
        // TODO: Implement actual health check based on service type
        // For now, assume all services are healthy
        this.healthStatus.set(serviceName, true);
      } catch (error) {
        this.healthStatus.set(serviceName, false);
      }
    }
  }
  ` : ''}

  /**
   * Select service based on routing strategy
   */
  selectService(context: RoutingContext): any {
    ${comp.routing === 'context_based' ? `
    // Context-based routing
    // Check each service's use_for configuration
    for (const [serviceName, service] of this.services.entries()) {
      const useFor = service.use_for || [];

      // Check if this service should be used for this context
      if (useFor.includes('*') || useFor.includes(context.operation)) {
        return service;
      }
    }

    // Fallback to primary if no match
    if (this.primary && this.services.has(this.primary)) {
      return this.services.get(this.primary);
    }

    // Fallback to any fallback service
    for (const [serviceName, service] of this.services.entries()) {
      if (service.fallback) {
        return service;
      }
    }

    throw new Error('No suitable service found for context: ' + context.operation);
    ` : comp.routing === 'load_balanced' ? `
    // Load-balanced routing with multiple strategies
    const strategy = context.metadata?.loadBalanceStrategy || 'round-robin';

    switch (strategy) {
      case 'round-robin':
        return this.selectRoundRobin();

      case 'weighted':
        return this.selectWeighted();

      case 'least-connections':
        return this.selectLeastConnections();

      case 'random':
        const healthyServices = this.getHealthyServices();
        if (healthyServices.length === 0) {
          throw new Error('No healthy services available');
        }
        const index = Math.floor(Math.random() * healthyServices.length);
        return healthyServices[index][1];

      default:
        return this.selectRoundRobin();
    }
    ` : `
    // Primary-only routing
    if (this.primary && this.services.has(this.primary)) {
      return this.services.get(this.primary);
    }
    return Array.from(this.services.values())[0];
    `}
  }

  /**
   * Express middleware for ${componentName}
   */
  middleware() {
    return (req: Request, res: Response, next: NextFunction) => {
      const context: RoutingContext = {
        component: '${componentName}',
        operation: req.path.split('/')[1] || 'default',
        userId: (req as any).user?.id,
        sessionId: req.sessionID,
        metadata: {},
      };

      const service = this.selectService(context);
      (req as any).${componentName}Service = service;

      next();
    };
  }
}`;
}).join('\n\n')}

${routedComponents.length === 0 ? `
// No routed components found in stack
// All components use primary_only routing
` : ''}
`;

    return {
      path: path.join(outputDir, 'backend', 'src', 'services', 'routing-middleware.ts'),
      content,
      overwrite: true, // Always regenerate to stay in sync with stack routing config
    };
  }

  private generateConfigLoader(allServices: Record<string, any[]>, outputDir: string): GeneratedFile {
    const serviceTypes = Object.keys(allServices);

    const content = `/**
 * Service Configuration Loader
 * Loads service configuration based on current stage
 * Auto-generated by Bangstack ServiceGenerator
 */

import { ServiceConfig } from './service-types';

/**
 * Load service configuration for the current stage
 */
export function loadServiceConfig(stage: string = 'local'): ServiceConfig {
  const config: ServiceConfig = {
    stage,
    services: {},
  };

${serviceTypes.map(type => `
  // Load ${type} configuration
  if (process.env.${type.toUpperCase()}_PROVIDER) {
    config.services.${type} = {
      provider: process.env.${type.toUpperCase()}_PROVIDER,
      connection: process.env.${type.toUpperCase()}_CONNECTION || '',
      config: loadServiceEnvConfig('${type}'),
    };
  }
`).join('')}

  return config;
}

/**
 * Load service-specific configuration from environment variables
 */
function loadServiceEnvConfig(serviceType: string): Record<string, any> {
  const config: Record<string, any> = {};
  const prefix = \`\${serviceType.toUpperCase()}_\`;

  for (const [key, value] of Object.entries(process.env)) {
    if (key.startsWith(prefix) && key !== \`\${prefix}PROVIDER\` && key !== \`\${prefix}CONNECTION\`) {
      const configKey = key.slice(prefix.length).toLowerCase();
      config[configKey] = value;
    }
  }

  return config;
}

/**
 * Validate service configuration
 */
export function validateServiceConfig(config: ServiceConfig): string[] {
  const errors: string[] = [];

${serviceTypes.map(type => `
  if (config.services.${type}) {
    if (!config.services.${type}.provider) {
      errors.push('${type}: Missing provider');
    }
    if (!config.services.${type}.connection) {
      errors.push('${type}: Missing connection string');
    }
  }
`).join('')}

  return errors;
}
`;

    return {
      path: path.join(outputDir, 'backend', 'src', 'services', 'config-loader.ts'),
      content,
      overwrite: true, // Always regenerate to stay in sync with services config
    };
  }

  private generateServiceTypes(allServices: Record<string, any[]>, outputDir: string): GeneratedFile {
    const serviceTypes = Object.keys(allServices);

    const content = `/**
 * Service Type Definitions
 * Auto-generated by Bangstack ServiceGenerator
 */

${serviceTypes.map(type => `
export interface ${this.toPascalCase(type)}ServiceConfig {
  provider: string;
  connection: string;
  config?: Record<string, any>;
}
`).join('')}

export interface ServiceConfig {
  stage: string;
  services: {
${serviceTypes.map(type => `    ${type}?: ${this.toPascalCase(type)}ServiceConfig;`).join('\n')}
  };
}
`;

    return {
      path: path.join(outputDir, 'backend', 'src', 'services', 'service-types.ts'),
      content,
      overwrite: true, // Always regenerate to stay in sync with service types
    };
  }

  private toPascalCase(str: string): string {
    return str
      .split(/[_-]/)
      .map(word => word.charAt(0).toUpperCase() + word.slice(1))
      .join('');
  }
}
