/**
 * E2E Tests for Phase Parallel Execution
 *
 * Tests proving phase dispatch/coordination flow works end-to-end
 * using the TestHarness with phaseDispatchManager.
 */

import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { createTestHarness, type TestHarness } from '../index.js';
import type {
  PhaseQueuedEvent,
  PhaseStartedEvent,
  PhaseCompletedEvent,
  PhaseBlockedEvent,
} from '../../events/types.js';

describe('Phase Parallel Execution', () => {
  let harness: TestHarness;

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

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

  // ===========================================================================
  // Test 1: Independent phases dispatch in parallel
  // ===========================================================================

  describe('Independent phases dispatch in parallel', () => {
    it('dispatches multiple independent phases when no dependencies exist', async () => {
      // Create initiative with 2 independent phases (no dependencies)
      const initiative = await harness.initiativeRepository.create({
        name: 'Independent Phases Test',
        status: 'active',
      });

      const phaseA = await harness.phaseRepository.create({
        initiativeId: initiative.id,
        name: 'Phase A',
        content: 'Independent phase A',
        status: 'pending',
      });

      const phaseB = await harness.phaseRepository.create({
        initiativeId: initiative.id,
        name: 'Phase B',
        content: 'Independent phase B',
        status: 'pending',
      });

      // Approve phases before queuing
      await harness.phaseRepository.update(phaseA.id, { status: 'approved' as const });
      await harness.phaseRepository.update(phaseB.id, { status: 'approved' as const });

      // Queue both phases
      await harness.phaseDispatchManager.queuePhase(phaseA.id);
      await harness.phaseDispatchManager.queuePhase(phaseB.id);

      // Verify phase:queued events
      const queuedEvents = harness.getEventsByType('phase:queued');
      expect(queuedEvents.length).toBe(2);

      // Get queue state - both should be ready (no dependencies)
      const queueState = await harness.phaseDispatchManager.getPhaseQueueState();
      expect(queueState.queued.length).toBe(2);
      expect(queueState.ready.length).toBe(2);
      expect(queueState.blocked.length).toBe(0);

      // Both phases should be dispatchable immediately
      const readyPhaseIds = queueState.ready.map((p) => p.phaseId);
      expect(readyPhaseIds).toContain(phaseA.id);
      expect(readyPhaseIds).toContain(phaseB.id);

      harness.clearEvents();

      // Dispatch first phase
      const result1 = await harness.phaseDispatchManager.dispatchNextPhase();
      expect(result1.success).toBe(true);

      // Dispatch second phase (parallel)
      const result2 = await harness.phaseDispatchManager.dispatchNextPhase();
      expect(result2.success).toBe(true);

      // Verify both dispatched to different phases
      expect(result1.phaseId).not.toBe(result2.phaseId);

      // Verify phase:started events
      const startedEvents = harness.getEventsByType('phase:started');
      expect(startedEvents.length).toBe(2);

      // Verify both phases are now in_progress
      const updatedPhaseA = await harness.phaseRepository.findById(phaseA.id);
      const updatedPhaseB = await harness.phaseRepository.findById(phaseB.id);
      expect(updatedPhaseA?.status).toBe('in_progress');
      expect(updatedPhaseB?.status).toBe('in_progress');
    });
  });

  // ===========================================================================
  // Test 2: Dependent phase waits for prerequisite
  // ===========================================================================

  describe('Dependent phase waits for prerequisite', () => {
    it('only dispatches phase A first, then B after A completes', async () => {
      // Create phases: A, B (depends on A)
      const initiative = await harness.initiativeRepository.create({
        name: 'Sequential Phases Test',
        status: 'active',
      });

      const phaseA = await harness.phaseRepository.create({
        initiativeId: initiative.id,
        name: 'Phase A',
        content: 'First phase',
        status: 'pending',
      });

      const phaseB = await harness.phaseRepository.create({
        initiativeId: initiative.id,
        name: 'Phase B',
        content: 'Second phase, depends on A',
        status: 'pending',
      });

      // Approve phases before queuing
      await harness.phaseRepository.update(phaseA.id, { status: 'approved' as const });
      await harness.phaseRepository.update(phaseB.id, { status: 'approved' as const });

      // Create dependency: B depends on A
      await harness.phaseRepository.createDependency(phaseB.id, phaseA.id);

      // Queue both phases
      await harness.phaseDispatchManager.queuePhase(phaseA.id);
      await harness.phaseDispatchManager.queuePhase(phaseB.id);

      // Check queue state - only A should be ready
      const queueState1 = await harness.phaseDispatchManager.getPhaseQueueState();
      expect(queueState1.queued.length).toBe(2);
      expect(queueState1.ready.length).toBe(1);
      expect(queueState1.ready[0].phaseId).toBe(phaseA.id);

      harness.clearEvents();

      // Dispatch - should get phase A
      const result1 = await harness.phaseDispatchManager.dispatchNextPhase();
      expect(result1.success).toBe(true);
      expect(result1.phaseId).toBe(phaseA.id);

      // Try to dispatch again - should fail (B is blocked by A)
      const result2 = await harness.phaseDispatchManager.dispatchNextPhase();
      expect(result2.success).toBe(false);
      expect(result2.reason).toBe('No dispatchable phases');

      // Verify phase B still in queue but not ready
      const queueState2 = await harness.phaseDispatchManager.getPhaseQueueState();
      expect(queueState2.queued.length).toBe(1);
      expect(queueState2.ready.length).toBe(0);

      // Complete phase A
      await harness.phaseDispatchManager.completePhase(phaseA.id);

      // Verify phase:completed event for A
      const completedEvents = harness.getEventsByType('phase:completed');
      expect(completedEvents.length).toBe(1);
      expect((completedEvents[0] as PhaseCompletedEvent).payload.phaseId).toBe(phaseA.id);

      // Now B should be ready
      const queueState3 = await harness.phaseDispatchManager.getPhaseQueueState();
      expect(queueState3.ready.length).toBe(1);
      expect(queueState3.ready[0].phaseId).toBe(phaseB.id);

      harness.clearEvents();

      // Dispatch - should get phase B
      const result3 = await harness.phaseDispatchManager.dispatchNextPhase();
      expect(result3.success).toBe(true);
      expect(result3.phaseId).toBe(phaseB.id);

      // Verify phase B is now in_progress
      const updatedPhaseB = await harness.phaseRepository.findById(phaseB.id);
      expect(updatedPhaseB?.status).toBe('in_progress');
    });
  });

  // ===========================================================================
  // Test 3: Diamond dependency pattern
  // ===========================================================================

  describe('Diamond dependency pattern', () => {
    it('handles diamond: A -> B,C -> D correctly', async () => {
      // Create phases: A, B (depends on A), C (depends on A), D (depends on B, C)
      const initiative = await harness.initiativeRepository.create({
        name: 'Diamond Pattern Test',
        status: 'active',
      });

      const phaseA = await harness.phaseRepository.create({
        initiativeId: initiative.id,
        name: 'Phase A',
        content: 'Root phase',
        status: 'pending',
      });

      const phaseB = await harness.phaseRepository.create({
        initiativeId: initiative.id,
        name: 'Phase B',
        content: 'Depends on A',
        status: 'pending',
      });

      const phaseC = await harness.phaseRepository.create({
        initiativeId: initiative.id,
        name: 'Phase C',
        content: 'Depends on A',
        status: 'pending',
      });

      const phaseD = await harness.phaseRepository.create({
        initiativeId: initiative.id,
        name: 'Phase D',
        content: 'Depends on B and C',
        status: 'pending',
      });

      // Approve all phases before queuing
      await harness.phaseRepository.update(phaseA.id, { status: 'approved' as const });
      await harness.phaseRepository.update(phaseB.id, { status: 'approved' as const });
      await harness.phaseRepository.update(phaseC.id, { status: 'approved' as const });
      await harness.phaseRepository.update(phaseD.id, { status: 'approved' as const });

      // Create dependencies
      await harness.phaseRepository.createDependency(phaseB.id, phaseA.id);
      await harness.phaseRepository.createDependency(phaseC.id, phaseA.id);
      await harness.phaseRepository.createDependency(phaseD.id, phaseB.id);
      await harness.phaseRepository.createDependency(phaseD.id, phaseC.id);

      // Queue all phases
      await harness.phaseDispatchManager.queuePhase(phaseA.id);
      await harness.phaseDispatchManager.queuePhase(phaseB.id);
      await harness.phaseDispatchManager.queuePhase(phaseC.id);
      await harness.phaseDispatchManager.queuePhase(phaseD.id);

      // Step 1: Only A should be ready
      const state1 = await harness.phaseDispatchManager.getPhaseQueueState();
      expect(state1.queued.length).toBe(4);
      expect(state1.ready.length).toBe(1);
      expect(state1.ready[0].phaseId).toBe(phaseA.id);

      // Dispatch A
      const resultA = await harness.phaseDispatchManager.dispatchNextPhase();
      expect(resultA.success).toBe(true);
      expect(resultA.phaseId).toBe(phaseA.id);

      // Step 2: After A completes, B and C should be ready (parallel)
      await harness.phaseDispatchManager.completePhase(phaseA.id);

      const state2 = await harness.phaseDispatchManager.getPhaseQueueState();
      expect(state2.queued.length).toBe(3); // B, C, D still queued
      expect(state2.ready.length).toBe(2); // B and C ready

      const readyIds = state2.ready.map((p) => p.phaseId);
      expect(readyIds).toContain(phaseB.id);
      expect(readyIds).toContain(phaseC.id);
      expect(readyIds).not.toContain(phaseD.id);

      // Dispatch B and C in parallel
      const resultB = await harness.phaseDispatchManager.dispatchNextPhase();
      expect(resultB.success).toBe(true);

      const resultC = await harness.phaseDispatchManager.dispatchNextPhase();
      expect(resultC.success).toBe(true);

      // Verify D is still not ready (needs both B and C complete)
      const state3 = await harness.phaseDispatchManager.getPhaseQueueState();
      expect(state3.ready.length).toBe(0);
      expect(state3.queued.length).toBe(1);
      expect(state3.queued[0].phaseId).toBe(phaseD.id);

      // Step 3: Complete B only - D still not ready
      await harness.phaseDispatchManager.completePhase(resultB.phaseId);

      const state4 = await harness.phaseDispatchManager.getPhaseQueueState();
      expect(state4.ready.length).toBe(0); // D still blocked by C

      // Step 4: Complete C - now D should be ready
      await harness.phaseDispatchManager.completePhase(resultC.phaseId);

      const state5 = await harness.phaseDispatchManager.getPhaseQueueState();
      expect(state5.ready.length).toBe(1);
      expect(state5.ready[0].phaseId).toBe(phaseD.id);

      // Dispatch D
      const resultD = await harness.phaseDispatchManager.dispatchNextPhase();
      expect(resultD.success).toBe(true);
      expect(resultD.phaseId).toBe(phaseD.id);

      // Verify D is now in_progress
      const updatedPhaseD = await harness.phaseRepository.findById(phaseD.id);
      expect(updatedPhaseD?.status).toBe('in_progress');
    });
  });

  // ===========================================================================
  // Test 4: Approval gate rejects non-approved phases
  // ===========================================================================

  describe('Approval gate rejects non-approved phases', () => {
    it('rejects queuePhase for pending phase', async () => {
      const initiative = await harness.initiativeRepository.create({
        name: 'Approval Gate Test',
        status: 'active',
      });

      const phase = await harness.phaseRepository.create({
        initiativeId: initiative.id,
        name: 'Unapproved Phase',
        status: 'pending',
      });

      await expect(
        harness.phaseDispatchManager.queuePhase(phase.id)
      ).rejects.toThrow('must be approved before queuing');
    });

    it('rejects queuePhase for in_progress phase', async () => {
      const initiative = await harness.initiativeRepository.create({
        name: 'Approval Gate Test 2',
        status: 'active',
      });

      const phase = await harness.phaseRepository.create({
        initiativeId: initiative.id,
        name: 'In Progress Phase',
        status: 'in_progress',
      });

      await expect(
        harness.phaseDispatchManager.queuePhase(phase.id)
      ).rejects.toThrow('must be approved before queuing');
    });
  });

  // ===========================================================================
  // Test 5: Blocked phase doesn't dispatch
  // ===========================================================================

  describe('Blocked phase does not dispatch', () => {
    it('prevents dispatch of blocked phase even if dependencies complete', async () => {
      // Create phases: A, B (depends on A)
      const initiative = await harness.initiativeRepository.create({
        name: 'Blocked Phase Test',
        status: 'active',
      });

      const phaseA = await harness.phaseRepository.create({
        initiativeId: initiative.id,
        name: 'Phase A',
        content: 'First phase that will be blocked',
        status: 'pending',
      });

      const phaseB = await harness.phaseRepository.create({
        initiativeId: initiative.id,
        name: 'Phase B',
        content: 'Second phase, depends on A',
        status: 'pending',
      });

      // Approve phases before queuing
      await harness.phaseRepository.update(phaseA.id, { status: 'approved' as const });
      await harness.phaseRepository.update(phaseB.id, { status: 'approved' as const });

      // Create dependency: B depends on A
      await harness.phaseRepository.createDependency(phaseB.id, phaseA.id);

      // Queue phase A
      await harness.phaseDispatchManager.queuePhase(phaseA.id);

      // Block phase A
      await harness.phaseDispatchManager.blockPhase(phaseA.id, 'External dependency unavailable');

      // Verify phase:blocked event
      const blockedEvents = harness.getEventsByType('phase:blocked');
      expect(blockedEvents.length).toBe(1);
      expect((blockedEvents[0] as PhaseBlockedEvent).payload.phaseId).toBe(phaseA.id);
      expect((blockedEvents[0] as PhaseBlockedEvent).payload.reason).toBe(
        'External dependency unavailable'
      );

      // Try to dispatch - should fail
      const result = await harness.phaseDispatchManager.dispatchNextPhase();
      expect(result.success).toBe(false);
      expect(result.reason).toBe('No dispatchable phases');

      // Verify queue state shows A as blocked
      const queueState = await harness.phaseDispatchManager.getPhaseQueueState();
      expect(queueState.blocked.length).toBe(1);
      expect(queueState.blocked[0].phaseId).toBe(phaseA.id);
      expect(queueState.blocked[0].reason).toBe('External dependency unavailable');

      // Queue phase B
      await harness.phaseDispatchManager.queuePhase(phaseB.id);

      // B should never become ready because A is blocked (not completed)
      const queueState2 = await harness.phaseDispatchManager.getPhaseQueueState();
      expect(queueState2.ready.length).toBe(0);
      expect(queueState2.queued.length).toBe(1); // Only B is queued (A is blocked, not queued)
      expect(queueState2.queued[0].phaseId).toBe(phaseB.id);

      // Try to dispatch B - should fail
      const resultB = await harness.phaseDispatchManager.dispatchNextPhase();
      expect(resultB.success).toBe(false);
      expect(resultB.reason).toBe('No dispatchable phases');

      // Verify phase A status is blocked in database
      const updatedPhaseA = await harness.phaseRepository.findById(phaseA.id);
      expect(updatedPhaseA?.status).toBe('blocked');
    });

    it('blocked phase prevents all downstream phases from dispatching', async () => {
      // Create chain: A -> B -> C, then block A
      const initiative = await harness.initiativeRepository.create({
        name: 'Chain Block Test',
        status: 'active',
      });

      const phaseA = await harness.phaseRepository.create({
        initiativeId: initiative.id,
        name: 'Phase A',
        content: 'Root phase',
        status: 'pending',
      });

      const phaseB = await harness.phaseRepository.create({
        initiativeId: initiative.id,
        name: 'Phase B',
        content: 'Depends on A',
        status: 'pending',
      });

      const phaseC = await harness.phaseRepository.create({
        initiativeId: initiative.id,
        name: 'Phase C',
        content: 'Depends on B',
        status: 'pending',
      });

      // Approve all phases before queuing
      await harness.phaseRepository.update(phaseA.id, { status: 'approved' as const });
      await harness.phaseRepository.update(phaseB.id, { status: 'approved' as const });
      await harness.phaseRepository.update(phaseC.id, { status: 'approved' as const });

      // Create dependency chain: A -> B -> C
      await harness.phaseRepository.createDependency(phaseB.id, phaseA.id);
      await harness.phaseRepository.createDependency(phaseC.id, phaseB.id);

      // Queue all phases
      await harness.phaseDispatchManager.queuePhase(phaseA.id);
      await harness.phaseDispatchManager.queuePhase(phaseB.id);
      await harness.phaseDispatchManager.queuePhase(phaseC.id);

      // Block phase A
      await harness.phaseDispatchManager.blockPhase(phaseA.id, 'Resource unavailable');

      // Verify only B and C are in queue (A is blocked)
      const queueState = await harness.phaseDispatchManager.getPhaseQueueState();
      expect(queueState.queued.length).toBe(2);
      expect(queueState.ready.length).toBe(0); // Neither B nor C can dispatch
      expect(queueState.blocked.length).toBe(1);

      // Try to dispatch any phase - should fail for all
      const result = await harness.phaseDispatchManager.dispatchNextPhase();
      expect(result.success).toBe(false);
      expect(result.reason).toBe('No dispatchable phases');
    });
  });
});
