import ApiService from './ApiService';

/**
 * OrchestrationService - Handles complex translation workflows and operations
 * 
 * This service orchestrates multiple API calls, manages state across operations,
 * handles retries, progress tracking, and complex translation workflows.
 */
class OrchestrationService {
  constructor() {
    this.apiService = ApiService;
    this.activeJobs = new Map();
    this.workflowState = new Map();
    
    // Configuration
    this.config = {
      maxRetries: 3,
      retryDelayMs: 1000,
      batchTimeout: 300000, // 5 minutes
      pollInterval: 2000, // 2 seconds
      maxParallelJobs: 5,
      urlTranslationTimeout: 600000 // 10 minutes for URL translation workflow
    };
  }

  /**
   * Initialize orchestration service with API key
   */
  initialize(apiKey) {
    this.apiService.setApiKey(apiKey);
    return this;
  }

  /**
   * URL Translation Workflow
   * Complete workflow for translating a WordPress page URL
   */
  async translateUrl(params) {
    const {
      pageUrl,
      targetLanguage,
      sourceLanguage,
      allowExternal = false,
      onProgress
    } = params;

    const workflowId = this._generateId();
    let currentProgress = 0;
    let totalNodes = 0;
    let completedNodes = 0;
    let jobIds = [];

    try {
      this.workflowState.set(workflowId, {
        id: workflowId,
        status: 'running',
        startTime: Date.now(),
        currentStep: 'fetching-html',
        totalNodes: 0,
        completedNodes: 0,
        errors: []
      });

      // Step 1: cURL the HTML content
      if (onProgress) {
        onProgress({
          workflowId,
          step: 'fetching-html',
          progress: 0,
          message: 'Fetching HTML content...'
        });
      }

      const htmlResponse = await this._fetchPageHtml(pageUrl, allowExternal);
      
      // Step 2: Process HTML with GraphQL
      if (onProgress) {
        onProgress({
          workflowId,
          step: 'processing-html',
          progress: 10,
          message: 'Processing HTML content...'
        });
      }

      const processedHtml = await this.apiService.processHtml({
        html: htmlResponse.html,
        base64Encode: true,
        sourceLanguage: sourceLanguage
      });

      // Step 3: Count total nodes and set up progress tracking
      totalNodes = processedHtml.count;
      
      this.workflowState.get(workflowId).totalNodes = totalNodes;
      this.workflowState.get(workflowId).currentStep = 'translating-nodes';

      if (totalNodes === 0) {
        return {
          workflowId,
          success: true,
          message: 'No translatable content found',
          totalNodes: 0,
          completedNodes: 0,
          progress: 100
        };
      }

      if (onProgress) {
        onProgress({
          workflowId,
          step: 'translating-nodes',
          progress: 20,
          message: `Found ${totalNodes} text nodes to translate...`,
          totalNodes,
          completedNodes: 0
        });
      }

      // Step 4: Submit each text node for translation (batch mode)
      const translationJobs = [];
      
      for (let i = 0; i < processedHtml.textNodes.length; i++) {
        const textNode = processedHtml.textNodes[i];
        
        try {
          const translationResult = await this.apiService.translateContent({
            input: textNode,
            inputFormat: 'text',
            targetLanguage: targetLanguage,
            sourceLanguage: sourceLanguage,
            mode: 'batch'
          });

          if (translationResult.jobId) {
            translationJobs.push({
              jobId: translationResult.jobId,
              nodeIndex: i,
              status: 'pending'
            });
            jobIds.push(translationResult.jobId);
          }
        } catch (error) {
          // If submission fails, count as error but continue
          completedNodes++;
          this.workflowState.get(workflowId).errors.push({
            nodeIndex: i,
            error: error.message
          });
        }
      }

      // Step 5: Poll results for each job
      const startTime = Date.now();
      const timeout = this.config.urlTranslationTimeout;
      
      while (completedNodes < totalNodes && (Date.now() - startTime) < timeout) {
        const pendingJobs = translationJobs.filter(job => job.status === 'pending');
        
        if (pendingJobs.length === 0) {
          break;
        }

        // Poll each pending job
        for (const job of pendingJobs) {
          try {
            const result = await this.apiService.getTranslationResults(job.jobId);
            
            if (result.status === 'complete' || result.status === 'failed') {
              job.status = result.status;
              completedNodes++;
              
              this.workflowState.get(workflowId).completedNodes = completedNodes;
              
              // Calculate progress (20% base + 80% for translation completion)
              const translationProgress = (completedNodes / totalNodes) * 80;
              const totalProgress = Math.min(20 + translationProgress, 100);
              
              if (onProgress) {
                onProgress({
                  workflowId,
                  step: 'translating-nodes',
                  progress: totalProgress,
                  message: `Translated ${completedNodes} of ${totalNodes} text nodes...`,
                  totalNodes,
                  completedNodes
                });
              }

              if (result.status === 'failed') {
                this.workflowState.get(workflowId).errors.push({
                  jobId: job.jobId,
                  nodeIndex: job.nodeIndex,
                  error: result.error || 'Translation failed'
                });
              }
            }
          } catch (error) {
            // If we can't get results, mark as error and continue
            job.status = 'error';
            completedNodes++;
            this.workflowState.get(workflowId).errors.push({
              jobId: job.jobId,
              nodeIndex: job.nodeIndex,
              error: error.message
            });
          }
        }

        // Wait before next poll cycle
        if (completedNodes < totalNodes) {
          await this._delay(this.config.pollInterval);
        }
      }

      // Step 6: Final status update
      const finalState = this.workflowState.get(workflowId);
      finalState.status = 'completed';
      finalState.endTime = Date.now();
      finalState.duration = finalState.endTime - finalState.startTime;

      const successfulTranslations = totalNodes - finalState.errors.length;
      
      if (onProgress) {
        onProgress({
          workflowId,
          step: 'completed',
          progress: 100,
          message: `Translation completed! ${successfulTranslations}/${totalNodes} nodes translated successfully.`,
          totalNodes,
          completedNodes
        });
      }

      return {
        workflowId,
        success: true,
        totalNodes,
        completedNodes,
        successfulTranslations,
        errors: finalState.errors,
        duration: finalState.duration,
        progress: 100
      };

    } catch (error) {
      const state = this.workflowState.get(workflowId);
      state.status = 'failed';
      state.endTime = Date.now();
      state.finalError = error.message;

      if (onProgress) {
        onProgress({
          workflowId,
          step: 'error',
          progress: 0,
          message: `Translation failed: ${error.message}`,
          error: true
        });
      }

      throw error;
    }
  }

