/**
 * Workflow Execution Engine
 * Analyzes workflows and provides execution order for code generation
 * Updated for flexible stack system
 */

import { Workflow, StackComponent, Step, Action, Implementation } from '../types/workflow';
import { DAG } from './dag';

export interface ExecutionPlan {
  workflow: Workflow;
  componentPlans: Map<string, ComponentExecutionPlan>;
}

export interface ComponentExecutionPlan {
  componentName: string;
  component: StackComponent;
  actionPlans: Map<string, ActionExecutionPlan>;
  endpointPlans: Map<string, ImplementationExecutionPlan>;
}

export interface ActionExecutionPlan {
  actionId: string;
  componentId: string;
  dag: DAG;
  executionLevels: Step[][];
  entryPoints: Step[];
  exitPoints: Step[];
}

export interface ImplementationExecutionPlan {
  endpointId: string;
  dag: DAG;
  executionLevels: Step[][];
  entryPoints: Step[];
  exitPoints: Step[];
}

export class WorkflowEngine {
  /**
   * Analyze a workflow and create an execution plan
   */
  analyze(workflow: Workflow): ExecutionPlan {
    const plan: ExecutionPlan = {
      workflow,
      componentPlans: new Map(),
    };

    // Analyze each stack component if present
    if (workflow.stack && Object.keys(workflow.stack).length > 0) {
      for (const [componentName, component] of Object.entries(workflow.stack)) {
        const componentPlan = this.analyzeComponent(componentName, component);
        plan.componentPlans.set(componentName, componentPlan);
      }
    }

    return plan;
  }

  /**
   * Analyze a single stack component
   */
  private analyzeComponent(
    componentName: string,
    component: StackComponent
  ): ComponentExecutionPlan {
    const plan: ComponentExecutionPlan = {
      componentName,
      component,
      actionPlans: new Map(),
      endpointPlans: new Map(),
    };

    // Analyze frontend-like components (with UI components and actions)
    if (component.components) {
      for (const uiComponent of component.components) {
        if (uiComponent.actions) {
          for (const action of uiComponent.actions) {
            const actionPlan = this.analyzeAction(action, uiComponent.id, componentName);
            plan.actionPlans.set(action.id, actionPlan);
          }
        }
      }
    }

    // Analyze backend-like components (with endpoints)
    if (component.endpoints) {
      for (const endpoint of component.endpoints) {
        if (endpoint.implementation) {
          const implPlan = this.analyzeImplementation(
            endpoint.implementation,
            endpoint.id,
            componentName
          );
          plan.endpointPlans.set(endpoint.id, implPlan);
        }
      }
    }

    return plan;
  }

  /**
   * Analyze a frontend action and build execution plan
   */
  private analyzeAction(action: Action, componentId: string, stackComponentName: string): ActionExecutionPlan {
    const dag = new DAG();

    // Only build DAG if action has steps (steps are optional for scaffold-only generation)
    if (action.steps && action.steps.length > 0) {
      try {
        dag.build(action.steps);
      } catch (error) {
        throw new Error(
          `Error analyzing action "${action.id}" in component "${componentId}" of stack "${stackComponentName}": ${
            error instanceof Error ? error.message : String(error)
          }`
        );
      }
    }

    return {
      actionId: action.id,
      componentId,
      dag,
      executionLevels: dag.getExecutionLevels(),
      entryPoints: dag.getEntryPoints(),
      exitPoints: dag.getExitPoints(),
    };
  }

  /**
   * Analyze a backend implementation and build execution plan
   */
  private analyzeImplementation(
    implementation: Implementation,
    endpointId: string,
    stackComponentName: string
  ): ImplementationExecutionPlan {
    const dag = new DAG();

    try {
      dag.build(implementation.steps);
    } catch (error) {
      throw new Error(
        `Error analyzing implementation for endpoint "${endpointId}" in stack "${stackComponentName}": ${
          error instanceof Error ? error.message : String(error)
        }`
      );
    }

    return {
      endpointId,
      dag,
      executionLevels: dag.getExecutionLevels(),
      entryPoints: dag.getEntryPoints(),
      exitPoints: dag.getExitPoints(),
    };
  }

