/**
 * E2E Tests for Extended Scenarios
 *
 * Tests extended scenarios in dispatch/coordination flow:
 * - Conflict hand-back round-trip (conflict -> agent resolves -> merge succeeds)
 * - Multi-agent parallel work and completion
 *
 * Uses TestHarness from src/test/ for full system wiring.
 */

import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import {
  createTestHarness,
  SIMPLE_FIXTURE,
  PARALLEL_FIXTURE,
  COMPLEX_FIXTURE,
  type TestHarness,
} from '../index.js';
import type {
  MergeConflictedEvent,
  MergeCompletedEvent,
  TaskQueuedEvent,
  AgentStoppedEvent,
  AgentCrashedEvent,
} from '../../events/types.js';

describe('E2E Extended Scenarios', () => {
  let harness: TestHarness;

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

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

  // ===========================================================================
  // Conflict Hand-back Round-trip
  // ===========================================================================

  describe('Conflict hand-back round-trip', () => {
    it('conflict triggers resolution task, agent resolves, merge succeeds', async () => {
      vi.useFakeTimers();
      const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
      const taskAId = seeded.tasks.get('Task A')!;

      // Step 1: Complete Task A
      await harness.taskRepository.update(taskAId, { status: 'completed' });

      // Step 2: Create agent in agentRepository with worktreeId
      const worktreeId = `wt-${taskAId.slice(0, 6)}`;
      await harness.agentRepository.create({
        name: `agent-${taskAId.slice(0, 6)}`,
        worktreeId,
        taskId: taskAId,
        status: 'idle',
      });

      // Step 3: Create worktree via MockWorktreeManager
      await harness.worktreeManager.create(worktreeId, 'feature-task-a');

      // Step 4: Set merge conflict result for first merge attempt
      harness.worktreeManager.setMergeResult(worktreeId, {
        success: false,
        conflicts: ['src/shared.ts', 'src/types.ts'],
        message: 'Merge conflict in 2 files',
      });

      // Step 5: Queue and process merge (should fail with conflict)
      await harness.coordinationManager.queueMerge(taskAId);
      harness.clearEvents();
      const conflictResults = await harness.coordinationManager.processMerges('main');

      // Verify: merge failed with conflict
      expect(conflictResults.length).toBe(1);
      expect(conflictResults[0].success).toBe(false);
      expect(conflictResults[0].conflicts).toEqual(['src/shared.ts', 'src/types.ts']);

      // Verify: merge:conflicted event emitted
      const conflictedEvents = harness.getEventsByType('merge:conflicted');
      expect(conflictedEvents.length).toBe(1);
      const conflictPayload = (conflictedEvents[0] as MergeConflictedEvent).payload;
      expect(conflictPayload.taskId).toBe(taskAId);
      expect(conflictPayload.conflictingFiles).toEqual(['src/shared.ts', 'src/types.ts']);

      // Verify: original task marked blocked
      const originalTask = await harness.taskRepository.findById(taskAId);
      expect(originalTask?.status).toBe('blocked');

      // Note: CoordinationManager.handleConflict updates task status to blocked
      // but does not emit task:blocked event (that's emitted by DispatchManager.blockTask)

      // Verify: task:queued event emitted for resolution task
      const queuedEvents = harness.getEventsByType('task:queued');
      const resolutionTaskEvent = queuedEvents.find(
        (e) => (e as TaskQueuedEvent).payload.taskId !== taskAId
      );
      expect(resolutionTaskEvent).toBeDefined();

      // Step 6: Clear the merge conflict (setMergeResult to success)
      harness.worktreeManager.setMergeResult(worktreeId, {
        success: true,
        message: 'Merged successfully',
      });

      // Step 7: Re-queue original task for merge (simulating resolution completed)
      // In a real system, the resolution task would fix conflicts and re-queue
      // Here we simulate by clearing conflict and re-queuing
      await harness.taskRepository.update(taskAId, { status: 'completed' });
      harness.clearEvents();

      await harness.coordinationManager.queueMerge(taskAId);
      const successResults = await harness.coordinationManager.processMerges('main');

      // Verify: merge succeeded
      expect(successResults.length).toBe(1);
      expect(successResults[0].taskId).toBe(taskAId);
      expect(successResults[0].success).toBe(true);

      // Verify: merge:completed event for original task
      const completedEvents = harness.getEventsByType('merge:completed');
      expect(completedEvents.length).toBe(1);
      const completedPayload = (completedEvents[0] as MergeCompletedEvent).payload;
      expect(completedPayload.taskId).toBe(taskAId);
    });

    it('conflict resolution preserves original task context', async () => {
      vi.useFakeTimers();
      const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
      const taskAId = seeded.tasks.get('Task A')!;

      // Complete Task A
      await harness.taskRepository.update(taskAId, { status: 'completed' });

      // Create agent and worktree
      const worktreeId = `wt-${taskAId.slice(0, 6)}`;
      await harness.agentRepository.create({
        name: `agent-${taskAId.slice(0, 6)}`,
        worktreeId,
        taskId: taskAId,
        status: 'idle',
      });
      await harness.worktreeManager.create(worktreeId, 'feature-task-a');

      // Set conflict
      harness.worktreeManager.setMergeResult(worktreeId, {
        success: false,
        conflicts: ['src/conflict-file.ts'],
        message: 'Merge conflict',
      });

      // Process merge to trigger conflict handling
      await harness.coordinationManager.queueMerge(taskAId);
      harness.clearEvents();
      await harness.coordinationManager.processMerges('main');

      // Get the resolution task from task:queued events
      const queuedEvents = harness.getEventsByType('task:queued');
      expect(queuedEvents.length).toBeGreaterThan(0);

      // Find resolution task (the one that isn't the original task)
      const resolutionTaskQueuedEvent = queuedEvents.find(
        (e) => (e as TaskQueuedEvent).payload.taskId !== taskAId
      );
      expect(resolutionTaskQueuedEvent).toBeDefined();

      // Resolution task should exist and link back to original task
      const resolutionTaskId = (resolutionTaskQueuedEvent as TaskQueuedEvent).payload.taskId;
      const resolutionTask = await harness.taskRepository.findById(resolutionTaskId);
      expect(resolutionTask).toBeDefined();

      // Resolution task description should contain conflict file info
      expect(resolutionTask?.description).toContain('conflict');
    });

    it('multiple sequential conflicts resolved in order', async () => {
      vi.useFakeTimers();
      const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
      const taskAId = seeded.tasks.get('Task A')!;
      const taskBId = seeded.tasks.get('Task B')!;

      // Complete both tasks
      await harness.taskRepository.update(taskAId, { status: 'completed' });
      await harness.taskRepository.update(taskBId, { status: 'completed' });

      // Set up worktrees and agents for both tasks
      const worktreeIdA = `wt-${taskAId.slice(0, 6)}`;
      const worktreeIdB = `wt-${taskBId.slice(0, 6)}`;

      await harness.agentRepository.create({
        name: `agent-${taskAId.slice(0, 6)}`,
        worktreeId: worktreeIdA,
        taskId: taskAId,
        status: 'idle',
      });
      await harness.agentRepository.create({
        name: `agent-${taskBId.slice(0, 6)}`,
        worktreeId: worktreeIdB,
        taskId: taskBId,
        status: 'idle',
      });

      await harness.worktreeManager.create(worktreeIdA, 'feature-task-a');
      await harness.worktreeManager.create(worktreeIdB, 'feature-task-b');

      // Set conflicts for both
      harness.worktreeManager.setMergeResult(worktreeIdA, {
        success: false,
        conflicts: ['src/shared-a.ts'],
        message: 'Conflict A',
      });
      harness.worktreeManager.setMergeResult(worktreeIdB, {
        success: false,
        conflicts: ['src/shared-b.ts'],
        message: 'Conflict B',
      });

      // Queue both for merge
      await harness.coordinationManager.queueMerge(taskAId);
      await harness.coordinationManager.queueMerge(taskBId);
      harness.clearEvents();

      // Process merges - both should fail
      const conflictResults = await harness.coordinationManager.processMerges('main');
      expect(conflictResults.filter((r) => !r.success).length).toBe(2);

      // Verify both are in conflicted state
      const queueState = await harness.coordinationManager.getQueueState();
      expect(queueState.conflicted.length).toBe(2);

      // Resolve Task A's conflict
      harness.worktreeManager.setMergeResult(worktreeIdA, {
        success: true,
        message: 'Merged A',
      });
      await harness.taskRepository.update(taskAId, { status: 'completed' });
      await harness.coordinationManager.queueMerge(taskAId);
      harness.clearEvents();

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

      // Verify merge:completed for A
      const completedEventsA = harness.getEventsByType('merge:completed');
      expect(completedEventsA.length).toBe(1);
      expect((completedEventsA[0] as MergeCompletedEvent).payload.taskId).toBe(taskAId);

      // Resolve Task B's conflict
      harness.worktreeManager.setMergeResult(worktreeIdB, {
        success: true,
        message: 'Merged B',
      });
      await harness.taskRepository.update(taskBId, { status: 'completed' });
      await harness.coordinationManager.queueMerge(taskBId);
      harness.clearEvents();

      const resultB = await harness.coordinationManager.processMerges('main');
      expect(resultB.length).toBe(1);
      expect(resultB[0].taskId).toBe(taskBId);
      expect(resultB[0].success).toBe(true);

      // Verify merge:completed for B
      const completedEventsB = harness.getEventsByType('merge:completed');
      expect(completedEventsB.length).toBe(1);
      expect((completedEventsB[0] as MergeCompletedEvent).payload.taskId).toBe(taskBId);

      // Verify final merged list has both
      const finalState = await harness.coordinationManager.getQueueState();
      expect(finalState.merged).toContain(taskAId);
      expect(finalState.merged).toContain(taskBId);
    });
  });

  // ===========================================================================
  // Multi-agent Parallel Work
  // ===========================================================================

  describe('Multi-agent parallel work', () => {
    it('multiple agents complete tasks in parallel', 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 3 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.agentManager.spawn({
        name: 'pool-agent-3',
        taskId: 'placeholder-3',
        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();

      // Dispatch 3 tasks in parallel (3 agents working)
      const result1 = await harness.dispatchManager.dispatchNext();
      const result2 = await harness.dispatchManager.dispatchNext();
      const result3 = await harness.dispatchManager.dispatchNext();

      expect(result1.success).toBe(true);
      expect(result2.success).toBe(true);
      expect(result3.success).toBe(true);

      // All 3 should be dispatched to different agents
      const dispatchedIds = [result1.agentId, result2.agentId, result3.agentId];
      expect(new Set(dispatchedIds).size).toBe(3);

      // Advance timers to complete all 3 agents
      await harness.advanceTimers();

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

      // Complete all 3 tasks
      await harness.dispatchManager.completeTask(result1.taskId!);
      await harness.dispatchManager.completeTask(result2.taskId!);
      await harness.dispatchManager.completeTask(result3.taskId!);

      // Dispatch remaining task (Task Q)
      const result4 = await harness.dispatchManager.dispatchNext();
      expect(result4.success).toBe(true);

      await harness.advanceTimers();
      await harness.dispatchManager.completeTask(result4.taskId!);

      // Verify: all 4 tasks completed in database
      const tasks = await Promise.all([
        harness.taskRepository.findById(taskXId),
        harness.taskRepository.findById(taskYId),
        harness.taskRepository.findById(taskPId),
        harness.taskRepository.findById(taskQId),
      ]);
      expect(tasks.every((t) => t?.status === 'completed')).toBe(true);
    });

    it('parallel merges process in correct dependency order', async () => {
      vi.useFakeTimers();
      const seeded = await harness.seedFixture(COMPLEX_FIXTURE);

      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')!;

      // Complete Task 1A and Task 1B (no dependencies)
      await harness.taskRepository.update(task1AId, { status: 'completed' });
      await harness.taskRepository.update(task1BId, { status: 'completed' });

      // Set up worktrees and agents for both
      const wt1A = `wt-${task1AId.slice(0, 6)}`;
      const wt1B = `wt-${task1BId.slice(0, 6)}`;

      await harness.agentRepository.create({
        name: `agent-${task1AId.slice(0, 6)}`,
        worktreeId: wt1A,
        taskId: task1AId,
        status: 'idle',
      });
      await harness.agentRepository.create({
        name: `agent-${task1BId.slice(0, 6)}`,
        worktreeId: wt1B,
        taskId: task1BId,
        status: 'idle',
      });

      await harness.worktreeManager.create(wt1A, 'feature-1a');
      await harness.worktreeManager.create(wt1B, 'feature-1b');

      // Queue both for merge
      await harness.coordinationManager.queueMerge(task1AId);
      await harness.coordinationManager.queueMerge(task1BId);
      harness.clearEvents();

      // Process merges - both should succeed (no dependencies between them)
      const results1 = await harness.coordinationManager.processMerges('main');
      expect(results1.length).toBe(2);
      expect(results1.every((r) => r.success)).toBe(true);

      // Verify: merge:completed for both in same batch
      const completed1 = harness.getEventsByType('merge:completed');
      expect(completed1.length).toBe(2);

      // Complete Task 2A (depends on 1A) and Task 3A (depends on 1B)
      await harness.taskRepository.update(task2AId, { status: 'completed' });
      await harness.taskRepository.update(task3AId, { status: 'completed' });

      const wt2A = `wt-${task2AId.slice(0, 6)}`;
      const wt3A = `wt-${task3AId.slice(0, 6)}`;

      await harness.agentRepository.create({
        name: `agent-${task2AId.slice(0, 6)}`,
        worktreeId: wt2A,
        taskId: task2AId,
        status: 'idle',
      });
      await harness.agentRepository.create({
        name: `agent-${task3AId.slice(0, 6)}`,
        worktreeId: wt3A,
        taskId: task3AId,
        status: 'idle',
      });

      await harness.worktreeManager.create(wt2A, 'feature-2a');
      await harness.worktreeManager.create(wt3A, 'feature-3a');

      // Queue and merge
      await harness.coordinationManager.queueMerge(task2AId);
      await harness.coordinationManager.queueMerge(task3AId);
      harness.clearEvents();

      const results2 = await harness.coordinationManager.processMerges('main');
      expect(results2.length).toBe(2);
      expect(results2.every((r) => r.success)).toBe(true);

      // Complete Task 4A (depends on 2A and 3A)
      await harness.taskRepository.update(task4AId, { status: 'completed' });

      const wt4A = `wt-${task4AId.slice(0, 6)}`;
      await harness.agentRepository.create({
        name: `agent-${task4AId.slice(0, 6)}`,
        worktreeId: wt4A,
        taskId: task4AId,
        status: 'idle',
      });
      await harness.worktreeManager.create(wt4A, 'feature-4a');

      // Queue and merge
      await harness.coordinationManager.queueMerge(task4AId);
      harness.clearEvents();

      const results3 = await harness.coordinationManager.processMerges('main');
      expect(results3.length).toBe(1);
      expect(results3[0].taskId).toBe(task4AId);
      expect(results3[0].success).toBe(true);

      // Verify: final merge order respects dependency graph
      const finalState = await harness.coordinationManager.getQueueState();
      expect(finalState.merged).toContain(task1AId);
      expect(finalState.merged).toContain(task1BId);
      expect(finalState.merged).toContain(task2AId);
      expect(finalState.merged).toContain(task3AId);
      expect(finalState.merged).toContain(task4AId);
    });

    it('parallel dispatch with mixed outcomes', async () => {
      vi.useFakeTimers();
      const seeded = await harness.seedFixture(PARALLEL_FIXTURE);
      const taskXId = seeded.tasks.get('Task X')!;
      const taskYId = seeded.tasks.get('Task Y')!;

      // Pre-seed 2 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();

      // Set Task X to succeed, Task Y to crash
      harness.setAgentDone(`agent-${taskXId.slice(0, 6)}`, 'Task X completed');
      harness.setAgentError(`agent-${taskYId.slice(0, 6)}`, 'Out of memory error');

      // Queue both tasks
      await harness.dispatchManager.queue(taskXId);
      await harness.dispatchManager.queue(taskYId);
      harness.clearEvents();

      // Dispatch both tasks
      const result1 = await harness.dispatchManager.dispatchNext();
      const result2 = await harness.dispatchManager.dispatchNext();

      // Both should dispatch successfully
      expect(result1.success).toBe(true);
      expect(result2.success).toBe(true);

      // Run timers to complete agents
      await harness.advanceTimers();

      // Verify: one agent:stopped, one agent:crashed
      const stoppedEvents = harness.getEventsByType('agent:stopped');
      const crashedEvents = harness.getEventsByType('agent:crashed');

      expect(stoppedEvents.length).toBe(1);
      expect(crashedEvents.length).toBe(1);

      // Identify which task succeeded and which crashed
      const stoppedPayload = (stoppedEvents[0] as AgentStoppedEvent).payload;
      const crashedPayload = (crashedEvents[0] as AgentCrashedEvent).payload;

      // Find the successful task
      const successTaskId = stoppedPayload.taskId;
      const crashedTaskId = crashedPayload.taskId;

      // Complete the successful task
      await harness.dispatchManager.completeTask(successTaskId!);

      // Verify: completed task is actually completed
      const completedTask = await harness.taskRepository.findById(successTaskId!);
      expect(completedTask?.status).toBe('completed');

      // Verify: crashed task stays in_progress
      const inProgressTask = await harness.taskRepository.findById(crashedTaskId!);
      expect(inProgressTask?.status).toBe('in_progress');

      // Verify: completed task can merge (set up infrastructure)
      const wtSuccess = `wt-${successTaskId!.slice(0, 6)}`;
      await harness.agentRepository.create({
        name: `merge-agent-${successTaskId!.slice(0, 6)}`,
        worktreeId: wtSuccess,
        taskId: successTaskId!,
        status: 'idle',
      });
      await harness.worktreeManager.create(wtSuccess, 'feature-success');

      await harness.coordinationManager.queueMerge(successTaskId!);
      const mergeResults = await harness.coordinationManager.processMerges('main');

      expect(mergeResults.length).toBe(1);
      expect(mergeResults[0].success).toBe(true);
    });
  });
});