  /**
   * Get available languages for the language selector
   */
  async getLanguageOptions() {
    try {
      const availableLanguages = await this.apiService.getAvailableLanguages();
      
      return {
        success: true,
        targetLanguages: availableLanguages.supportedLanguages || [],
        sourceLanguages: availableLanguages.sourceLanguages || []
      };
    } catch (error) {
      throw new Error(`Failed to get language options: ${error.message}`);
    }
  }

  /**
   * Fetch page HTML via WordPress AJAX (server-side PHP)
   */
  async _fetchPageHtml(pageUrl, allowExternal = false) {
    try {
      return await this.apiService.fetchPageHtml(pageUrl, allowExternal);
    } catch (error) {
      throw new Error(`Failed to fetch HTML from ${pageUrl}: ${error.message}`);
    }
  }

  /**
   * Execute a complete translation workflow
   * 
   * @param {Object} workflow - Workflow configuration
   * @param {Array} workflow.steps - Array of workflow steps
   * @param {Function} workflow.onProgress - Progress callback
   * @param {Function} workflow.onComplete - Completion callback
   * @param {Function} workflow.onError - Error callback
   */
  async executeWorkflow(workflow) {
    const workflowId = this._generateId();
    
    try {
      this.workflowState.set(workflowId, {
        id: workflowId,
        status: 'running',
        currentStep: 0,
        totalSteps: workflow.steps.length,
        results: [],
        startTime: Date.now(),
        errors: []
      });

      const results = [];
      
      for (let i = 0; i < workflow.steps.length; i++) {
        const step = workflow.steps[i];
        const state = this.workflowState.get(workflowId);
        
        state.currentStep = i + 1;
        state.currentStepName = step.name;
        
        // Call progress callback
        if (workflow.onProgress) {
          workflow.onProgress({
            workflowId,
            step: i + 1,
            totalSteps: workflow.steps.length,
            stepName: step.name,
            status: 'running'
          });
        }

        try {
          const stepResult = await this._executeStep(step, results);
          results.push(stepResult);
          state.results.push(stepResult);
        } catch (error) {
          state.errors.push({ step: i + 1, error: error.message });
          
          if (step.required !== false) {
            throw new Error(`Required step "${step.name}" failed: ${error.message}`);
          } else {
            // Continue with optional step failure
            results.push({ success: false, error: error.message, step: step.name });
          }
        }
      }

      // Mark workflow as complete
      const finalState = this.workflowState.get(workflowId);
      finalState.status = 'completed';
      finalState.endTime = Date.now();
      finalState.duration = finalState.endTime - finalState.startTime;

      if (workflow.onComplete) {
        workflow.onComplete({ workflowId, results, state: finalState });
      }

      return { workflowId, results, state: finalState };

    } catch (error) {
      const state = this.workflowState.get(workflowId);
      state.status = 'failed';
      state.endTime = Date.now();
      state.finalError = error.message;

      if (workflow.onError) {
        workflow.onError({ workflowId, error, state });
      }

      throw error;
    }
  }

