/**
 * Performance Test: Agent Spawner Throughput
 *
 * Benchmarks the spawner service under concurrent load to verify
 * it meets performance requirements for agent orchestration.
 *
 * Covers issue #229 (ORCH-128)
 */
import { describe, it, expect, beforeEach, vi } from "vitest";
import { AgentSpawnerService } from "../../src/spawner/agent-spawner.service";
import { AgentsController } from "../../src/api/agents/agents.controller";
import { QueueService } from "../../src/queue/queue.service";
import { AgentLifecycleService } from "../../src/spawner/agent-lifecycle.service";
import { KillswitchService } from "../../src/killswitch/killswitch.service";
import { ConfigService } from "@nestjs/config";

function createSpawnRequest(taskId: string): {
  taskId: string;
  agentType: string;
  context: { repository: string; branch: string; workItems: string[] };
} {
  return {
    taskId,
    agentType: "worker",
    context: {
      repository: "https://git.example.com/repo.git",
      branch: "main",
      workItems: [`US-${taskId}`],
    },
  };
}

describe("Performance: Agent Spawner Throughput", () => {
  let controller: AgentsController;
  let spawnerService: AgentSpawnerService;

  const mockConfigService = {
    get: vi.fn((key: string, defaultValue?: unknown) => {
      const config: Record<string, unknown> = {
        "orchestrator.claude.apiKey": "test-api-key",
      };
      return config[key] ?? defaultValue;
    }),
  };

  beforeEach(() => {
    vi.clearAllMocks();

    spawnerService = new AgentSpawnerService(mockConfigService as unknown as ConfigService);

    const queueService = {
      addTask: vi.fn().mockResolvedValue(undefined),
    } as unknown as QueueService;

    const lifecycleService = {
      getAgentLifecycleState: vi.fn(),
    } as unknown as AgentLifecycleService;

    const killswitchService = {
      killAgent: vi.fn(),
      killAllAgents: vi.fn(),
    } as unknown as KillswitchService;

    controller = new AgentsController(
      queueService,
      spawnerService,
      lifecycleService,
      killswitchService
    );
  });

  describe("Spawn latency", () => {
    it("should spawn a single agent in under 50ms", async () => {
      // Warmup
      await controller.spawn(createSpawnRequest("warmup-1"));

      const start = performance.now();

      await controller.spawn(createSpawnRequest("perf-single-001"));

      const duration = performance.now() - start;
      expect(duration).toBeLessThan(50);
    });

    it("should spawn 100 agents sequentially in under 500ms", async () => {
      // Warmup
      for (let i = 0; i < 5; i++) {
        await controller.spawn(createSpawnRequest(`warmup-seq-${String(i)}`));
      }

      const start = performance.now();

      for (let i = 0; i < 100; i++) {
        await controller.spawn(createSpawnRequest(`perf-seq-${String(i)}`));
      }

      const duration = performance.now() - start;
      expect(duration).toBeLessThan(500);

      // 100 sequential + 5 warmup
      const agents = spawnerService.listAgentSessions();
      expect(agents.length).toBeGreaterThanOrEqual(100);
    });

    it("should spawn 100 agents concurrently in under 200ms", async () => {
      // Warmup
      for (let i = 0; i < 5; i++) {
        await controller.spawn(createSpawnRequest(`warmup-conc-${String(i)}`));
      }

      const start = performance.now();

      const promises = Array.from({ length: 100 }, (_, i) =>
        controller.spawn(createSpawnRequest(`perf-concurrent-${String(i)}`))
      );

      const results = await Promise.all(promises);
      const duration = performance.now() - start;

      expect(duration).toBeLessThan(200);
      expect(results).toHaveLength(100);

      // Verify all IDs are unique
      const ids = new Set(results.map((r) => r.agentId));
      expect(ids.size).toBe(100);
    });
  });

  describe("Session lookup performance", () => {
    it("should look up agents by ID in under 10ms with 1000 sessions", async () => {
      // Pre-populate 1000 sessions
      const agentIds: string[] = [];
      for (let i = 0; i < 1000; i++) {
        const result = await controller.spawn(createSpawnRequest(`perf-lookup-${String(i)}`));
        agentIds.push(result.agentId);
      }

      // Measure lookup time for random agents
      const lookupStart = performance.now();
      for (let i = 0; i < 100; i++) {
        const randomIdx = Math.floor(Math.random() * agentIds.length);
        const session = spawnerService.getAgentSession(agentIds[randomIdx] ?? "");
        expect(session).toBeDefined();
      }
      const lookupDuration = performance.now() - lookupStart;

      // 100 lookups should complete in under 10ms
      expect(lookupDuration).toBeLessThan(10);
    });

    it("should list all sessions in under 5ms with 1000 sessions", async () => {
      // Pre-populate 1000 sessions
      for (let i = 0; i < 1000; i++) {
        await controller.spawn(createSpawnRequest(`perf-list-${String(i)}`));
      }

      const listStart = performance.now();
      const sessions = spawnerService.listAgentSessions();
      const listDuration = performance.now() - listStart;

      expect(sessions).toHaveLength(1000);
      expect(listDuration).toBeLessThan(5);
    });
  });

  describe("Memory efficiency", () => {
    it("should not have excessive memory growth after 1000 spawns", async () => {
      // Force GC if available, then settle
      if (global.gc) global.gc();

      const memBefore = process.memoryUsage().heapUsed;

      for (let i = 0; i < 1000; i++) {
        await controller.spawn(createSpawnRequest(`perf-mem-${String(i)}`));
      }

      const memAfter = process.memoryUsage().heapUsed;
      const memGrowthMB = (memAfter - memBefore) / 1024 / 1024;

      // 1000 agent sessions should use less than 50MB
      expect(memGrowthMB).toBeLessThan(50);
    });
  });
});
