/**
 * Environment Configuration Generator
 * Generates .env files from workflow environment configuration
 */

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

export class EnvironmentGenerator implements Generator {
  id = 'environment-config';
  name = 'Environment Configuration Generator';
  description = 'Generates environment configuration files (.env)';

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

    // Check if workflow has legacy environment config or new stages config
    const hasLegacyEnv = workflow.environment !== undefined;
    const hasStages = workflow.stages !== undefined;

    if (!hasLegacyEnv && !hasStages) {
      return { files: [] };
    }

    // Get all stack components
    const stackComponents = Object.keys(workflow.stack);

    for (const componentName of stackComponents) {
      // Generate environment files based on stages or legacy environment
      if (hasStages) {
        // New stages-based configuration
        files.push(...this.generateStageBasedEnv(componentName, workflow, outputDirectory));
      } else if (hasLegacyEnv) {
        // Legacy environment configuration
        files.push(...this.generateLegacyEnv(componentName, workflow, outputDirectory));
      }
    }

    return { files };
  }

  private generateStageBasedEnv(
    componentName: string,
    workflow: any,
    outputDir: string
  ): GeneratedFile[] {
    const files: GeneratedFile[] = [];

    // Generate .env.example (template with all variables)
    files.push(this.generateEnvExampleFromStages(componentName, workflow, outputDir));

    // Generate .env file for each stage
    for (const [stageName, stageConfig] of Object.entries(workflow.stages || {})) {
      const stage = stageConfig as any;
      const envVars = this.collectStageEnvVars(componentName, stageName, stage, workflow);

      if (Object.keys(envVars).length > 0) {
        files.push(this.generateEnvFile(
          componentName,
          envVars,
          stageName as any,
          outputDir
        ));
      }
    }

    return files;
  }

  private generateLegacyEnv(
    componentName: string,
    workflow: any,
    outputDir: string
  ): GeneratedFile[] {
    const files: GeneratedFile[] = [];

    // Generate .env.example (template with all variables)
    files.push(this.generateEnvExample(componentName, workflow, outputDir));

    // Generate .env.development (with dev values)
    if (workflow.environment.development?.[componentName]) {
      files.push(this.generateEnvFile(
        componentName,
        workflow.environment.development[componentName],
        'development',
        outputDir
      ));
    }

    // Generate .env.test (with test values or dev defaults)
    const testEnv = workflow.environment.test?.[componentName] ||
                    workflow.environment.development?.[componentName];
    if (testEnv) {
      files.push(this.generateEnvFile(
        componentName,
        this.createTestEnvironment(testEnv),
        'test',
        outputDir
      ));
    }

    return files;
  }

  private collectStageEnvVars(
    componentName: string,
    stageName: string,
    stage: any,
    workflow: any
  ): Record<string, any> {
    const envVars: Record<string, any> = {};

    // Add stage-specific env vars
    if (stage.env) {
      Object.assign(envVars, stage.env);
    }

    // Add infrastructure services connection strings
    if (stage.infrastructure) {
      // Domains
      if (stage.infrastructure.domains) {
        const domains = stage.infrastructure.domains;
        if (domains.web) envVars.WEB_URL = domains.web;
        if (domains.api) envVars.API_URL = domains.api;
        if (domains.docs) envVars.DOCS_URL = domains.docs;
        if (domains.portal) envVars.PORTAL_URL = domains.portal;
        if (domains.admin) envVars.ADMIN_URL = domains.admin;
        if (domains.marketing) envVars.MARKETING_URL = domains.marketing;
        if (domains.custom) {
          for (const [key, value] of Object.entries(domains.custom)) {
            envVars[`${key.toUpperCase()}_URL`] = value;
          }
        }
      }

      // Databases
      if (stage.infrastructure.databases) {
        const dbs = stage.infrastructure.databases;
        if (dbs.primary) envVars.DATABASE_URL = dbs.primary;
        if (dbs.analytics) envVars.ANALYTICS_DATABASE_URL = dbs.analytics;
        if (dbs.cache) envVars.CACHE_URL = dbs.cache;
        if (dbs.custom) {
          for (const [key, value] of Object.entries(dbs.custom)) {
            envVars[`${key.toUpperCase()}_DATABASE_URL`] = value;
          }
        }
      }

      // Infrastructure services
      if (stage.infrastructure.services) {
        const services = stage.infrastructure.services;

        if (services.redis) {
          const redis = services.redis as any;
          envVars.REDIS_PROVIDER = redis.provider;
          envVars.REDIS_CONNECTION = redis.connection;
        }

        if (services.queue) {
          const queue = services.queue as any;
          envVars.QUEUE_PROVIDER = queue.provider;
          envVars.QUEUE_CONNECTION = queue.connection;
        }

        if (services.storage) {
          const storage = services.storage as any;
          envVars.STORAGE_PROVIDER = storage.provider;
          envVars.STORAGE_CONNECTION = storage.connection;
        }

        if (services.search) {
          const search = services.search as any;
          envVars.SEARCH_PROVIDER = search.provider;
          envVars.SEARCH_CONNECTION = search.connection;
        }

        if (services.email) {
          const email = services.email as any;
          envVars.EMAIL_PROVIDER = email.provider;
          envVars.EMAIL_CONNECTION = email.connection;
        }

        if (services.cdn) {
          const cdn = services.cdn as any;
          envVars.CDN_PROVIDER = cdn.provider;
          envVars.CDN_CONNECTION = cdn.connection;
        }

        if (services.monitoring) {
          const monitoring = services.monitoring as any;
          envVars.MONITORING_PROVIDER = monitoring.provider;
          envVars.MONITORING_CONNECTION = monitoring.connection;
        }

        if (services.custom) {
          for (const [key, serviceConfig] of Object.entries(services.custom)) {
            const service = serviceConfig as any;
            envVars[`${key.toUpperCase()}_PROVIDER`] = service.provider;
            envVars[`${key.toUpperCase()}_CONNECTION`] = service.connection;
          }
        }
      }
    }

    // Add stage-specific config
    if (stage.config) {
      if (stage.config.log_level) {
        envVars.LOG_LEVEL = stage.config.log_level;
      }
      if (stage.config.error_details !== undefined) {
        envVars.ERROR_DETAILS = stage.config.error_details;
      }
      if (stage.config.cors) {
        envVars.CORS_ENABLED = stage.config.cors.enabled;
        if (stage.config.cors.origins) {
          envVars.CORS_ORIGINS = stage.config.cors.origins.join(',');
        }
      }
      if (stage.config.rate_limiting) {
        envVars.RATE_LIMITING_ENABLED = stage.config.rate_limiting.enabled;
        if (stage.config.rate_limiting.requests_per_minute) {
          envVars.RATE_LIMIT_RPM = stage.config.rate_limiting.requests_per_minute;
        }
      }
    }

    // Add component-specific defaults
    const comp = workflow.stack[componentName];
    if (comp?.type === 'backend') {
      if (!envVars.PORT) envVars.PORT = 3000;
      if (!envVars.NODE_ENV) envVars.NODE_ENV = stageName === 'production' ? 'production' : 'development';
    }
    if (comp?.type === 'frontend') {
      if (!envVars.VITE_API_URL && envVars.API_URL) {
        envVars.VITE_API_URL = envVars.API_URL;
      }
    }

    return envVars;
  }

  private generateEnvExampleFromStages(
    componentName: string,
    workflow: any,
    outputDir: string
  ): GeneratedFile {
    const allEnvVars = new Map<string, string>();

    // Collect all environment variables from all stages
    for (const [stageName, stageConfig] of Object.entries(workflow.stages || {})) {
      const stage = stageConfig as any;
      const envVars = this.collectStageEnvVars(componentName, stageName, stage, workflow);

      for (const key of Object.keys(envVars)) {
        if (!allEnvVars.has(key)) {
          allEnvVars.set(key, this.getVarDescription(key));
        }
      }
    }

    // Add auth-specific environment variables
    this.addAuthEnvVars(workflow, componentName, allEnvVars);

    let content = `# Environment Configuration
# Copy this file to .env.{stage} and fill in the values
# Stages: ${Object.keys(workflow.stages || {}).join(', ')}
# DO NOT commit .env files with real secrets to version control

`;

    // Group variables by category
    const grouped = this.groupVariables(Array.from(allEnvVars.keys()));

    for (const [category, vars] of Object.entries(grouped)) {
      content += `# ${category}\n`;
      for (const varName of vars) {
        const description = allEnvVars.get(varName);
        if (description) {
          content += `# ${description}\n`;
        }
        content += `${varName}=\n\n`;
      }
    }

    return {
      path: path.join(outputDir, componentName, '.env.example'),
      content,
      overwrite: true,
    };
  }

  private generateEnvExample(
    componentName: string,
    workflow: any,
    outputDir: string
  ): GeneratedFile {
    const allEnvVars = new Map<string, string>();

    // Collect all environment variables from all environments
    for (const [_envName, envConfig] of Object.entries(workflow.environment)) {
      const componentEnv = (envConfig as any)[componentName];
      if (componentEnv) {
        for (const [key, _value] of Object.entries(componentEnv)) {
          if (!allEnvVars.has(key)) {
            // Add description comment based on key name
            allEnvVars.set(key, this.getVarDescription(key));
          }
        }
      }
    }

    // Check deployment config for additional required variables
    const deployment = workflow.deployment?.[componentName];
    if (deployment?.environment_variables) {
      for (const varName of deployment.environment_variables) {
        if (!allEnvVars.has(varName)) {
          allEnvVars.set(varName, this.getVarDescription(varName));
        }
      }
    }

    // Add auth-specific environment variables
    this.addAuthEnvVars(workflow, componentName, allEnvVars);

    let content = `# Environment Configuration
# Copy this file to .env.development or .env.test and fill in the values
# DO NOT commit .env files with real secrets to version control

`;

    // Group variables by category
    const grouped = this.groupVariables(Array.from(allEnvVars.keys()));

    for (const [category, vars] of Object.entries(grouped)) {
      content += `# ${category}\n`;
      for (const varName of vars) {
        const description = allEnvVars.get(varName);
        if (description) {
          content += `# ${description}\n`;
        }
        content += `${varName}=\n\n`;
      }
    }

    return {
      path: path.join(outputDir, componentName, '.env.example'),
      content,
      overwrite: true,
    };
  }

  private generateEnvFile(
    componentName: string,
    envConfig: Record<string, any>,
    envType: 'development' | 'test',
    outputDir: string
  ): GeneratedFile {
    let content = `# ${envType.charAt(0).toUpperCase() + envType.slice(1)} Environment
# Auto-generated from workflow configuration
# You can override these values as needed

`;

    // Group variables by category
    const varNames = Object.keys(envConfig);
    const grouped = this.groupVariables(varNames);

    for (const [category, vars] of Object.entries(grouped)) {
      content += `# ${category}\n`;
      for (const varName of vars) {
        const value = envConfig[varName];
        const processedValue = this.processValue(value, envType);
        content += `${varName}=${processedValue}\n`;
      }
      content += '\n';
    }

    return {
      path: path.join(outputDir, componentName, `.env.${envType}`),
      content,
      overwrite: false, // Don't overwrite existing env files
    };
  }

  private processValue(value: any, _envType: string): string {
    // Handle template values like {{ secrets.XXX }}
    if (typeof value === 'string' && value.includes('{{')) {
      // For non-production environments, provide placeholder
      const match = value.match(/\{\{\s*secrets\.([^}]+)\s*\}\}/);
      if (match) {
        const secretName = match[1];
        return `your-${secretName.toLowerCase().replace(/_/g, '-')}-here`;
      }
      return 'CHANGE_ME';
    }

    return String(value);
  }

  private createTestEnvironment(baseEnv: Record<string, any>): Record<string, any> {
    const testEnv: Record<string, any> = { ...baseEnv };

    // Override specific values for testing
    if (testEnv.DATABASE_URL) {
      testEnv.DATABASE_URL = testEnv.DATABASE_URL.replace(/\/[^/]+$/, '/test_db');
    }
    if (testEnv.NODE_ENV) {
      testEnv.NODE_ENV = 'test';
    }
    if (testEnv.JWT_SECRET) {
      testEnv.JWT_SECRET = 'test-secret-key';
    }

    return testEnv;
  }

  private getVarDescription(varName: string): string {
    const descriptions: Record<string, string> = {
      // Database
      DATABASE_URL: 'Database connection string',
      POSTGRES_DB: 'PostgreSQL database name',
      POSTGRES_USER: 'PostgreSQL username',
      POSTGRES_PASSWORD: 'PostgreSQL password',

      // Server
      PORT: 'Server port',
      HOST: 'Server host',
      NODE_ENV: 'Node environment (development, production, test)',

      // API
      API_URL: 'API base URL',
      VITE_API_URL: 'API URL for Vite frontend',
      NEXT_PUBLIC_API_URL: 'API URL for Next.js frontend',

      // Authentication
      JWT_SECRET: 'Secret key for JWT token signing',
      JWT_EXPIRES_IN: 'JWT token expiration time',
      SESSION_SECRET: 'Secret key for session management',
      CLERK_SECRET_KEY: 'Clerk secret key (from clerk.com dashboard)',
      VITE_CLERK_PUBLISHABLE_KEY: 'Clerk publishable key (from clerk.com dashboard)',

      // External Services
      REDIS_URL: 'Redis connection string',
      SMTP_HOST: 'SMTP server host',
      SMTP_PORT: 'SMTP server port',
      SMTP_USER: 'SMTP username',
      SMTP_PASSWORD: 'SMTP password',

      // Cloud Services
      AWS_REGION: 'AWS region',
      AWS_ACCESS_KEY_ID: 'AWS access key',
      AWS_SECRET_ACCESS_KEY: 'AWS secret key',
      S3_BUCKET: 'S3 bucket name',

      // Monitoring
      SENTRY_DSN: 'Sentry error tracking DSN',
      LOG_LEVEL: 'Logging level (debug, info, warn, error)',
    };

    return descriptions[varName] || `${varName} configuration`;
  }

  private addAuthEnvVars(workflow: any, componentName: string, allEnvVars: Map<string, string>): void {
    // Check if auth is configured in the workflow
    const authConfig = workflow.auth;
    if (!authConfig || !authConfig.enabled) {
      return;
    }

    const provider = authConfig.provider;
    const comp = workflow.stack[componentName];

    // Add Clerk environment variables
    if (provider === 'clerk') {
      if (comp?.type === 'backend') {
        if (!allEnvVars.has('CLERK_SECRET_KEY')) {
          allEnvVars.set('CLERK_SECRET_KEY', this.getVarDescription('CLERK_SECRET_KEY'));
        }
      }
      if (comp?.type === 'frontend') {
        if (!allEnvVars.has('VITE_CLERK_PUBLISHABLE_KEY')) {
          allEnvVars.set('VITE_CLERK_PUBLISHABLE_KEY', this.getVarDescription('VITE_CLERK_PUBLISHABLE_KEY'));
        }
      }
    }

    // Add JWT environment variables for local auth
    if (provider === 'local' || provider === 'jwt') {
      if (comp?.type === 'backend') {
        if (!allEnvVars.has('JWT_SECRET')) {
          allEnvVars.set('JWT_SECRET', this.getVarDescription('JWT_SECRET'));
        }
      }
    }
  }

  private groupVariables(varNames: string[]): Record<string, string[]> {
    const groups: Record<string, string[]> = {
      'Database': [],
      'Server': [],
      'API': [],
      'Authentication': [],
      'External Services': [],
      'Other': [],
    };

    for (const varName of varNames) {
      const upper = varName.toUpperCase();

      if (upper.includes('DATABASE') || upper.includes('POSTGRES') ||
          upper.includes('MYSQL') || upper.includes('MONGO') || upper.includes('REDIS')) {
        groups['Database'].push(varName);
      } else if (upper.includes('PORT') || upper.includes('HOST') ||
                 upper.includes('NODE_ENV')) {
        groups['Server'].push(varName);
      } else if (upper.includes('API') || upper.includes('URL')) {
        groups['API'].push(varName);
      } else if (upper.includes('JWT') || upper.includes('AUTH') ||
                 upper.includes('SECRET') || upper.includes('SESSION') ||
                 upper.includes('CLERK')) {
        groups['Authentication'].push(varName);
      } else if (upper.includes('SMTP') || upper.includes('AWS') ||
                 upper.includes('S3') || upper.includes('SENTRY')) {
        groups['External Services'].push(varName);
      } else {
        groups['Other'].push(varName);
      }
    }

    // Remove empty groups
    return Object.fromEntries(
      Object.entries(groups).filter(([_, vars]) => vars.length > 0)
    );
  }
}