  /**
   * Smart Translation Workflow
   * Detects language → Validates → Translates with retry logic
   */
  async smartTranslate(params) {
    const {
      input,
      inputFormat = 'text',
      targetLanguage,
      sourceLanguage,
      mode = 'auto', // 'auto', 'real-time', 'batch'
      validateLanguages = true,
      maxRetries = this.config.maxRetries,
      onProgress
    } = params;

    const workflow = {
      steps: [],
      onProgress
    };

    // Step 1: Validate available languages (if requested)
    if (validateLanguages) {
      workflow.steps.push({
        name: 'validate-languages',
        action: 'getAvailableLanguages',
        required: true
      });
    }

    // Step 2: Detect source language (if not provided)
    if (!sourceLanguage) {
      workflow.steps.push({
        name: 'detect-language',
        action: 'detectLanguage',
        params: { input, inputFormat },
        required: false
      });
    }

    // Step 3: Determine optimal translation mode
    if (mode === 'auto') {
      workflow.steps.push({
        name: 'determine-mode',
        action: 'custom',
        handler: (prevResults) => {
          const inputLength = input.length;
          const determinedMode = inputLength > 1000 ? 'batch' : 'real-time';
          return { mode: determinedMode };
        },
        required: true
      });
    }

    // Step 4: Execute translation
    workflow.steps.push({
      name: 'translate',
      action: 'translateContent',
      params: (prevResults) => {
        const finalMode = mode === 'auto' ? 
          prevResults.find(r => r.step === 'determine-mode')?.mode || 'real-time' : 
          mode;
        
        return {
          input,
          inputFormat,
          targetLanguage,
          sourceLanguage: sourceLanguage || prevResults.find(r => r.step === 'detect-language')?.detectedLanguage,
          mode: finalMode
        };
      },
      required: true,
      retries: maxRetries
    });

    // Step 5: Handle batch results (if needed)
    workflow.steps.push({
      name: 'handle-batch-results',
      action: 'custom',
      handler: async (prevResults) => {
        const translateResult = prevResults.find(r => r.step === 'translate');
        
        if (translateResult?.jobId) {
          // Poll for batch results
          return await this.pollBatchTranslation(translateResult.jobId, {
            timeout: this.config.batchTimeout,
            pollInterval: this.config.pollInterval,
            onProgress: (progress) => {
              if (onProgress) {
                onProgress({
                  step: 5,
                  totalSteps: workflow.steps.length,
                  stepName: 'handle-batch-results',
                  status: 'polling',
                  progress
                });
              }
            }
          });
        }
        
        return { skipped: true };
      },
      required: false
    });

    return await this.executeWorkflow(workflow);
  }

