import { 
  ProcessDefinition,
  Activity,
  ProcessTrigger,
  PersonaType
} from './types.js';
import { ProcessStore } from './process-store.js';
import { TriggerSuggestionEngine } from './trigger-suggestion-engine.js';

interface BuilderSession {
  id: string;
  currentStep: number;
  processData: Partial<ProcessDefinition>;
  responses: Record<string, any>;
  suggestedTriggers?: ProcessTrigger[];
}

interface BuilderStep {
  id: string;
  type: 'question' | 'multiSelect' | 'confirm' | 'trigger-suggestion';
  prompt: string;
  helpText?: string;
  options?: string[];
  validation?: (value: any) => boolean | string;
}

export class ProcessBuilder {
  private sessions: Map<string, BuilderSession> = new Map();
  private store: ProcessStore;
  private triggerEngine: TriggerSuggestionEngine;
  
  constructor(store: ProcessStore) {
    this.store = store;
    this.triggerEngine = new TriggerSuggestionEngine(store);
  }
  
  // Simple API for tests
  async createProcess(options: {
    name: string;
    description?: string;
    persona?: PersonaType;
    activities?: Activity[];
    triggers?: ProcessTrigger[];
    variables?: Record<string, any>;
  }): Promise<ProcessDefinition> {
    if (!options.name || options.name.trim().length === 0) {
      throw new Error('Process name is required');
    }
    
    const process: ProcessDefinition = {
      id: `process-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
      name: options.name,
      description: options.description || '',
      version: '1.0.0',
      persona: options.persona,
      triggers: options.triggers || [],
      activities: options.activities || [],
      variables: options.variables || {},
      metadata: {
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString(),
        executionCount: 0
      }
    };
    
    await this.store.saveProcess(process);
    return process;
  }
  
  async addActivity(processId: string, activity: Activity, position?: number): Promise<ProcessDefinition> {
    const process = await this.store.getProcess(processId);
    if (!process) {
      throw new Error(`Process ${processId} not found`);
    }
    
    if (position !== undefined && position >= 0 && position <= process.activities.length) {
      process.activities.splice(position, 0, activity);
    } else {
      process.activities.push(activity);
    }
    
    process.metadata.updatedAt = new Date().toISOString();
    
    await this.store.saveProcess(process);
    return process;
  }
  
  async updateActivity(processId: string, activityId: string, updates: Partial<Activity>): Promise<ProcessDefinition> {
    const process = await this.store.getProcess(processId);
    if (!process) {
      throw new Error(`Process ${processId} not found`);
    }
    
    const activityIndex = process.activities.findIndex(a => a.id === activityId);
    if (activityIndex === -1) {
      throw new Error(`Activity ${activityId} not found`);
    }
    
    process.activities[activityIndex] = { ...process.activities[activityIndex], ...updates };
    process.metadata.updatedAt = new Date().toISOString();
    
    await this.store.saveProcess(process);
    return process;
  }
  
  async removeActivity(processId: string, activityId: string): Promise<ProcessDefinition> {
    const process = await this.store.getProcess(processId);
    if (!process) {
      throw new Error(`Process ${processId} not found`);
    }
    
    const originalLength = process.activities.length;
    process.activities = process.activities.filter(a => a.id !== activityId);
    
    if (process.activities.length === originalLength) {
      throw new Error('Activity not found');
    }
    
    process.metadata.updatedAt = new Date().toISOString();
    
    await this.store.saveProcess(process);
    return process;
  }
  
  async addTrigger(processId: string, trigger: ProcessTrigger): Promise<ProcessDefinition> {
    const process = await this.store.getProcess(processId);
    if (!process) {
      throw new Error(`Process ${processId} not found`);
    }
    
    // Check for duplicate trigger ID
    if (process.triggers.some(t => t.id === trigger.id)) {
      throw new Error(`Trigger with ID ${trigger.id} already exists`);
    }
    
    process.triggers.push(trigger);
    process.metadata.updatedAt = new Date().toISOString();
    
    await this.store.saveProcess(process);
    return process;
  }
  
  async updateTrigger(processId: string, triggerId: string, updates: Partial<ProcessTrigger>): Promise<ProcessDefinition> {
    const process = await this.store.getProcess(processId);
    if (!process) {
      throw new Error(`Process ${processId} not found`);
    }
    
    const triggerIndex = process.triggers.findIndex(t => t.id === triggerId);
    if (triggerIndex === -1) {
      throw new Error(`Trigger ${triggerId} not found`);
    }
    
    process.triggers[triggerIndex] = { ...process.triggers[triggerIndex], ...updates };
    process.metadata.updatedAt = new Date().toISOString();
    
    await this.store.saveProcess(process);
    return process;
  }
  
  async removeTrigger(processId: string, triggerId: string): Promise<ProcessDefinition> {
    const process = await this.store.getProcess(processId);
    if (!process) {
      throw new Error(`Process ${processId} not found`);
    }
    
    process.triggers = process.triggers.filter(t => t.id !== triggerId);
    process.metadata.updatedAt = new Date().toISOString();
    
    await this.store.saveProcess(process);
    return process;
  }
  
  async cloneProcess(processId: string, newName: string, options?: { persona?: string }): Promise<ProcessDefinition> {
    const process = await this.store.getProcess(processId);
    if (!process) {
      throw new Error(`Process ${processId} not found`);
    }
    
    const cloned: ProcessDefinition = {
      ...process,
      id: `process-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
      name: newName,
      persona: (options?.persona || process.persona) as PersonaType,
      // Deep clone activities with new IDs
      activities: process.activities.map(activity => ({
        ...activity,
        id: `${activity.id}-clone-${Date.now()}`
      })),
      // Deep clone triggers with new IDs
      triggers: process.triggers.map(trigger => ({
        ...trigger,
        id: `${trigger.id}-clone-${Date.now()}`
      })),
      // Deep clone variables
      variables: JSON.parse(JSON.stringify(process.variables)),
      metadata: {
        ...process.metadata,
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString(),
        executionCount: 0,
      }
    };
    
    await this.store.saveProcess(cloned);
    return cloned;
  }
  
