/**
 * BaseAgent - Abstract base class for all LocalGreenChain agents
 * Provides common functionality for autonomous task execution
 */

import {
  AgentConfig,
  AgentStatus,
  AgentMetrics,
  AgentTask,
  AgentAlert,
  AgentError,
  AgentPriority,
  BaseAgent as IBaseAgent
} from './types';

export abstract class BaseAgent implements IBaseAgent {
  config: AgentConfig;
  status: AgentStatus = 'idle';
  metrics: AgentMetrics;

  protected alerts: AgentAlert[] = [];
  protected taskQueue: AgentTask[] = [];
  protected runInterval: NodeJS.Timeout | null = null;
  protected startTime: number = 0;

  constructor(config: AgentConfig) {
    this.config = config;
    this.metrics = {
      agentId: config.id,
      tasksCompleted: 0,
      tasksFailed: 0,
      averageExecutionMs: 0,
      lastRunAt: null,
      lastSuccessAt: null,
      lastErrorAt: null,
      uptime: 0,
      errors: []
    };
  }

  /**
   * Start the agent's execution loop
   */
  async start(): Promise<void> {
    if (this.status === 'running') {
      console.log(`[${this.config.name}] Already running`);
      return;
    }

    this.status = 'running';
    this.startTime = Date.now();
    console.log(`[${this.config.name}] Starting agent...`);

    // Run immediately once
    await this.executeTask();

    // Set up interval
    this.runInterval = setInterval(async () => {
      if (this.status === 'running') {
        await this.executeTask();
      }
    }, this.config.intervalMs);

    this.emitEvent('agent_started', { config: this.config });
  }

  /**
   * Stop the agent
   */
  async stop(): Promise<void> {
    if (this.runInterval) {
      clearInterval(this.runInterval);
      this.runInterval = null;
    }
    this.status = 'idle';
    this.metrics.uptime += Date.now() - this.startTime;
    console.log(`[${this.config.name}] Stopped`);
    this.emitEvent('agent_stopped', { uptime: this.metrics.uptime });
  }

  /**
   * Pause the agent
   */
  pause(): void {
    if (this.status === 'running') {
      this.status = 'paused';
      console.log(`[${this.config.name}] Paused`);
    }
  }

  /**
   * Resume the agent
   */
  resume(): void {
    if (this.status === 'paused') {
      this.status = 'running';
      console.log(`[${this.config.name}] Resumed`);
    }
  }

  /**
   * Execute a single task cycle
   */
  protected async executeTask(): Promise<void> {
    const startTime = Date.now();
    this.metrics.lastRunAt = new Date().toISOString();

    try {
      const task = await this.runOnce();

      if (task) {
        const executionTime = Date.now() - startTime;
        this.updateAverageExecutionTime(executionTime);

        if (task.status === 'completed') {
          this.metrics.tasksCompleted++;
          this.metrics.lastSuccessAt = new Date().toISOString();
          this.emitEvent('task_completed', { taskId: task.id, result: task.result });
        } else if (task.status === 'failed') {
          this.metrics.tasksFailed++;
          this.metrics.lastErrorAt = new Date().toISOString();
          this.emitEvent('task_failed', { taskId: task.id, error: task.error });
        }
      }
    } catch (error: any) {
      this.handleError(error);
    }
  }

  /**
   * Run a single execution cycle - must be implemented by subclasses
   */
  abstract runOnce(): Promise<AgentTask | null>;

  /**
   * Get current metrics
   */
  getMetrics(): AgentMetrics {
    return {
      ...this.metrics,
      uptime: this.status === 'running'
        ? this.metrics.uptime + (Date.now() - this.startTime)
        : this.metrics.uptime
    };
  }

  /**
   * Get all alerts
   */
  getAlerts(): AgentAlert[] {
    return this.alerts;
  }

  /**
   * Add a task to the queue
   */
  protected addTask(type: string, payload: Record<string, any>, priority: AgentPriority = 'medium'): AgentTask {
    const task: AgentTask = {
      id: `task-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
      agentId: this.config.id,
      type,
      payload,
      priority,
      createdAt: new Date().toISOString(),
      status: 'pending',
      retryCount: 0
    };

    this.taskQueue.push(task);
    this.sortTaskQueue();
    return task;
  }

  /**
   * Create an alert
   */
  protected createAlert(
    severity: AgentAlert['severity'],
    title: string,
    message: string,
    options?: {
      actionRequired?: string;
      relatedEntityId?: string;
      relatedEntityType?: string;
    }
  ): AgentAlert {
    const alert: AgentAlert = {
      id: `alert-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
      agentId: this.config.id,
      severity,
      title,
      message,
      timestamp: new Date().toISOString(),
      acknowledged: false,
      ...options
    };

    this.alerts.push(alert);

    // Keep only last 100 alerts
    if (this.alerts.length > 100) {
      this.alerts = this.alerts.slice(-100);
    }

    console.log(`[${this.config.name}] Alert (${severity}): ${title}`);
    return alert;
  }

  /**
   * Handle errors
   */
  protected handleError(error: any, taskId?: string): void {
    const agentError: AgentError = {
      timestamp: new Date().toISOString(),
      message: error.message || String(error),
      taskId,
      stack: error.stack
    };

    this.metrics.errors.push(agentError);
    this.metrics.lastErrorAt = agentError.timestamp;

    // Keep only last 50 errors
    if (this.metrics.errors.length > 50) {
      this.metrics.errors = this.metrics.errors.slice(-50);
    }

    console.error(`[${this.config.name}] Error: ${agentError.message}`);

    // Create critical alert if too many errors
    const recentErrors = this.metrics.errors.filter(
      e => Date.now() - new Date(e.timestamp).getTime() < 60000
    );

    if (recentErrors.length >= 5) {
      this.createAlert('critical', 'High Error Rate',
        `${recentErrors.length} errors in the last minute`,
        { actionRequired: 'Check agent configuration and dependencies' }
      );
    }
  }

  /**
   * Emit an event
   */
  protected emitEvent(eventType: string, data: Record<string, any>): void {
    // Events can be picked up by the orchestrator
    console.log(`[${this.config.name}] Event: ${eventType}`);
  }

  /**
   * Update average execution time
   */
  private updateAverageExecutionTime(newTime: number): void {
    const totalTasks = this.metrics.tasksCompleted + this.metrics.tasksFailed;
    if (totalTasks === 0) {
      this.metrics.averageExecutionMs = newTime;
    } else {
      this.metrics.averageExecutionMs =
        (this.metrics.averageExecutionMs * totalTasks + newTime) / (totalTasks + 1);
    }
  }

  /**
   * Sort task queue by priority
   */
  private sortTaskQueue(): void {
    const priorityOrder: Record<AgentPriority, number> = {
      critical: 0,
      high: 1,
      medium: 2,
      low: 3
    };

    this.taskQueue.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
  }

  /**
   * Create a task result
   */
  protected createTaskResult(
    type: string,
    status: 'completed' | 'failed',
    result?: any,
    error?: string
  ): AgentTask {
    return {
      id: `task-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
      agentId: this.config.id,
      type,
      payload: {},
      priority: 'medium',
      createdAt: new Date().toISOString(),
      status,
      result,
      error,
      retryCount: 0
    };
  }
}
