/**
 * E2E Test: Concurrent Agents
 *
 * Tests multiple agents running concurrently with proper isolation.
 * Verifies agent-level isolation, queue management, and concurrent operations.
 *
 * Covers issue #228 (ORCH-127)
 */
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";

describe("E2E: Concurrent Agents", () => {
  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("Concurrent spawning", () => {
    it("should spawn multiple agents simultaneously without conflicts", async () => {
      // Spawn 5 agents in parallel
      const spawnPromises = Array.from({ length: 5 }, (_, i) =>
        controller.spawn({
          taskId: `concurrent-task-${String(i)}`,
          agentType: "worker",
          context: {
            repository: "https://git.example.com/repo.git",
            branch: `feature/task-${String(i)}`,
            workItems: [`US-${String(i).padStart(3, "0")}`],
          },
        })
      );

      const results = await Promise.all(spawnPromises);

      // All should succeed
      expect(results).toHaveLength(5);
      results.forEach((result) => {
        expect(result.agentId).toBeDefined();
        expect(result.status).toBe("spawning");
      });

      // All IDs should be unique
      const ids = new Set(results.map((r) => r.agentId));
      expect(ids.size).toBe(5);

      // All should appear in the list
      const agents = spawnerService.listAgentSessions();
      expect(agents).toHaveLength(5);
    });

    it("should assign unique IDs to every agent even under concurrent load", async () => {
      const allIds = new Set<string>();
      const batchSize = 10;

      // Spawn agents in batches
      for (let batch = 0; batch < 3; batch++) {
        const promises = Array.from({ length: batchSize }, (_, i) =>
          controller.spawn({
            taskId: `batch-${String(batch)}-task-${String(i)}`,
            agentType: "worker",
            context: {
              repository: "https://git.example.com/repo.git",
              branch: "main",
              workItems: [`US-${String(batch * batchSize + i)}`],
            },
          })
        );

        const results = await Promise.all(promises);
        results.forEach((r) => allIds.add(r.agentId));
      }

      // All 30 IDs should be unique
      expect(allIds.size).toBe(30);

      // All 30 should be listed
      const agents = spawnerService.listAgentSessions();
      expect(agents).toHaveLength(30);
    });
  });

  describe("Mixed agent types concurrently", () => {
    it("should handle mixed worker/reviewer/tester agents concurrently", async () => {
      const types = ["worker", "reviewer", "tester"] as const;

      const promises = types.flatMap((agentType, typeIndex) =>
        Array.from({ length: 3 }, (_, i) =>
          controller.spawn({
            taskId: `mixed-${agentType}-${String(i)}`,
            agentType,
            context: {
              repository: "https://git.example.com/repo.git",
              branch: `branch-${String(typeIndex * 3 + i)}`,
              workItems: [`US-${String(typeIndex * 3 + i)}`],
            },
          })
        )
      );

      const results = await Promise.all(promises);
      expect(results).toHaveLength(9);

      const agents = spawnerService.listAgentSessions();
      expect(agents).toHaveLength(9);

      // Verify type distribution
      const typeCounts = agents.reduce(
        (acc, a) => {
          acc[a.agentType] = (acc[a.agentType] ?? 0) + 1;
          return acc;
        },
        {} as Record<string, number>
      );

      expect(typeCounts["worker"]).toBe(3);
      expect(typeCounts["reviewer"]).toBe(3);
      expect(typeCounts["tester"]).toBe(3);
    });
  });

  describe("Agent isolation", () => {
    it("should isolate agent contexts from each other", async () => {
      const agent1 = await controller.spawn({
        taskId: "isolated-task-1",
        agentType: "worker",
        context: {
          repository: "https://git.example.com/repo-a.git",
          branch: "main",
          workItems: ["US-001"],
          skills: ["typescript"],
        },
      });

      const agent2 = await controller.spawn({
        taskId: "isolated-task-2",
        agentType: "reviewer",
        context: {
          repository: "https://git.example.com/repo-b.git",
          branch: "develop",
          workItems: ["US-002"],
          skills: ["python"],
        },
      });

      // Verify sessions are independent
      const session1 = spawnerService.getAgentSession(agent1.agentId);
      const session2 = spawnerService.getAgentSession(agent2.agentId);

      expect(session1?.context.repository).toBe("https://git.example.com/repo-a.git");
      expect(session2?.context.repository).toBe("https://git.example.com/repo-b.git");
      expect(session1?.context.branch).toBe("main");
      expect(session2?.context.branch).toBe("develop");
    });

    it("should not leak state between concurrent agent operations", async () => {
      // Spawn agents with different task contexts
      const spawnPromises = Array.from({ length: 5 }, (_, i) =>
        controller.spawn({
          taskId: `leak-test-${String(i)}`,
          agentType: "worker",
          context: {
            repository: `https://git.example.com/repo-${String(i)}.git`,
            branch: `branch-${String(i)}`,
            workItems: [`US-${String(i).padStart(3, "0")}`],
          },
        })
      );

      const results = await Promise.all(spawnPromises);

      // Verify each agent has its own isolated context
      results.forEach((result, i) => {
        const session = spawnerService.getAgentSession(result.agentId);
        expect(session?.taskId).toBe(`leak-test-${String(i)}`);
        expect(session?.context.repository).toBe(`https://git.example.com/repo-${String(i)}.git`);
        expect(session?.context.branch).toBe(`branch-${String(i)}`);
      });
    });
  });
});