  private steps: BuilderStep[] = [
    {
      id: 'name',
      type: 'question',
      prompt: '📝 What would you like to name this process?',
      helpText: 'Choose a descriptive name that explains what the process does',
      validation: (value: string) => {
        if (!value || value.trim().length < 3) {
          return 'Process name must be at least 3 characters';
        }
        return true;
      }
    },
    {
      id: 'description',
      type: 'question',
      prompt: '📄 Describe what this process does',
      helpText: 'This helps others understand the purpose of the process'
    },
    {
      id: 'persona',
      type: 'question',
      prompt: '👤 Who is this process for?',
      helpText: 'Choose a persona or type "custom" for general use',
      options: [
        'software-engineer',
        'architect',
        'cto',
        'ceo',
        'cfo',
        'marketing',
        'sales',
        'product-manager',
        'designer',
        'custom'
      ]
    },
    {
      id: 'activities',
      type: 'multiSelect',
      prompt: '📊 What activities should this process include?',
      helpText: 'Select all that apply (we\'ll configure details later)',
      options: [
        'Run automated tools',
        'Wait for human approval',
        'Delegate to AI agents',
        'Make conditional decisions',
        'Loop through items',
        'Call external APIs',
        'Generate reports',
        'Send notifications'
      ]
    },
    {
      id: 'trigger-review',
      type: 'trigger-suggestion',
      prompt: '🎯 Based on your process, here are my trigger recommendations',
      helpText: 'You can accept, modify, or choose alternatives'
    },
    {
      id: 'confirm',
      type: 'confirm',
      prompt: '✅ Ready to create your process?',
      helpText: 'You can always modify the process later'
    }
  ];

  async startProcessBuilder(): Promise<BuilderSession> {
    const session: BuilderSession = {
      id: this.generateSessionId(),
      currentStep: 0,
      processData: {},
      responses: {}
    };

    this.sessions.set(session.id, session);
    return session;
  }