  /**
   * Validate workflow for common issues
   */
  validate(workflow: Workflow): ValidationResult {
    const errors: string[] = [];
    const warnings: string[] = [];

    // Check for at least one stack component OR enhanced features
    const hasStackComponents = workflow.stack && Object.keys(workflow.stack).length > 0;
    const hasEnhancedFeatures = !!(
      workflow.migrations ||
      workflow.error_handling ||
      workflow.rate_limiting ||
      workflow.email ||
      workflow.background_jobs ||
      workflow.monitoring ||
      workflow.security ||
      workflow.performance ||
      workflow.i18n ||
      workflow.feature_flags ||
      workflow.access_control ||
      workflow.analytics ||
      workflow.testing
    );

    if (!hasStackComponents && !hasEnhancedFeatures) {
      errors.push('Workflow must define at least one stack component or enhanced feature');
    }

    // Validate stack component dependencies
    if (workflow.stack) {
      for (const [componentName, component] of Object.entries(workflow.stack)) {
        if (component.dependsOn) {
          for (const depName of component.dependsOn) {
            if (!workflow.stack[depName]) {
              errors.push(
                `Stack component "${componentName}" depends on "${depName}" which doesn't exist`
              );
            }
          }
        }
      }
    }

    // Validate API references between components
    if (workflow.stack) {
      for (const [_componentName, component] of Object.entries(workflow.stack)) {
        if (component.components) {
          for (const uiComponent of component.components) {
            if (uiComponent.actions) {
              for (const action of uiComponent.actions) {
                // Only validate steps if they exist (steps are optional for scaffold-only generation)
                if (action.steps) {
                  for (const step of action.steps) {
                    // Check if API calls reference existing endpoints
                    if (step.action === 'api-call' && step.api_workflow) {
                      const [targetComponent, endpointId] = step.api_workflow.split('.');
                      const targetStack = workflow.stack[targetComponent];

                      if (!targetStack) {
                        errors.push(
                          `Action "${action.id}" references non-existent stack component "${targetComponent}"`
                        );
                      } else if (targetStack.endpoints) {
                        const endpointExists = targetStack.endpoints.some(
                          e => e.id === endpointId
                        );
                        if (!endpointExists) {
                          errors.push(
                            `Action "${action.id}" references non-existent endpoint "${endpointId}" in "${targetComponent}"`
                          );
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }

    // Check for orphaned workflows (no actors defined)
    if (!workflow.workflow.actors || workflow.workflow.actors.length === 0) {
      warnings.push('Workflow has no actors defined - who is this for?');
    }

    // === ENHANCED SCHEMA VALIDATION ===

    // Validate Testing configuration
    if (workflow.testing) {
      const testing = workflow.testing;

      // Check coverage thresholds are valid percentages
      if (testing.coverage?.threshold?.global) {
        const threshold = testing.coverage.threshold.global;
        const checkPercentage = (value: number | undefined, name: string) => {
          if (value !== undefined && (value < 0 || value > 100)) {
            errors.push(`Testing coverage threshold for ${name} must be between 0 and 100`);
          }
        };
        checkPercentage(threshold.statements, 'statements');
        checkPercentage(threshold.branches, 'branches');
        checkPercentage(threshold.functions, 'functions');
        checkPercentage(threshold.lines, 'lines');
      }

      // Warn if using legacy format
      if (testing.unit_tests || testing.integration_tests || testing.e2e_tests) {
        warnings.push('Using legacy testing format. Consider migrating to the new frameworks-based configuration.');
      }
    }

    // Validate Monitoring configuration
    if (workflow.monitoring) {
      const monitoring = workflow.monitoring;

      // Check tracing sampling rate
      if (monitoring.tracing?.sampling_rate !== undefined) {
        const rate = monitoring.tracing.sampling_rate;
        if (rate < 0 || rate > 1) {
          errors.push('Monitoring tracing sampling_rate must be between 0 and 1');
        }
      }

      // Warn if no error tracking configured
      if (!monitoring.error_tracking) {
        warnings.push('No error tracking configured. Consider adding Sentry or similar for production monitoring.');
      }
    }

    // Validate Security configuration
    if (workflow.security) {
      const security = workflow.security;

      // Warn if compliance regulations are set but encryption is not enabled
      if (security.compliance?.regulations && security.compliance.regulations.length > 0) {
        if (!security.encryption?.at_rest || !security.encryption?.in_transit) {
          warnings.push(
            `Compliance regulations (${security.compliance.regulations.join(', ')}) are specified but encryption is not fully enabled. Consider enabling both at-rest and in-transit encryption.`
          );
        }
      }

      // Check audit logging retention format
      if (security.audit_logging?.retention) {
        const retention = security.audit_logging.retention;
        // Basic validation - should be like "30d", "1y", etc.
        if (!/^\d+[dwmy]$/.test(retention)) {
          warnings.push(
            `Audit logging retention "${retention}" should be in format like "30d" (days), "12w" (weeks), "6m" (months), or "1y" (years)`
          );
        }
      }
    }

    // Validate Performance configuration
    if (workflow.performance) {
      const perf = workflow.performance;

      // Warn if CDN is configured but no provider specified
      if (perf.cdn && !perf.cdn.provider) {
        warnings.push('CDN is configured but no provider specified. Add a provider like "cloudflare" or "cloudfront".');
      }

      // Check database sharding configuration
      if (perf.database?.sharding && !perf.database.sharding.strategy) {
        warnings.push('Database sharding is configured but no strategy specified.');
      }
    }

    // Validate Internationalization configuration
    if (workflow.i18n) {
      const i18n = workflow.i18n;

      // Check that at least one locale is supported
      if (!i18n.supported_locales || i18n.supported_locales.length === 0) {
        warnings.push('Internationalization is configured but no supported_locales specified.');
      }

      // Check default locale is in supported locales
      if (i18n.default_locale && i18n.supported_locales) {
        if (!i18n.supported_locales.includes(i18n.default_locale)) {
          errors.push(`Default locale "${i18n.default_locale}" is not in supported_locales list.`);
        }
      }
    }

    // Validate Feature Flags configuration
    if (workflow.feature_flags) {
      const flags = workflow.feature_flags;

      // Check for duplicate flag names
      if (flags.flags) {
        const names = flags.flags.map(f => f.name);
        const duplicates = names.filter((name, index) => names.indexOf(name) !== index);
        if (duplicates.length > 0) {
          errors.push(`Duplicate feature flag names found: ${duplicates.join(', ')}`);
        }
      }
    }

    // Validate Access Control configuration
    if (workflow.access_control) {
      const ac = workflow.access_control;

      // Check for duplicate role names
      if (ac.roles) {
        const roleNames = ac.roles.map(r => r.name);
        const duplicates = roleNames.filter((name, index) => roleNames.indexOf(name) !== index);
        if (duplicates.length > 0) {
          errors.push(`Duplicate role names found: ${duplicates.join(', ')}`);
        }
      }
    }

    // Validate API configuration
    if (workflow.api) {
      const api = workflow.api;

      // Warn if CORS is enabled with no origins
      if (api.cors?.enabled && (!api.cors.origins || api.cors.origins.length === 0)) {
        warnings.push('CORS is enabled but no origins specified. This may block requests from all domains.');
      }
    }

    // Validate Deployment configuration
    if (workflow.deployment) {
      const deploy = workflow.deployment;

      // Validate multi-region configuration
      if (deploy.strategy === 'multi_region' && (!deploy.regions || deploy.regions.length === 0)) {
        errors.push('Deployment strategy is multi_region but no regions specified.');
      }

      // Check disaster recovery configuration
      if (deploy.disaster_recovery) {
        const dr = deploy.disaster_recovery;
        if (dr.failover === 'automatic' && (!dr.replica_regions || dr.replica_regions.length === 0)) {
          warnings.push('Disaster recovery failover is automatic but no replica_regions specified.');
        }
      }
    }

    // Validate Analytics configuration
    if (workflow.analytics) {
      const analytics = workflow.analytics;

      // Check for duplicate event names in custom events
      if (analytics.tracking?.custom_events) {
        const eventNames = analytics.tracking.custom_events.map((e: any) => e.name);
        const duplicates = eventNames.filter((name: any, index: number) => eventNames.indexOf(name) !== index);
        if (duplicates.length > 0) {
          warnings.push(`Duplicate analytics event names found: ${duplicates.join(', ')}`);
        }
      }
    }

    return {
      valid: errors.length === 0,
      errors,
      warnings,
    };
  }

  /**
   * Extract all template variables used in a workflow
   */
  extractTemplateVariables(workflow: Workflow): Set<string> {
    const variables = new Set<string>();
    const templateRegex = /\{\{\s*([^}]+)\s*\}\}/g;

    const extractFromObject = (obj: unknown): void => {
      if (typeof obj === 'string') {
        let match;
        while ((match = templateRegex.exec(obj)) !== null) {
          variables.add(match[1].trim());
        }
      } else if (Array.isArray(obj)) {
        obj.forEach(extractFromObject);
      } else if (obj && typeof obj === 'object') {
        Object.values(obj).forEach(extractFromObject);
      }
    };

    extractFromObject(workflow);

    return variables;
  }

  /**
   * Find all workflows that this workflow depends on
   */
  getDependencies(workflow: Workflow): string[] {
    const dependencies: string[] = [];

    if (workflow.workflow.relationships) {
      for (const rel of workflow.workflow.relationships) {
        if (rel.type === 'prerequisite' || rel.type === 'dependent') {
          dependencies.push(rel.workflow);
        }
      }
    }

    return dependencies;
  }

  /**
   * Find all workflows that depend on this workflow
   */
  getDependents(workflow: Workflow): string[] {
    const dependents: string[] = [];

    if (workflow.workflow.relationships) {
      for (const rel of workflow.workflow.relationships) {
        if (rel.type === 'related') {
          dependents.push(rel.workflow);
        }
      }
    }

    return dependents;
  }

  /**
   * Get stack components by type
   */
  getComponentsByType(workflow: Workflow, type: string): Array<[string, StackComponent]> {
    const components: Array<[string, StackComponent]> = [];

    for (const [name, component] of Object.entries(workflow.stack)) {
      if (component.type === type) {
        components.push([name, component]);
      }
    }

    return components;
  }
}

export interface ValidationResult {
  valid: boolean;
  errors: string[];
  warnings: string[];
}
