/**
 * AgentLifecycleController Tests — Regression coverage for event emissions.
 */

import { describe, it, expect, beforeEach, vi } from 'vitest';
import { AgentLifecycleController } from './controller.js';
import type { AgentRepository } from '../../db/repositories/agent-repository.js';
import type { AccountRepository } from '../../db/repositories/account-repository.js';
import type { SignalManager } from './signal-manager.js';
import type { RetryPolicy } from './retry-policy.js';
import type { AgentErrorAnalyzer } from './error-analyzer.js';
import type { ProcessManager } from '../process-manager.js';
import type { CleanupManager } from '../cleanup-manager.js';
import type { CleanupStrategy } from './cleanup-strategy.js';
import type { EventBus, AgentAccountSwitchedEvent } from '../../events/types.js';

function makeController(overrides: {
  repository?: Partial<AgentRepository>;
  accountRepository?: Partial<AccountRepository>;
  eventBus?: EventBus;
}): AgentLifecycleController {
  const signalManager: SignalManager = {
    clearSignal: vi.fn(),
    checkSignalExists: vi.fn(),
    readSignal: vi.fn(),
    waitForSignal: vi.fn(),
    validateSignalFile: vi.fn(),
  };
  const retryPolicy: RetryPolicy = {
    maxAttempts: 3,
    backoffMs: [1000, 2000, 4000],
    shouldRetry: vi.fn().mockReturnValue(false),
    getRetryDelay: vi.fn().mockReturnValue(0),
  };
  const errorAnalyzer = { analyzeError: vi.fn() } as unknown as AgentErrorAnalyzer;
  const processManager = { getAgentWorkdir: vi.fn() } as unknown as ProcessManager;
  const cleanupManager = {} as unknown as CleanupManager;
  const cleanupStrategy = {
    shouldCleanup: vi.fn(),
    executeCleanup: vi.fn(),
  } as unknown as CleanupStrategy;

  return new AgentLifecycleController(
    signalManager,
    retryPolicy,
    errorAnalyzer,
    processManager,
    overrides.repository as AgentRepository,
    cleanupManager,
    cleanupStrategy,
    overrides.accountRepository as AccountRepository | undefined,
    false,
  );
}

describe('AgentLifecycleController', () => {
  describe('handleAccountExhaustion', () => {
    it('emits agent:account_switched with correct payload when new account is available', async () => {
      const emittedEvents: AgentAccountSwitchedEvent[] = [];
      const eventBus: EventBus = {
        emit: vi.fn((event) => { emittedEvents.push(event as AgentAccountSwitchedEvent); }),
        on: vi.fn(),
        off: vi.fn(),
        once: vi.fn(),
      };

      const agentRecord = {
        id: 'agent-1',
        name: 'test-agent',
        accountId: 'old-account-id',
        provider: 'claude',
      };
      const newAccount = { id: 'new-account-id' };

      const repository: Partial<AgentRepository> = {
        findById: vi.fn().mockResolvedValue(agentRecord),
      };
      const accountRepository: Partial<AccountRepository> = {
        markExhausted: vi.fn().mockResolvedValue(agentRecord),
        findNextAvailable: vi.fn().mockResolvedValue(newAccount),
      };

      const controller = makeController({ repository, accountRepository, eventBus });

      // Call private method via any-cast
      await (controller as any).handleAccountExhaustion('agent-1');

      const accountSwitchedEvents = emittedEvents.filter(
        (e) => e.type === 'agent:account_switched'
      );
      expect(accountSwitchedEvents).toHaveLength(1);
      const event = accountSwitchedEvents[0];
      expect(event.type).toBe('agent:account_switched');
      expect(event.payload.agentId).toBe('agent-1');
      expect(event.payload.name).toBe('test-agent');
      expect(event.payload.previousAccountId).toBe('old-account-id');
      expect(event.payload.newAccountId).toBe('new-account-id');
      expect(event.payload.reason).toBe('account_exhausted');
    });

    it('does not emit agent:account_switched when no new account is available', async () => {
      const eventBus: EventBus = {
        emit: vi.fn(),
        on: vi.fn(),
        off: vi.fn(),
        once: vi.fn(),
      };

      const agentRecord = {
        id: 'agent-2',
        name: 'test-agent-2',
        accountId: 'old-account-id',
        provider: 'claude',
      };

      const repository: Partial<AgentRepository> = {
        findById: vi.fn().mockResolvedValue(agentRecord),
      };
      const accountRepository: Partial<AccountRepository> = {
        markExhausted: vi.fn().mockResolvedValue(agentRecord),
        findNextAvailable: vi.fn().mockResolvedValue(null),
      };

      const controller = makeController({ repository, accountRepository, eventBus });

      await (controller as any).handleAccountExhaustion('agent-2');

      expect(eventBus.emit).not.toHaveBeenCalled();
    });

    it('does not emit when agent has no accountId', async () => {
      const eventBus: EventBus = {
        emit: vi.fn(),
        on: vi.fn(),
        off: vi.fn(),
        once: vi.fn(),
      };

      const repository: Partial<AgentRepository> = {
        findById: vi.fn().mockResolvedValue({ id: 'agent-3', name: 'x', accountId: null }),
      };
      const accountRepository: Partial<AccountRepository> = {
        markExhausted: vi.fn(),
        findNextAvailable: vi.fn(),
      };

      const controller = makeController({ repository, accountRepository, eventBus });

      await (controller as any).handleAccountExhaustion('agent-3');

      expect(eventBus.emit).not.toHaveBeenCalled();
    });
  });
});
