/**
 * BaseAgent Tests
 * Tests for the abstract base agent class
 */

import { BaseAgent } from '../../../lib/agents/BaseAgent';
import { AgentConfig, AgentTask, AgentStatus } from '../../../lib/agents/types';

// Concrete implementation for testing
class TestAgent extends BaseAgent {
  public runOnceResult: AgentTask | null = null;
  public runOnceError: Error | null = null;
  public runOnceCallCount = 0;

  constructor(config?: Partial<AgentConfig>) {
    super({
      id: 'test-agent',
      name: 'Test Agent',
      description: 'Agent for testing',
      enabled: true,
      intervalMs: 100,
      priority: 'medium',
      maxRetries: 3,
      timeoutMs: 5000,
      ...config,
    });
  }

  async runOnce(): Promise<AgentTask | null> {
    this.runOnceCallCount++;
    if (this.runOnceError) {
      throw this.runOnceError;
    }
    return this.runOnceResult;
  }

  // Expose protected methods for testing
  public testCreateAlert(
    severity: 'info' | 'warning' | 'error' | 'critical',
    title: string,
    message: string
  ) {
    return this.createAlert(severity, title, message);
  }

  public testHandleError(error: Error) {
    return this.handleError(error);
  }

  public testAddTask(type: string, payload: Record<string, any>) {
    return this.addTask(type, payload);
  }

  public testCreateTaskResult(
    type: string,
    status: 'completed' | 'failed',
    result?: any
  ) {
    return this.createTaskResult(type, status, result);
  }
}

