/**
 * E2E Happy Path Tests
 *
 * Tests proving core dispatch/coordination flow works end-to-end
 * using the TestHarness with mocked agents and worktrees.
 */

import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import {
  createTestHarness,
  SIMPLE_FIXTURE,
  PARALLEL_FIXTURE,
  COMPLEX_FIXTURE,
  type TestHarness,
} from '../index.js';

describe('E2E Happy Path', () => {
  let harness: TestHarness;

  beforeEach(() => {
    harness = createTestHarness();
  });

  afterEach(() => {
    harness.cleanup();
    vi.useRealTimers();
  });

  // ===========================================================================
  // Scenario 1: Single Task Flow
  // ===========================================================================

  describe('Single task flow', () => {
    it('completes a single task from queue to completion', async () => {
      vi.useFakeTimers();
      const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
      const taskAId = seeded.tasks.get('Task A')!;

      // Pre-seed idle agent (required by DispatchManager before spawning new ones)
      await harness.agentManager.spawn({
        name: 'pool-agent',
        taskId: 'placeholder',
        prompt: 'placeholder',
      });
      await harness.advanceTimers();
      harness.clearEvents();

      // Step 1: Queue task
      await harness.dispatchManager.queue(taskAId);

      // Verify task:queued event
      const queuedEvents = harness.getEventsByType('task:queued');
      expect(queuedEvents.length).toBe(1);
      expect((queuedEvents[0].payload as { taskId: string }).taskId).toBe(taskAId);

      // Step 2: Dispatch task
      const dispatchResult = await harness.dispatchManager.dispatchNext();
      expect(dispatchResult.success).toBe(true);
      expect(dispatchResult.taskId).toBe(taskAId);
      expect(dispatchResult.agentId).toBeDefined();

      // Verify task:dispatched event
      const dispatchedEvents = harness.getEventsByType('task:dispatched');
      expect(dispatchedEvents.length).toBe(1);
      expect((dispatchedEvents[0].payload as { taskId: string }).taskId).toBe(taskAId);

      // Verify agent:spawned event
      const spawnedEvents = harness.getEventsByType('agent:spawned');
      expect(spawnedEvents.length).toBe(1);

      // Step 3: Wait for agent completion
      await harness.advanceTimers();

      // Verify agent:stopped event
      const stoppedEvents = harness.getEventsByType('agent:stopped');
      expect(stoppedEvents.length).toBe(1);

      // Step 4: Mark task complete
      await harness.dispatchManager.completeTask(taskAId);

      // Verify task status in database
      const task = await harness.taskRepository.findById(taskAId);
      expect(task?.status).toBe('completed');
    });
  });

  // ===========================================================================
  // Scenario 2: Sequential Dependencies
  // ===========================================================================

  describe('Sequential dependencies', () => {
    it('dispatches tasks in priority order (dependency ordering via task status)', async () => {
      vi.useFakeTimers();
      const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
      const taskAId = seeded.tasks.get('Task A')!;
      const taskBId = seeded.tasks.get('Task B')!;
      const taskCId = seeded.tasks.get('Task C')!;

      // Pre-seed idle agent
      await harness.agentManager.spawn({
        name: 'pool-agent',
        taskId: 'placeholder',
        prompt: 'placeholder',
      });
      await harness.advanceTimers();
      harness.clearEvents();

      // Queue all three tasks
      await harness.dispatchManager.queue(taskAId);
      await harness.dispatchManager.queue(taskBId);
      await harness.dispatchManager.queue(taskCId);
      harness.clearEvents();

      // All three tasks are queued
      const queueState = await harness.dispatchManager.getQueueState();
      expect(queueState.queued.length).toBe(3);

      // First dispatchNext: Task A (high priority) dispatches first
      const nextTask = await harness.dispatchManager.getNextDispatchable();
      expect(nextTask).not.toBeNull();
      expect(nextTask!.taskId).toBe(taskAId); // High priority first

      // All tasks are "ready" in current implementation (dependency loading TBD)
      const readyTaskIds = queueState.ready.map((t) => t.taskId);
      expect(readyTaskIds).toContain(taskAId);

      // Dispatch Task A
      const dispatchResult = await harness.dispatchManager.dispatchNext();
      expect(dispatchResult.success).toBe(true);
      expect(dispatchResult.taskId).toBe(taskAId);

      // Wait for agent completion
      await harness.advanceTimers();

      // Complete Task A
      await harness.dispatchManager.completeTask(taskAId);

      // Verify Task A removed from queue, B and C remain
      const queueStateAfter = await harness.dispatchManager.getQueueState();
      const remainingTaskIds = queueStateAfter.queued.map((t) => t.taskId);
      expect(remainingTaskIds).not.toContain(taskAId);
      expect(remainingTaskIds).toContain(taskBId);
      expect(remainingTaskIds).toContain(taskCId);

      // Task A marked completed in database
      const taskA = await harness.taskRepository.findById(taskAId);
      expect(taskA?.status).toBe('completed');
    });
  });

  // ===========================================================================
  // Scenario 3: Parallel Dispatch
  // ===========================================================================

  describe('Parallel dispatch', () => {
    it('dispatches multiple independent tasks to multiple agents', async () => {
      vi.useFakeTimers();
      const seeded = await harness.seedFixture(PARALLEL_FIXTURE);
      const taskXId = seeded.tasks.get('Task X')!;
      const taskYId = seeded.tasks.get('Task Y')!;
      const taskPId = seeded.tasks.get('Task P')!;
      const taskQId = seeded.tasks.get('Task Q')!;

      // Pre-seed 2 idle agents
      await harness.agentManager.spawn({
        name: 'pool-agent-1',
        taskId: 'placeholder-1',
        prompt: 'placeholder',
      });
      await harness.agentManager.spawn({
        name: 'pool-agent-2',
        taskId: 'placeholder-2',
        prompt: 'placeholder',
      });
      await harness.advanceTimers();
      harness.clearEvents();

      // Queue all 4 tasks
      await harness.dispatchManager.queue(taskXId);
      await harness.dispatchManager.queue(taskYId);
      await harness.dispatchManager.queue(taskPId);
      await harness.dispatchManager.queue(taskQId);
      harness.clearEvents();

      // All 4 tasks should be dispatchable (no dependencies)
      const queueState = await harness.dispatchManager.getQueueState();
      expect(queueState.ready.length).toBe(4);

      // Dispatch first task
      const result1 = await harness.dispatchManager.dispatchNext();
      expect(result1.success).toBe(true);

      // Dispatch second task (parallel)
      const result2 = await harness.dispatchManager.dispatchNext();
      expect(result2.success).toBe(true);

      // Verify both agents assigned different tasks
      expect(result1.taskId).not.toBe(result2.taskId);
      expect(result1.agentId).not.toBe(result2.agentId);

      // Both dispatches succeeded
      const dispatchedEvents = harness.getEventsByType('task:dispatched');
      expect(dispatchedEvents.length).toBe(2);
    });
  });

  // ===========================================================================
  // Scenario 4: Full Merge Flow
  // ===========================================================================

  describe('Full merge flow', () => {
    it('queues and processes merge after task completion', async () => {
      vi.useFakeTimers();
      const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
      const taskAId = seeded.tasks.get('Task A')!;

      // Pre-seed idle agent in MockAgentManager
      await harness.agentManager.spawn({
        name: 'pool-agent',
        taskId: 'placeholder',
        prompt: 'placeholder',
      });
      await harness.advanceTimers();
      harness.clearEvents();

      // Queue and dispatch task
      await harness.dispatchManager.queue(taskAId);
      const dispatchResult = await harness.dispatchManager.dispatchNext();
      expect(dispatchResult.success).toBe(true);

      // Wait for agent completion
      await harness.advanceTimers();

      // Complete task
      await harness.dispatchManager.completeTask(taskAId);
      harness.clearEvents();

      // Create agent in database (CoordinationManager.queueMerge requires it)
      // This bridges the gap between MockAgentManager (in-memory) and AgentRepository (database)
      const worktreeId = `worktree-${taskAId.slice(0, 8)}`;
      const agent = await harness.agentRepository.create({
        name: `agent-${taskAId.slice(0, 6)}`,
        taskId: taskAId,
        worktreeId,
        status: 'idle',
      });

      // Create worktree for merge
      await harness.worktreeManager.create(worktreeId, `feature-${taskAId.slice(0, 6)}`);

      // Queue merge
      await harness.coordinationManager.queueMerge(taskAId);

      // Verify merge:queued event
      const mergeQueuedEvents = harness.getEventsByType('merge:queued');
      expect(mergeQueuedEvents.length).toBe(1);

      // Process merges
      const mergeResults = await harness.coordinationManager.processMerges('main');
      expect(mergeResults.length).toBe(1);
      expect(mergeResults[0].taskId).toBe(taskAId);
      expect(mergeResults[0].success).toBe(true);

      // Verify merge:completed event
      const mergeCompletedEvents = harness.getEventsByType('merge:completed');
      expect(mergeCompletedEvents.length).toBe(1);
    });
  });

  // ===========================================================================
  // Scenario 5: Complex Dependency Flow
  // ===========================================================================

  describe('Complex dependency flow', () => {
    it('handles multi-level dependency graph with COMPLEX_FIXTURE', async () => {
      vi.useFakeTimers();
      const seeded = await harness.seedFixture(COMPLEX_FIXTURE);

      // Get all task IDs
      const task1AId = seeded.tasks.get('Task 1A')!;
      const task1BId = seeded.tasks.get('Task 1B')!;
      const task2AId = seeded.tasks.get('Task 2A')!;
      const task3AId = seeded.tasks.get('Task 3A')!;
      const task4AId = seeded.tasks.get('Task 4A')!;

      // Pre-seed idle agent
      await harness.agentManager.spawn({
        name: 'pool-agent',
        taskId: 'placeholder',
        prompt: 'placeholder',
      });
      await harness.advanceTimers();
      harness.clearEvents();

      // Queue all 5 tasks
      await harness.dispatchManager.queue(task1AId);
      await harness.dispatchManager.queue(task1BId);
      await harness.dispatchManager.queue(task2AId);
      await harness.dispatchManager.queue(task3AId);
      await harness.dispatchManager.queue(task4AId);
      harness.clearEvents();

      // Verify all 5 tasks are queued
      const initialState = await harness.dispatchManager.getQueueState();
      expect(initialState.queued.length).toBe(5);

      // Only tasks with no dependencies are ready:
      // - Task 1A: no deps -> READY
      // - Task 1B: no deps -> READY
      // - Task 2A: depends on 1A -> NOT READY
      // - Task 3A: depends on 1B -> NOT READY
      // - Task 4A: depends on 2A, 3A -> NOT READY
      expect(initialState.ready.length).toBe(2);

      // First dispatch: Task 1A (high priority, first queued)
      const result1 = await harness.dispatchManager.dispatchNext();
      expect(result1.success).toBe(true);
      expect(result1.taskId).toBe(task1AId);

      // Wait for agent completion
      await harness.advanceTimers();

      // Complete Task 1A
      await harness.dispatchManager.completeTask(task1AId);

      // Verify Task 1A completed in database
      const task1A = await harness.taskRepository.findById(task1AId);
      expect(task1A?.status).toBe('completed');

      // 4 tasks remain in queue
      const afterFirstState = await harness.dispatchManager.getQueueState();
      expect(afterFirstState.queued.length).toBe(4);

      // Dispatch and complete remaining tasks one by one
      // Task 1B (high priority among remaining)
      const result2 = await harness.dispatchManager.dispatchNext();
      expect(result2.success).toBe(true);
      await harness.advanceTimers();
      await harness.dispatchManager.completeTask(result2.taskId!);

      // 3 tasks remain
      const midState = await harness.dispatchManager.getQueueState();
      expect(midState.queued.length).toBe(3);

      // Continue dispatching remaining tasks
      const result3 = await harness.dispatchManager.dispatchNext();
      expect(result3.success).toBe(true);
      await harness.advanceTimers();
      await harness.dispatchManager.completeTask(result3.taskId!);

      const result4 = await harness.dispatchManager.dispatchNext();
      expect(result4.success).toBe(true);
      await harness.advanceTimers();
      await harness.dispatchManager.completeTask(result4.taskId!);

      const result5 = await harness.dispatchManager.dispatchNext();
      expect(result5.success).toBe(true);
      await harness.advanceTimers();
      await harness.dispatchManager.completeTask(result5.taskId!);

      // All tasks completed
      const finalState = await harness.dispatchManager.getQueueState();
      expect(finalState.queued.length).toBe(0);

      // Verify all 5 tasks completed in database
      const allTasks = await Promise.all([
        harness.taskRepository.findById(task1AId),
        harness.taskRepository.findById(task1BId),
        harness.taskRepository.findById(task2AId),
        harness.taskRepository.findById(task3AId),
        harness.taskRepository.findById(task4AId),
      ]);
      expect(allTasks.every((t) => t?.status === 'completed')).toBe(true);

      // Verify event sequence: 5 task:dispatched, 5 task:completed
      const dispatchedEvents = harness.getEventsByType('task:dispatched');
      expect(dispatchedEvents.length).toBe(5);

      const completedEvents = harness.getEventsByType('task:completed');
      expect(completedEvents.length).toBe(5);
    });

    it('fixture dependencies are stored correctly in database', async () => {
      const seeded = await harness.seedFixture(COMPLEX_FIXTURE);

      // Get task IDs
      const task1AId = seeded.tasks.get('Task 1A')!;
      const task1BId = seeded.tasks.get('Task 1B')!;
      const task2AId = seeded.tasks.get('Task 2A')!;
      const task3AId = seeded.tasks.get('Task 3A')!;
      const task4AId = seeded.tasks.get('Task 4A')!;

      // Query task_dependencies directly to verify fixture setup
      const { taskDependencies } = await import('../../db/schema.js');
      const { eq } = await import('drizzle-orm');

      // Task 2A should depend on Task 1A
      const task2ADeps = await harness.db
        .select()
        .from(taskDependencies)
        .where(eq(taskDependencies.taskId, task2AId));
      expect(task2ADeps.length).toBe(1);
      expect(task2ADeps[0].dependsOnTaskId).toBe(task1AId);

      // Task 3A should depend on Task 1B
      const task3ADeps = await harness.db
        .select()
        .from(taskDependencies)
        .where(eq(taskDependencies.taskId, task3AId));
      expect(task3ADeps.length).toBe(1);
      expect(task3ADeps[0].dependsOnTaskId).toBe(task1BId);

      // Task 4A should depend on both Task 2A and Task 3A
      const task4ADeps = await harness.db
        .select()
        .from(taskDependencies)
        .where(eq(taskDependencies.taskId, task4AId));
      expect(task4ADeps.length).toBe(2);
      const depIds = task4ADeps.map((d) => d.dependsOnTaskId);
      expect(depIds).toContain(task2AId);
      expect(depIds).toContain(task3AId);

      // Tasks 1A and 1B should have no dependencies
      const task1ADeps = await harness.db
        .select()
        .from(taskDependencies)
        .where(eq(taskDependencies.taskId, task1AId));
      expect(task1ADeps.length).toBe(0);

      const task1BDeps = await harness.db
        .select()
        .from(taskDependencies)
        .where(eq(taskDependencies.taskId, task1BId));
      expect(task1BDeps.length).toBe(0);
    });
  });
});