  async continueBuilder(sessionId: string, response: any): Promise<BuilderResult> {
    const session = this.sessions.get(sessionId);
    if (!session) {
      return { valid: false, message: 'Session not found' };
    }

    const currentStep = this.steps[session.currentStep];
    
    // Validate response
    if (currentStep.validation) {
      const validationResult = currentStep.validation(response);
      if (validationResult !== true) {
        return { 
          valid: false, 
          message: typeof validationResult === 'string' ? validationResult : 'Invalid response' 
        };
      }
    }

    // Store response
    session.responses[currentStep.id] = response;

    // Process based on step type
    if (currentStep.type === 'trigger-suggestion') {
      // User selected a trigger configuration
      if (response.selectedTrigger) {
        session.processData.triggers = [response.selectedTrigger];
      }
    }

    // Move to next step
    session.currentStep++;

    // Check if we need to prepare trigger suggestions
    if (session.currentStep < this.steps.length) {
      const nextStep = this.steps[session.currentStep];
      if (nextStep.type === 'trigger-suggestion') {
        // Generate trigger suggestions based on collected data
        const tempProcess = this.buildProcessFromSession(session);
        const suggestions = await this.triggerEngine.suggestTriggers(tempProcess);
        session.suggestedTriggers = suggestions.map(s => s.trigger);
      }
    }

    // Check if complete
    if (session.currentStep >= this.steps.length) {
      const process = this.buildProcessFromSession(session);
      this.sessions.delete(sessionId);
      
      return {
        valid: true,
        completed: true,
        process
      };
    }

    return { valid: true };
  }

  formatCurrentStep(sessionId: string): string {
    const session = this.sessions.get(sessionId);
    if (!session) {
      return 'Session not found';
    }

    const step = this.steps[session.currentStep];
    const progress = `Step ${session.currentStep + 1} of ${this.steps.length}`;
    
    let output = `${progress}\n\n${step.prompt}\n`;
    
    if (step.helpText) {
      output += `💡 ${step.helpText}\n`;
    }

    if (step.type === 'trigger-suggestion' && session.suggestedTriggers) {
      output += '\n';
      
      session.suggestedTriggers.forEach((trigger, index) => {
        output += `\n${index + 1}. **${trigger.name}**\n`;
        output += `   Type: ${trigger.type}\n`;
        
        if (trigger.type === 'schedule' && trigger.config.cron) {
          output += `   Schedule: ${trigger.config.cron}\n`;
        }
        
        if (trigger.reasoning) {
          output += `   📝 ${trigger.reasoning}\n`;
        }
      });
      
      output += '\nWhich trigger would you like to use? (number or "custom" for manual setup)';
    } else if (step.options) {
      output += '\nOptions:\n';
      step.options.forEach((option, index) => {
        output += `${index + 1}. ${option}\n`;
      });
    }

    return output;
  }

  cancelSession(sessionId: string): void {
    this.sessions.delete(sessionId);
  }

  private buildProcessFromSession(session: BuilderSession): ProcessDefinition {
    const responses = session.responses;
    
    // Build activities based on selections
    const activities: Activity[] = [];
    const activitySelections = responses.activities || [];
    
    // Map selections to basic activity templates
    if (activitySelections.includes('Run automated tools')) {
      activities.push({
        id: 'tool-1',
        type: 'tool',
        name: 'Execute Tool',
        config: {
          toolName: 'check_project_status',
          toolArgs: {}
        }
      });
    }
    
    if (activitySelections.includes('Wait for human approval')) {
      activities.push({
        id: 'human-1',
        type: 'human',
        name: 'Human Approval',
        config: {
          prompt: 'Please review and approve',
          approvalType: 'any'
        }
      });
    }
    
    if (activitySelections.includes('Generate reports')) {
      activities.push({
        id: 'tool-2',
        type: 'tool',
        name: 'Generate Report',
        config: {
          toolName: 'generate_report',
          toolArgs: {}
        }
      });
    }

    const process: ProcessDefinition = {
      id: this.generateId('process'),
      name: responses.name || 'Unnamed Process',
      description: responses.description,
      version: '1.0.0',
      persona: responses.persona as PersonaType,
      triggers: session.processData.triggers || [],
      activities,
      variables: {},
      metadata: {
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString(),
        executionCount: 0
      }
    };

    return process;
  }

