/**
 * E2E Test: Full Agent Lifecycle
 *
 * Tests the complete lifecycle of an agent from spawn to completion/failure.
 * Uses mocked services to simulate the full flow without external dependencies.
 *
 * Lifecycle: spawn → running → completed/failed/killed
 *
 * Covers issue #226 (ORCH-125)
 */
import { describe, it, expect, beforeEach, vi } from "vitest";
import { ConfigService } from "@nestjs/config";
import { AgentSpawnerService } from "../../src/spawner/agent-spawner.service";
import { AgentLifecycleService } from "../../src/spawner/agent-lifecycle.service";
import { QueueService } from "../../src/queue/queue.service";
import { KillswitchService } from "../../src/killswitch/killswitch.service";
import { AgentsController } from "../../src/api/agents/agents.controller";
import type { AgentState } from "../../src/valkey/types";

describe("E2E: Full Agent Lifecycle", () => {
  let controller: AgentsController;
  let spawnerService: AgentSpawnerService;
  let lifecycleService: AgentLifecycleService;
  let queueService: QueueService;

  const mockConfigService = {
    get: vi.fn((key: string, defaultValue?: unknown) => {
      const config: Record<string, unknown> = {
        "orchestrator.claude.apiKey": "test-api-key",
        "orchestrator.queue.name": "test-queue",
        "orchestrator.queue.maxRetries": 3,
        "orchestrator.queue.baseDelay": 100,
        "orchestrator.queue.maxDelay": 1000,
        "orchestrator.valkey.host": "localhost",
        "orchestrator.valkey.port": 6379,
      };
      return config[key] ?? defaultValue;
    }),
  };

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

    // Create real spawner service with mock config
    spawnerService = new AgentSpawnerService(mockConfigService as unknown as ConfigService);

    // Create mock lifecycle service
    lifecycleService = {
      transitionToRunning: vi.fn(),
      transitionToCompleted: vi.fn(),
      transitionToFailed: vi.fn(),
      getAgentLifecycleState: vi.fn(),
    } as unknown as AgentLifecycleService;

    // Create mock queue service
    queueService = {
      addTask: vi.fn().mockResolvedValue(undefined),
      getStats: vi.fn(),
    } as unknown as QueueService;

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

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

  describe("Happy path: spawn → queue → track", () => {
    it("should spawn an agent, register it, and queue the task", async () => {
      // Step 1: Spawn agent
      const spawnResult = await controller.spawn({
        taskId: "e2e-task-001",
        agentType: "worker",
        context: {
          repository: "https://git.example.com/repo.git",
          branch: "main",
          workItems: ["US-001"],
          skills: ["typescript"],
        },
      });

      expect(spawnResult.agentId).toBeDefined();
      expect(spawnResult.status).toBe("spawning");

      // Step 2: Verify agent appears in list
      const agents = spawnerService.listAgentSessions();
      expect(agents).toHaveLength(1);
      expect(agents[0].state).toBe("spawning");
      expect(agents[0].taskId).toBe("e2e-task-001");

      // Step 3: Verify agent status
      const session = spawnerService.getAgentSession(spawnResult.agentId);
      expect(session).toBeDefined();
      expect(session?.state).toBe("spawning");
      expect(session?.agentType).toBe("worker");

      // Step 4: Verify task was queued
      expect(queueService.addTask).toHaveBeenCalledWith(
        "e2e-task-001",
        expect.objectContaining({
          repository: "https://git.example.com/repo.git",
          branch: "main",
        }),
        { priority: 5 }
      );
    });

    it("should track multiple agents spawned sequentially", async () => {
      // Spawn 3 agents
      const agents = [];
      for (let i = 0; i < 3; i++) {
        const result = await controller.spawn({
          taskId: `e2e-task-${String(i).padStart(3, "0")}`,
          agentType: "worker",
          context: {
            repository: "https://git.example.com/repo.git",
            branch: "main",
            workItems: [`US-${String(i).padStart(3, "0")}`],
          },
        });
        agents.push(result);
      }

      // Verify all 3 agents are listed
      const listedAgents = spawnerService.listAgentSessions();
      expect(listedAgents).toHaveLength(3);

      // Verify each agent has unique ID
      const agentIds = listedAgents.map((a) => a.agentId);
      const uniqueIds = new Set(agentIds);
      expect(uniqueIds.size).toBe(3);
    });
  });

  describe("Failure path: spawn → running → failed", () => {
    it("should handle agent spawn with invalid parameters", async () => {
      await expect(
        controller.spawn({
          taskId: "",
          agentType: "worker",
          context: {
            repository: "https://git.example.com/repo.git",
            branch: "main",
            workItems: ["US-001"],
          },
        })
      ).rejects.toThrow("taskId is required");
    });

    it("should reject invalid agent types", async () => {
      await expect(
        controller.spawn({
          taskId: "e2e-task-001",
          agentType: "invalid" as "worker",
          context: {
            repository: "https://git.example.com/repo.git",
            branch: "main",
            workItems: ["US-001"],
          },
        })
      ).rejects.toThrow("agentType must be one of");
    });
  });

  describe("Multi-type agents", () => {
    it("should support worker, reviewer, and tester agent types", async () => {
      const types = ["worker", "reviewer", "tester"] as const;

      for (const agentType of types) {
        const result = await controller.spawn({
          taskId: `e2e-task-${agentType}`,
          agentType,
          context: {
            repository: "https://git.example.com/repo.git",
            branch: "main",
            workItems: ["US-001"],
          },
        });

        expect(result.agentId).toBeDefined();
        expect(result.status).toBe("spawning");
      }

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

      const agentTypes = agents.map((a) => a.agentType);
      expect(agentTypes).toContain("worker");
      expect(agentTypes).toContain("reviewer");
      expect(agentTypes).toContain("tester");
    });
  });

  describe("Agent status tracking", () => {
    it("should track spawn timestamp", async () => {
      const before = new Date();

      const result = await controller.spawn({
        taskId: "e2e-task-time",
        agentType: "worker",
        context: {
          repository: "https://git.example.com/repo.git",
          branch: "main",
          workItems: ["US-001"],
        },
      });

      const after = new Date();
      const agents = spawnerService.listAgentSessions();
      const agent = agents.find((a) => a.agentId === result.agentId);
      expect(agent).toBeDefined();

      const spawnedAt = new Date(agent!.spawnedAt);
      expect(spawnedAt.getTime()).toBeGreaterThanOrEqual(before.getTime());
      expect(spawnedAt.getTime()).toBeLessThanOrEqual(after.getTime());
    });

    it("should return correct status for each agent", async () => {
      // Mock lifecycle to return specific states
      const mockState: AgentState = {
        agentId: "mock-agent-1",
        taskId: "e2e-task-001",
        status: "running",
        startedAt: new Date().toISOString(),
      };

      (lifecycleService.getAgentLifecycleState as ReturnType<typeof vi.fn>).mockResolvedValue(
        mockState
      );

      const status = await controller.getAgentStatus("mock-agent-1");
      expect(status.status).toBe("running");
      expect(status.taskId).toBe("e2e-task-001");
    });
  });
});