  /**
   * Batch Translation Orchestrator
   * Handles multiple translations in parallel with smart batching
   */
  async batchTranslate(items, options = {}) {
    const {
      targetLanguage,
      sourceLanguage,
      inputFormat = 'text',
      maxParallel = this.config.maxParallelJobs,
      onProgress,
      onItemComplete
    } = options;

    const batchId = this._generateId();
    const results = [];
    const errors = [];

    // Process items in parallel batches
    const chunks = this._chunkArray(items, maxParallel);
    let processedCount = 0;

    for (const chunk of chunks) {
      const promises = chunk.map(async (item, index) => {
        try {
          const result = await this.smartTranslate({
            input: item.content || item,
            inputFormat,
            targetLanguage,
            sourceLanguage,
            mode: 'batch',
            onProgress: (progress) => {
              if (onProgress) {
                onProgress({
                  batchId,
                  totalItems: items.length,
                  processedItems: processedCount,
                  currentItem: index,
                  itemProgress: progress
                });
              }
            }
          });

          processedCount++;
          
          if (onItemComplete) {
            onItemComplete({ item, result, index: processedCount - 1 });
          }

          return { success: true, item, result };
        } catch (error) {
          processedCount++;
          const errorResult = { success: false, item, error: error.message };
          errors.push(errorResult);
          return errorResult;
        }
      });

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

    return {
      batchId,
      totalItems: items.length,
      successCount: results.filter(r => r.success).length,
      errorCount: errors.length,
      results,
      errors
    };
  }

  /**
   * Poll for batch translation results with smart backoff
   */
  async pollBatchTranslation(jobId, options = {}) {
    const {
      timeout = this.config.batchTimeout,
      pollInterval = this.config.pollInterval,
      onProgress
    } = options;

    const startTime = Date.now();
    let attempt = 0;
    
    while (Date.now() - startTime < timeout) {
      attempt++;
      
      try {
        const result = await this.apiService.getTranslationResults(jobId);
        
        if (result.status === 'complete') {
          return result;
        } else if (result.status === 'failed') {
          throw new Error(result.error || 'Translation failed');
        } else if (result.status === 'processing') {
          // Provide progress update
          if (onProgress) {
            onProgress({
              status: 'processing',
              attempt,
              estimatedWaitSeconds: result.estimatedWaitSeconds,
              nextPollAfter: result.nextPollAfter
            });
          }

          // Use server-suggested wait time or fallback to pollInterval
          const waitTime = result.estimatedWaitSeconds ? 
            Math.min(result.estimatedWaitSeconds * 1000, pollInterval) : 
            pollInterval;
          
          await this._delay(waitTime);
        }
      } catch (error) {
        // Retry with exponential backoff for transient errors
        if (attempt < this.config.maxRetries) {
          const backoffDelay = this.config.retryDelayMs * Math.pow(2, attempt - 1);
          await this._delay(backoffDelay);
          continue;
        }
        throw error;
      }
    }

    throw new Error(`Batch translation timed out after ${timeout}ms`);
  }

  /**
   * Complex Page Translation Workflow
   * Handles full page translation with content extraction and reassembly
   */
  async translatePage(params) {
    const {
      pageUrl,
      targetLanguage,
      sourceLanguage,
      preserveFormatting = true,
      translateMetadata = true,
      onProgress
    } = params;

    const workflow = {
      steps: [
        {
          name: 'fetch-page-content',
          action: 'custom',
          handler: async () => {
            // In a real implementation, this would fetch page content server-side
            // For now, we'll simulate content extraction
            return {
              title: 'Sample Page Title',
              content: '<h1>Welcome</h1><p>This is sample content</p>',
              metadata: { description: 'Sample page description' }
            };
          },
          required: true
        },
        {
          name: 'translate-title',
          action: 'translateContent',
          params: (prevResults) => ({
            input: prevResults[0].title,
            inputFormat: 'text',
            targetLanguage,
            sourceLanguage,
            mode: 'real-time'
          }),
          required: true
        },
        {
          name: 'translate-content',
          action: 'translateContent',
          params: (prevResults) => ({
            input: prevResults[0].content,
            inputFormat: 'html',
            targetLanguage,
            sourceLanguage,
            mode: 'batch'
          }),
          required: true
        }
      ],
      onProgress
    };

    // Add metadata translation if requested
    if (translateMetadata) {
      workflow.steps.push({
        name: 'translate-metadata',
        action: 'translateContent',
        params: (prevResults) => ({
          input: prevResults[0].metadata.description,
          inputFormat: 'text',
          targetLanguage,
          sourceLanguage,
          mode: 'real-time'
        }),
        required: false
      });
    }

    return await this.executeWorkflow(workflow);
  }

  /**
   * Get workflow status
   */
  getWorkflowStatus(workflowId) {
    return this.workflowState.get(workflowId);
  }

  /**
   * Cancel a running workflow
   */
  cancelWorkflow(workflowId) {
    const state = this.workflowState.get(workflowId);
    if (state && state.status === 'running') {
      state.status = 'cancelled';
      state.endTime = Date.now();
      return true;
    }
    return false;
  }

  /**
   * Cleanup completed workflows (call periodically)
   */
  cleanupWorkflows(olderThanMs = 3600000) { // 1 hour default
    const now = Date.now();
    for (const [id, state] of this.workflowState.entries()) {
      if (state.endTime && (now - state.endTime) > olderThanMs) {
        this.workflowState.delete(id);
      }
    }
  }

  // Private helper methods

  async _executeStep(step, prevResults) {
    const { action, params, handler, retries = 1 } = step;
    
    let lastError;
    
    for (let attempt = 1; attempt <= retries; attempt++) {
      try {
        if (action === 'custom' && handler) {
          return await handler(prevResults);
        } else if (this.apiService[action]) {
          const stepParams = typeof params === 'function' ? params(prevResults) : params;
          const result = await this.apiService[action](stepParams);
          return { ...result, step: step.name };
        } else {
          throw new Error(`Unknown action: ${action}`);
        }
      } catch (error) {
        lastError = error;
        if (attempt < retries) {
          const delay = this.config.retryDelayMs * Math.pow(2, attempt - 1);
          await this._delay(delay);
        }
      }
    }
    
    throw lastError;
  }

  _generateId() {
    return `workflow_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  _chunkArray(array, chunkSize) {
    const chunks = [];
    for (let i = 0; i < array.length; i += chunkSize) {
      chunks.push(array.slice(i, i + chunkSize));
    }
    return chunks;
  }

  _delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  /**
   * URL Translation Workflow with Sample Results
   * Translates up to maxSamples nodes and returns before/after results
   */
  async translateUrlWithSamples(params) {
    const {
      pageUrl,
      targetLanguage,
      sourceLanguage,
      maxSamples = 5,
      allowExternal = true,
      onProgress
    } = params;

    const workflowId = this._generateId();
    let currentProgress = 0;
    let totalNodes = 0;
    const samples = [];
    const errors = [];

    try {
      this.workflowState.set(workflowId, {
        id: workflowId,
        status: 'running',
        startTime: Date.now(),
        currentStep: 'fetching-html',
        totalNodes: 0,
        completedNodes: 0,
        errors: []
      });

      // Step 1: Fetch HTML content
      if (onProgress) {
        onProgress({
          workflowId,
          step: 'fetching-html',
          progress: 0,
          message: 'Fetching HTML content...'
        });
      }

      const htmlResponse = await this._fetchPageHtml(pageUrl, allowExternal);
      
      // Step 2: Process HTML with GraphQL
      if (onProgress) {
        onProgress({
          workflowId,
          step: 'processing-html',
          progress: 20,
          message: 'Processing HTML content...'
        });
      }

      const processedHtml = await this.apiService.processHtml({
        html: htmlResponse.html,
        base64Encode: true,
        sourceLanguage: sourceLanguage
      });

      totalNodes = processedHtml.count;
      
      if (totalNodes === 0) {
        return {
          workflowId,
          success: true,
          message: 'No translatable content found',
          totalNodes: 0,
          samples: [],
          errors: [],
          duration: Date.now() - this.workflowState.get(workflowId).startTime
        };
      }

      if (onProgress) {
        onProgress({
          workflowId,
          step: 'translating-samples',
          progress: 40,
          message: `Found ${totalNodes} text nodes, translating first ${maxSamples}...`
        });
      }

      // Step 3: Select random sample of nodes to translate
      const allNodes = processedHtml.textNodes;
      const shuffledNodes = [...allNodes].sort(() => 0.5 - Math.random());
      const nodesToTranslate = shuffledNodes.slice(0, Math.min(maxSamples, allNodes.length));

      // Store original index to map results back correctly if needed, though not strictly required here
      const translationJobs = nodesToTranslate.map(textNode => ({
        originalText: textNode,
        originalIndex: allNodes.indexOf(textNode)
      }));

      for (let i = 0; i < translationJobs.length; i++) {
        const job = translationJobs[i];
        const textNode = job.originalText;
        
        try {
          if (onProgress) {
            onProgress({
              workflowId,
              step: 'translating-samples',
              progress: 40 + ((i / translationJobs.length) * 50),
              message: `Translating sample ${i + 1} of ${translationJobs.length}...`
            });
          }

          const translationResult = await this.apiService.translateContent({
            input: textNode,
            inputFormat: 'text',
            targetLanguage: targetLanguage,
            sourceLanguage: sourceLanguage,
            mode: 'real-time' // Use real-time for immediate results
          });

          samples.push({
            index: job.originalIndex,
            original: textNode,
            translation: translationResult.translation || 'Translation failed',
            status: translationResult.status || 'completed'
          });
          
        } catch (error) {
          errors.push({
            index: job.originalIndex,
            original: textNode,
            error: error.message
          });
          
          samples.push({
            index: job.originalIndex,
            original: textNode,
            translation: `Error: ${error.message}`,
            status: 'error'
          });
        }
      }

      if (onProgress) {
        onProgress({
          workflowId,
          step: 'completed',
          progress: 100,
          message: `Translation completed! Showing ${samples.length} sample results.`
        });
      }

      return {
        workflowId,
        success: true,
        totalNodes,
        samples,
        errors,
        duration: Date.now() - this.workflowState.get(workflowId).startTime
      };

    } catch (error) {
      console.error('Translation workflow error:', error);
      
      this.workflowState.set(workflowId, {
        ...this.workflowState.get(workflowId),
        status: 'error',
        error: error.message
      });

      throw new Error(`Translation workflow failed: ${error.message}`);
    }
  }
}

export default new OrchestrationService(); 