describe('BaseAgent', () => {
  let agent: TestAgent;

  beforeEach(() => {
    agent = new TestAgent();
    jest.clearAllMocks();
  });

  afterEach(async () => {
    if (agent.status === 'running') {
      await agent.stop();
    }
  });

  describe('Initialization', () => {
    it('should initialize with correct config', () => {
      expect(agent.config.id).toBe('test-agent');
      expect(agent.config.name).toBe('Test Agent');
      expect(agent.config.enabled).toBe(true);
    });

    it('should initialize with idle status', () => {
      expect(agent.status).toBe('idle');
    });

    it('should initialize with empty metrics', () => {
      const metrics = agent.getMetrics();
      expect(metrics.tasksCompleted).toBe(0);
      expect(metrics.tasksFailed).toBe(0);
      expect(metrics.averageExecutionMs).toBe(0);
      expect(metrics.errors).toEqual([]);
    });

    it('should initialize with empty alerts', () => {
      expect(agent.getAlerts()).toEqual([]);
    });
  });

  describe('Lifecycle', () => {
    it('should start and update status to running', async () => {
      agent.runOnceResult = agent.testCreateTaskResult('test', 'completed');
      await agent.start();
      expect(agent.status).toBe('running');
    });

    it('should not start twice', async () => {
      agent.runOnceResult = agent.testCreateTaskResult('test', 'completed');
      await agent.start();
      const firstCallCount = agent.runOnceCallCount;
      await agent.start(); // Second call
      expect(agent.runOnceCallCount).toBe(firstCallCount);
    });

    it('should stop and update status to idle', async () => {
      agent.runOnceResult = agent.testCreateTaskResult('test', 'completed');
      await agent.start();
      await agent.stop();
      expect(agent.status).toBe('idle');
    });

    it('should pause and resume', async () => {
      agent.runOnceResult = agent.testCreateTaskResult('test', 'completed');
      await agent.start();
      agent.pause();
      expect(agent.status).toBe('paused');
      agent.resume();
      expect(agent.status).toBe('running');
    });

    it('should not pause if not running', () => {
      agent.pause();
      expect(agent.status).toBe('idle');
    });

    it('should not resume if not paused', () => {
      agent.resume();
      expect(agent.status).toBe('idle');
    });
  });

  describe('Task Execution', () => {
    it('should run task on start', async () => {
      agent.runOnceResult = agent.testCreateTaskResult('test', 'completed');
      await agent.start();
      expect(agent.runOnceCallCount).toBe(1);
      await agent.stop();
    });

    it('should increment tasksCompleted on success', async () => {
      agent.runOnceResult = agent.testCreateTaskResult('test', 'completed', { data: 'test' });
      await agent.start();
      await agent.stop();
      expect(agent.getMetrics().tasksCompleted).toBe(1);
    });

    it('should increment tasksFailed on failure', async () => {
      agent.runOnceResult = agent.testCreateTaskResult('test', 'failed', null);
      await agent.start();
      await agent.stop();
      expect(agent.getMetrics().tasksFailed).toBe(1);
    });

    it('should update lastRunAt after execution', async () => {
      agent.runOnceResult = agent.testCreateTaskResult('test', 'completed');
      await agent.start();
      await agent.stop();
      expect(agent.getMetrics().lastRunAt).not.toBeNull();
    });
  });

  describe('Error Handling', () => {
    it('should handle thrown errors', async () => {
      agent.runOnceError = new Error('Test error');
      await agent.start();
      await agent.stop();
      const metrics = agent.getMetrics();
      expect(metrics.errors.length).toBe(1);
      expect(metrics.errors[0].message).toBe('Test error');
    });

    it('should record error timestamp', async () => {
      agent.testHandleError(new Error('Test error'));
      const metrics = agent.getMetrics();
      expect(metrics.lastErrorAt).not.toBeNull();
    });

    it('should limit errors to 50', () => {
      for (let i = 0; i < 60; i++) {
        agent.testHandleError(new Error(`Error ${i}`));
      }
      expect(agent.getMetrics().errors.length).toBe(50);
    });
  });

  describe('Alerts', () => {
    it('should create alerts with correct structure', () => {
      const alert = agent.testCreateAlert('warning', 'Test Alert', 'Test message');
      expect(alert.id).toBeDefined();
      expect(alert.severity).toBe('warning');
      expect(alert.title).toBe('Test Alert');
      expect(alert.message).toBe('Test message');
      expect(alert.acknowledged).toBe(false);
    });

    it('should add alerts to the list', () => {
      agent.testCreateAlert('info', 'Alert 1', 'Message 1');
      agent.testCreateAlert('warning', 'Alert 2', 'Message 2');
      expect(agent.getAlerts().length).toBe(2);
    });

    it('should limit alerts to 100', () => {
      for (let i = 0; i < 110; i++) {
        agent.testCreateAlert('info', `Alert ${i}`, 'Message');
      }
      expect(agent.getAlerts().length).toBe(100);
    });
  });

  describe('Metrics', () => {
    it('should calculate uptime when running', async () => {
      agent.runOnceResult = agent.testCreateTaskResult('test', 'completed');
      await agent.start();
      await new Promise((resolve) => setTimeout(resolve, 50));
      const metrics = agent.getMetrics();
      expect(metrics.uptime).toBeGreaterThan(0);
      await agent.stop();
    });

    it('should calculate average execution time', async () => {
      agent.runOnceResult = agent.testCreateTaskResult('test', 'completed');
      await agent.start();
      await agent.stop();
      const metrics = agent.getMetrics();
      expect(metrics.averageExecutionMs).toBeGreaterThanOrEqual(0);
    });
  });

  describe('Task Queue', () => {
    it('should add tasks with unique IDs', () => {
      const task1 = agent.testAddTask('type1', { key: 'value1' });
      const task2 = agent.testAddTask('type2', { key: 'value2' });
      expect(task1.id).not.toBe(task2.id);
    });

    it('should set correct task properties', () => {
      const task = agent.testAddTask('test-type', { data: 'value' });
      expect(task.type).toBe('test-type');
      expect(task.payload).toEqual({ data: 'value' });
      expect(task.status).toBe('pending');
      expect(task.agentId).toBe('test-agent');
    });
  });

  describe('Task Results', () => {
    it('should create completed task result', () => {
      const result = agent.testCreateTaskResult('scan', 'completed', { count: 10 });
      expect(result.status).toBe('completed');
      expect(result.result).toEqual({ count: 10 });
    });

    it('should create failed task result', () => {
      const result = agent.testCreateTaskResult('scan', 'failed', null);
      expect(result.status).toBe('failed');
    });
  });
});
