import { AgentConfig, createAgentRunner, AgentError, DEFAULT_AGENT_CONFIG, CRITICAL_RETRY_CONFIG } from './errorRecovery';
import { updateAgentOutput, isAgentCompleted, getFailedAgents, saveContext } from './contextStore';

export interface AgentDefinition {
  name: string;
  dependencies: string[];
  critical: boolean;
  runner: () => Promise<any>;
  config?: Partial<AgentConfig>;
}

export class DependencyGraph {
  public agents: Map<string, AgentDefinition> = new Map();
  private completed: Set<string> = new Set();
  public failed: Set<string> = new Set();
  public running: Set<string> = new Set();

  addAgent(definition: AgentDefinition) {
    this.agents.set(definition.name, definition);
  }

  private canRunAgent(agentName: string): boolean {
    const agent = this.agents.get(agentName);
    if (!agent) return false;
    
    // Check if already completed, failed, or running
    if (this.completed.has(agentName) || this.failed.has(agentName) || this.running.has(agentName)) {
      return false;
    }
    
    // Check if all dependencies are completed
    return agent.dependencies.every(dep => this.completed.has(dep));
  }

  getReadyAgents(): string[] {
    return Array.from(this.agents.keys()).filter(name => this.canRunAgent(name));
  }

  markCompleted(agentName: string) {
    this.completed.add(agentName);
    this.running.delete(agentName);
  }

  markFailed(agentName: string) {
    this.failed.add(agentName);
    this.running.delete(agentName);
  }

  markRunning(agentName: string) {
    this.running.add(agentName);
  }

  getStatus() {
    return {
      total: this.agents.size,
      completed: this.completed.size,
      failed: this.failed.size,
      running: this.running.size,
      pending: this.agents.size - this.completed.size - this.failed.size - this.running.size
    };
  }

  getCriticalFailures(): string[] {
    return Array.from(this.failed).filter(name => {
      const agent = this.agents.get(name);
      return agent?.critical === true;
    });
  }

  isComplete(): boolean {
    return this.completed.size + this.failed.size === this.agents.size;
  }

  canContinue(): boolean {
    // Can continue if there are no critical failures and there are still agents to run
    const criticalFailures = this.getCriticalFailures();
    const hasReadyAgents = this.getReadyAgents().length > 0;
    const hasRunningAgents = this.running.size > 0;
    
    return criticalFailures.length === 0 && (hasReadyAgents || hasRunningAgents);
  }
}

export class AgentOrchestrator {
  private graph: DependencyGraph = new DependencyGraph();
  private maxConcurrent: number;
  private saveInterval: number = 30000; // Save every 30 seconds
  private saveTimer?: NodeJS.Timeout;
  private onProgress?: (status: { completed: number; failed: number; running: number }) => void;

  constructor(
    maxConcurrent: number = 5, 
    onProgress?: (status: { completed: number; failed: number; running: number }) => void
  ) {
    this.maxConcurrent = maxConcurrent;
    this.onProgress = onProgress;
    this.startPeriodicSave();
  }

  private startPeriodicSave() {
    this.saveTimer = setInterval(async () => {
      try {
        await saveContext(false); // Don't create backup for periodic saves
      } catch (error) {
        console.warn('Periodic save failed:', error);
      }
    }, this.saveInterval);
  }

  private stopPeriodicSave() {
    if (this.saveTimer) {
      clearInterval(this.saveTimer);
      this.saveTimer = undefined;
    }
  }

  addAgent(definition: AgentDefinition) {
    this.graph.addAgent(definition);
  }