  private generateSessionId(): string {
    return `session-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
  }

  private generateId(prefix: string): string {
    return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
  }

  // Additional methods for test compatibility
  async setVariables(processId: string, variables: Record<string, any>, merge: boolean = false): Promise<ProcessDefinition> {
    const process = await this.store.getProcess(processId);
    if (!process) {
      throw new Error(`Process ${processId} not found`);
    }
    
    if (merge) {
      // Merge variables with existing ones
      process.variables = {
        ...process.variables,
        ...variables
      };
    } else {
      // Replace all variables
      process.variables = variables;
    }
    
    process.metadata.updatedAt = new Date().toISOString();
    await this.store.saveProcess(process);
    return process;
  }

  async validateProcess(processId: string): Promise<{ 
    isValid: boolean; 
    errors: Array<{ type: 'error'; field?: string; message: string }>;
    warnings: Array<{ type: 'warning'; field?: string; message: string }>;
  }> {
    const process = await this.store.getProcess(processId);
    if (!process) {
      return {
        isValid: false,
        errors: [{ type: 'error', message: `Process ${processId} not found` }],
        warnings: []
      };
    }
    
    const errors: Array<{ type: 'error'; field?: string; message: string }> = [];
    const warnings: Array<{ type: 'warning'; field?: string; message: string }> = [];
    
    // Validate required fields
    if (!process.name || process.name.trim().length === 0) {
      errors.push({ type: 'error', field: 'name', message: 'Process name is required' });
    }
    
    if (!process.id) {
      errors.push({ type: 'error', field: 'id', message: 'Process ID is required' });
    }
    
    // Validate activities
    if (!process.activities || process.activities.length === 0) {
      warnings.push({ type: 'warning', message: 'Process has no activities defined' });
    } else {
      // Validate each activity
      process.activities.forEach((activity, index) => {
        if (!activity.id) {
          errors.push({ 
            type: 'error', 
            field: `activities[${index}].id`, 
            message: `Activity at index ${index} is missing ID` 
          });
        }
        if (!activity.type) {
          errors.push({ 
            type: 'error', 
            field: `activities[${index}].type`, 
            message: `Activity ${activity.id || index} is missing type` 
          });
        }
        if (!activity.name) {
          errors.push({ 
            type: 'error', 
            field: `activities[${index}].name`, 
            message: `Activity ${activity.id || index} is missing name` 
          });
        }
        
        // Validate activity-specific config
        if (activity.type === 'tool' && (!activity.config || !activity.config.toolName)) {
          errors.push({ 
            type: 'error', 
            field: `activities[${index}].config`, 
            message: `Tool activity ${activity.id || index} is missing toolName in config` 
          });
        }
        
        // Validate condition syntax
        if (activity.condition && !this.isValidCondition(activity.condition)) {
          errors.push({ 
            type: 'error', 
            field: `activities[${index}].condition`, 
            message: `Invalid condition syntax for activity ${activity.id || index}` 
          });
        }
      });
    }
    
    // Validate triggers
    if (!process.triggers || process.triggers.length === 0) {
      warnings.push({ type: 'warning', message: 'Process has no triggers defined' });
    } else {
      process.triggers.forEach((trigger, index) => {
        if (!trigger.id) {
          errors.push({ 
            type: 'error', 
            field: `triggers[${index}].id`, 
            message: `Trigger at index ${index} is missing ID` 
          });
        }
        if (!trigger.type) {
          errors.push({ 
            type: 'error', 
            field: `triggers[${index}].type`, 
            message: `Trigger ${trigger.id || index} is missing type` 
          });
        }
        if (trigger.type === 'schedule') {
          if (!trigger.config?.cron) {
            errors.push({ 
              type: 'error', 
              field: `triggers[${index}].config.cron`, 
              message: `Schedule trigger ${trigger.id || index} is missing cron expression` 
            });
          } else if (!this.isValidCronExpression(trigger.config.cron)) {
            errors.push({ 
              type: 'error', 
              field: `triggers[${index}].config.cron`, 
              message: `Invalid cron expression for trigger ${trigger.id || index}` 
            });
          }
        }
      });
    }
    
    return {
      isValid: errors.length === 0,
      errors,
      warnings
    };
  }
  
  private isValidCondition(condition: string): boolean {
    // Simple validation for condition syntax
    // Should contain comparison operators and valid syntax
    const validOperators = ['===', '!==', '==', '!=', '>', '<', '>=', '<=', '&&', '||', '!'];
    const hasValidOperator = validOperators.some(op => condition.includes(op));
    
    // Check for invalid characters
    const invalidChars = ['@', '#', '$'];
    const hasInvalidChars = invalidChars.some(char => condition.includes(char));
    
    // Check for balanced parentheses
    let parenCount = 0;
    for (const char of condition) {
      if (char === '(') parenCount++;
      if (char === ')') parenCount--;
      if (parenCount < 0) return false;
    }
    
    return hasValidOperator && !hasInvalidChars && parenCount === 0;
  }
  
  private isValidCronExpression(cron: string): boolean {
    // Basic cron expression validation
    // Format: minute hour day month weekday
    const parts = cron.trim().split(/\s+/);
    
    // Must have exactly 5 parts for standard cron
    if (parts.length !== 5) {
      return false;
    }
    
    // Validate each part
    const patterns = [
      /^(\*|[0-5]?\d)(,(\*|[0-5]?\d))*$/, // minute (0-59)
      /^(\*|[01]?\d|2[0-3])(,(\*|[01]?\d|2[0-3]))*$/, // hour (0-23)
      /^(\*|[1-9]|[12]\d|3[01])(,(\*|[1-9]|[12]\d|3[01]))*$/, // day (1-31)
      /^(\*|[1-9]|1[0-2])(,(\*|[1-9]|1[0-2]))*$/, // month (1-12)
      /^(\*|[0-6])(,(\*|[0-6]))*$/ // weekday (0-6)
    ];
    
    for (let i = 0; i < parts.length; i++) {
      const part = parts[i];
      
      // Handle ranges (e.g., 1-5)
      if (part.includes('-')) {
        const rangeParts = part.split('-');
        if (rangeParts.length !== 2) return false;
        // For simplicity, just check it's not completely invalid
        if (!/^\d+$/.test(rangeParts[0]) || !/^\d+$/.test(rangeParts[1])) {
          return false;
        }
        continue;
      }
      
      // Handle step values (e.g., */5)
      if (part.includes('/')) {
        const stepParts = part.split('/');
        if (stepParts.length !== 2) return false;
        if (stepParts[0] !== '*' && !/^\d+$/.test(stepParts[0])) return false;
        if (!/^\d+$/.test(stepParts[1])) return false;
        continue;
      }
      
      // Validate against pattern
      if (!patterns[i].test(part)) {
        return false;
      }
    }
    
    return true;
  }

  async exportProcess(processId: string): Promise<any> {
    const process = await this.store.getProcess(processId);
    if (!process) {
      throw new Error(`Process ${processId} not found`);
    }
    
    // Create export format without runtime data
    const exportData = {
      name: process.name,
      description: process.description,
      version: process.version,
      persona: process.persona,
      triggers: process.triggers,
      activities: process.activities,
      variables: process.variables,
      exportVersion: '1.0',
      exportedAt: new Date().toISOString()
    };
    
    return exportData;
  }

  async importProcess(data: any, options?: { namePrefix?: string }): Promise<ProcessDefinition> {
    let importData: any;
    
    // Handle both JSON string and object input
    if (typeof data === 'string') {
      try {
        importData = JSON.parse(data);
      } catch (error) {
        throw new Error('Invalid JSON format');
      }
    } else {
      importData = data;
    }
    
    // Build process from import data
    const process: ProcessDefinition = {
      id: this.generateId('process'),
      name: (options?.namePrefix || '') + (importData.name || 'Imported Process'),
      description: importData.description,
      version: importData.version || '1.0.0',
      persona: importData.persona || 'custom',
      triggers: importData.triggers || [],
      activities: importData.activities || [],
      variables: importData.variables || {},
      metadata: {
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString(),
        executionCount: 0,
      }
    };
    
    // Save the imported process first
    await this.store.saveProcess(process);
    
    // Then validate it
    const validation = await this.validateProcess(process.id);
    if (!validation.isValid) {
      // Remove invalid process
      await this.store.deleteProcess(process.id);
      const errorMessages = validation.errors.map(e => e.message).join(', ');
      throw new Error(`Invalid process: ${errorMessages}`);
    }
    
    return process;
  }
}

interface BuilderResult {
  valid: boolean;
  message?: string;
  completed?: boolean;
  process?: ProcessDefinition;
}