  private async runAgent(agentName: string): Promise<void> {
    const agent = this.graph.agents.get(agentName);
    if (!agent) throw new Error(`Agent ${agentName} not found`);

    const config: AgentConfig = {
      name: agentName,
      dependencies: agent.dependencies,
      ...DEFAULT_AGENT_CONFIG,
      ...(agent.critical ? { retryConfig: CRITICAL_RETRY_CONFIG } : {}),
      ...agent.config,
      critical: agent.critical,
    };

    const runner = createAgentRunner(config);
    const startTime = Date.now();

    try {
      this.graph.markRunning(agentName);
      
      // Update agent as started
      updateAgentOutput(agentName, {
        completed: false,
        startTime,
        retryCount: 0,
      });

      const isVerbose = process.env.DEBUG === 'true';
      
      const output = await runner(agent.runner);
      const executionTime = Date.now() - startTime;

      // Mark as completed
      updateAgentOutput(agentName, {
        output,
        completed: true,
        executionTime,
      });

      this.graph.markCompleted(agentName);
      if (isVerbose) console.log(`✅ Agent completed: ${agentName} (${executionTime}ms)`);
      
      // Report progress
      if (this.onProgress) {
        const status = this.graph.getStatus();
        this.onProgress({ completed: status.completed, failed: status.failed, running: status.running });
      }
    } catch (error) {
      const executionTime = Date.now() - startTime;
      const agentError = error instanceof AgentError ? error : new AgentError(agentName, String(error));

      // Update context with failure
      updateAgentOutput(agentName, {
        completed: false,
        error: agentError.message,
        executionTime,
      });

      this.graph.markFailed(agentName);
      
      if (agent.critical) {
        console.error(`💥 Critical agent failed: ${agentName} - ${agentError.message}`);
        throw agentError;
      } else {
        console.warn(`⚠️  Non-critical agent failed: ${agentName} - ${agentError.message}`);
      }
    }
  }

  async runAll(): Promise<void> {
    const isVerbose = process.env.DEBUG === 'true';
    
    if (isVerbose) console.log('🎬 Starting orchestration...');
    
    try {
      while (!this.graph.isComplete() && this.graph.canContinue()) {
        const readyAgents = this.graph.getReadyAgents();
        const currentlyRunning = this.graph.running.size;
        
        if (readyAgents.length === 0 && currentlyRunning === 0) {
          if (isVerbose) console.warn('⚠️  No agents ready to run and none currently running. Possible dependency deadlock.');
          break;
        }

        // Start agents up to the concurrent limit
        const availableSlots = this.maxConcurrent - currentlyRunning;
        const agentsToStart = readyAgents.slice(0, availableSlots);

        if (agentsToStart.length > 0) {
          if (isVerbose) {
            console.log(`📊 Status: ${JSON.stringify(this.graph.getStatus())}`);
            console.log(`🔄 Starting ${agentsToStart.length} agents: ${agentsToStart.join(', ')}`);
          }
          
          // Start agents in parallel
          const promises = agentsToStart.map(agentName => 
            this.runAgent(agentName).catch(error => {
              // Critical errors will be thrown and handled by the outer catch
              if (error instanceof AgentError && this.graph.agents.get(agentName)?.critical) {
                throw error;
              }
              // Non-critical errors are already logged in runAgent
            })
          );

          await Promise.allSettled(promises);
        } else {
          // Wait a bit before checking again
          await new Promise(resolve => setTimeout(resolve, 1000));
        }
      }

      // Final status check
      const status = this.graph.getStatus();
      const criticalFailures = this.graph.getCriticalFailures();
      
      if (isVerbose) console.log(`📊 Final Status: ${JSON.stringify(status)}`);
      
      if (criticalFailures.length > 0) {
        throw new Error(`Orchestration failed due to critical agent failures: ${criticalFailures.join(', ')}`);
      }
      
      if (status.failed > 0) {
        const failedAgents = Array.from(this.graph.failed);
        console.warn(`⚠️  Orchestration completed with ${status.failed} non-critical failures: ${failedAgents.join(', ')}`);
      }
      
      // Report completion with statistics
      const completionRate = status.total > 0 ? Math.round((status.completed / status.total) * 100) : 0;
      console.log(`🎉 Orchestration completed! ${status.completed}/${status.total} agents (${completionRate}%) succeeded`);
    } finally {
      this.stopPeriodicSave();
      // Final save
      try {
        await saveContext();
      } catch (error) {
        if (isVerbose) console.error('Failed to save final context:', error);
      }
    }
  }

  getStatus() {
    return this.graph.getStatus();
  }

  // Cleanup method
  cleanup() {
    this.stopPeriodicSave();
  }
}
