/**
 * E2E Tests for Recovery and Extended Scenarios
 *
 * Tests recovery/resume after interruption scenarios:
 * - Queue state survives harness recreation (DB is source of truth)
 * - In-progress task recoverable after agent crash
 * - Blocked task state persists and can be unblocked
 * - Merge queue state recoverable
 *
 * Tests extended agent Q&A scenarios:
 * - Multiple questions in sequence
 * - Question surfaces in message queue
 * - Agent resumes with answer in context
 * - Waiting agent blocks task completion
 *
 * Uses TestHarness from src/test/ for full system wiring.
 */

import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import {
  createTestHarness,
  SIMPLE_FIXTURE,
  type TestHarness,
} from '../index.js';
import type {
  AgentWaitingEvent,
  AgentResumedEvent,
  AgentStoppedEvent,
} from '../../events/types.js';

describe('E2E Recovery Scenarios', () => {
  describe('Recovery after interruption', () => {
    let harness: TestHarness;

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

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

    it('queue state survives in database (source of truth)', async () => {
      // Seed fixture, queue tasks
      const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
      const taskAId = seeded.tasks.get('Task A')!;

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

      // Verify queue state shows task (queued, not pending)
      const queueState1 = await harness.dispatchManager.getQueueState();
      expect(queueState1.queued.length).toBe(1);
      expect(queueState1.queued[0].taskId).toBe(taskAId);

      // The queue state is in memory, but task status is in DB.
      // Verify task status in database directly
      const task = await harness.taskRepository.findById(taskAId);
      expect(task?.status).toBe('pending');

      // Verify: even after clearing in-memory queue state,
      // we can still find pending tasks from database
      const allTasks = await harness.taskRepository.findByParentTaskId(
        seeded.taskGroups.get('Task Group 1')!
      );
      const pendingTasks = allTasks.filter((t) => t.status === 'pending');

      // Task A is pending (not queued, but status is pending)
      // Task B and C are also pending but depend on Task A
      expect(pendingTasks.length).toBeGreaterThanOrEqual(1);
    });

    it('in-progress task recoverable after agent crash', async () => {
      vi.useFakeTimers();
      const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
      const taskAId = seeded.tasks.get('Task A')!;

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

      // Set crash scenario
      harness.setAgentError(`agent-${taskAId.slice(0, 6)}`, 'Token limit exceeded');

      // Queue and dispatch
      await harness.dispatchManager.queue(taskAId);
      await harness.dispatchManager.dispatchNext();
      await harness.advanceTimers();

      // Verify task status is 'in_progress' (not completed, not lost)
      let task = await harness.taskRepository.findById(taskAId);
      expect(task?.status).toBe('in_progress');

      // Task can be re-queued and dispatched to a new agent
      // First, clear agent manager and create new pool agent
      harness.agentManager.clear();
      await harness.agentManager.spawn({
        name: 'new-pool-agent',
        taskId: 'placeholder',
        prompt: 'placeholder',
      });
      await harness.advanceTimers();

      // Re-queue the task (it's still in_progress but we can retry)
      await harness.dispatchManager.queue(taskAId);

      // Set success scenario for the new agent
      harness.setAgentDone(`agent-${taskAId.slice(0, 6)}`, 'Task completed after retry');

      // Clear events and dispatch again
      harness.clearEvents();
      const dispatchResult = await harness.dispatchManager.dispatchNext();
      await harness.advanceTimers();

      // Verify: agent completed successfully
      expect(dispatchResult.agentId).toBeDefined();
      const agentResult = await harness.agentManager.getResult(dispatchResult.agentId!);
      expect(agentResult?.success).toBe(true);
    });

    it('blocked task state persists in database', async () => {
      const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
      const taskAId = seeded.tasks.get('Task A')!;

      // Queue task and block it
      await harness.dispatchManager.queue(taskAId);
      await harness.dispatchManager.blockTask(taskAId, 'Waiting for user decision');

      // Verify task in blocked state in DB
      const task = await harness.taskRepository.findById(taskAId);
      expect(task?.status).toBe('blocked');

      // Query blocked tasks from queue state
      const queueState = await harness.dispatchManager.getQueueState();
      expect(queueState.blocked.length).toBe(1);
      expect(queueState.blocked[0].taskId).toBe(taskAId);
      expect(queueState.blocked[0].reason).toBe('Waiting for user decision');

      // Re-queue task to unblock (set status back to pending via repository)
      await harness.taskRepository.update(taskAId, { status: 'pending' });
      await harness.dispatchManager.queue(taskAId);

      // Verify: task now in pending state in database
      const unblocked = await harness.taskRepository.findById(taskAId);
      expect(unblocked?.status).toBe('pending');

      // Task should be in queued list
      const queueState2 = await harness.dispatchManager.getQueueState();
      expect(queueState2.queued.some((t) => t.taskId === taskAId)).toBe(true);
    });

    it('merge queue state recoverable', async () => {
      const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
      const taskAId = seeded.tasks.get('Task A')!;

      // Mark task as completed (required for merge)
      await harness.taskRepository.update(taskAId, { status: 'completed' });

      // Create worktree for task
      const worktreeId = `wt-${taskAId.slice(0, 6)}`;
      await harness.worktreeManager.create(worktreeId, 'feature-task-a');

      // Create agent in agentRepository (required for merge lookup)
      await harness.agentRepository.create({
        name: `agent-${taskAId.slice(0, 6)}`,
        worktreeId,
        taskId: taskAId,
        status: 'idle',
      });

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

      // Verify merge queue has queued item
      const queueState1 = await harness.coordinationManager.getQueueState();
      expect(queueState1.queued.some((item) => item.taskId === taskAId)).toBe(true);

      // Process merge
      const results = await harness.coordinationManager.processMerges('main');

      // Verify: merge completed correctly
      expect(results.length).toBe(1);
      expect(results[0].taskId).toBe(taskAId);
      expect(results[0].success).toBe(true);

      // Verify: task in merged list
      const queueState2 = await harness.coordinationManager.getQueueState();
      expect(queueState2.merged.includes(taskAId)).toBe(true);
    });
  });

  describe('Agent Q&A extended scenarios', () => {
    let harness: TestHarness;

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

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

    it('question enters waiting state and completes after resume', async () => {
      vi.useFakeTimers();
      const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
      const taskAId = seeded.tasks.get('Task A')!;

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

      // Set questions scenario with options
      harness.setAgentQuestions(`agent-${taskAId.slice(0, 6)}`, [
        {
          id: 'q1',
          question: 'Which database should I use?',
          options: [
            { label: 'PostgreSQL', description: 'Relational, ACID compliant' },
            { label: 'SQLite', description: 'Lightweight, file-based' },
          ],
        },
      ]);

      // Queue and dispatch
      await harness.dispatchManager.queue(taskAId);
      harness.clearEvents();

      const dispatchResult = await harness.dispatchManager.dispatchNext();
      await harness.advanceTimers();

      // Verify: agent:waiting event emitted
      const waitingEvents = harness.getEventsByType('agent:waiting');
      expect(waitingEvents.length).toBe(1);
      const waitingPayload = (waitingEvents[0] as AgentWaitingEvent).payload;
      expect(waitingPayload.taskId).toBe(taskAId);
      expect(waitingPayload.questions[0].question).toBe('Which database should I use?');

      // Clear and resume with answers map
      harness.clearEvents();
      await harness.agentManager.resume(dispatchResult.agentId!, { q1: 'PostgreSQL' });
      await harness.advanceTimers();

      // Verify: resumed and stopped events
      const resumedEvents = harness.getEventsByType('agent:resumed');
      expect(resumedEvents.length).toBe(1);
      const resumedPayload = (resumedEvents[0] as AgentResumedEvent).payload;
      expect(resumedPayload.taskId).toBe(taskAId);

      const stoppedEvents = harness.getEventsByType('agent:stopped');
      expect(stoppedEvents.length).toBe(1);
      const stoppedPayload = (stoppedEvents[0] as AgentStoppedEvent).payload;
      expect(stoppedPayload.reason).toBe('task_complete');
    });

    it('questions surface as structured PendingQuestions', async () => {
      vi.useFakeTimers();
      const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
      const taskAId = seeded.tasks.get('Task A')!;

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

      // Set questions scenario with options
      harness.setAgentQuestions(`agent-${taskAId.slice(0, 6)}`, [
        {
          id: 'q1',
          question: 'Select your framework',
          options: [
            { label: 'React' },
            { label: 'Vue' },
            { label: 'Svelte' },
          ],
        },
      ]);

      // Queue and dispatch
      await harness.dispatchManager.queue(taskAId);
      const dispatchResult = await harness.dispatchManager.dispatchNext();
      await harness.advanceTimers();

      // Verify: agent:waiting event has questions
      const waitingEvents = harness.getEventsByType('agent:waiting');
      expect(waitingEvents.length).toBe(1);
      const waitingPayload = (waitingEvents[0] as AgentWaitingEvent).payload;
      expect(waitingPayload.questions[0].question).toBe('Select your framework');
      expect(waitingPayload.questions[0].options).toEqual([
        { label: 'React' },
        { label: 'Vue' },
        { label: 'Svelte' },
      ]);

      // Verify: getPendingQuestions returns structured data
      const pendingQuestions = await harness.getPendingQuestions(dispatchResult.agentId!);
      expect(pendingQuestions).not.toBeNull();
      expect(pendingQuestions?.questions[0].question).toBe('Select your framework');
      expect(pendingQuestions?.questions[0].options).toEqual([
        { label: 'React' },
        { label: 'Vue' },
        { label: 'Svelte' },
      ]);
    });

    it('agent resumes with answer and completes successfully', async () => {
      vi.useFakeTimers();
      const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
      const taskAId = seeded.tasks.get('Task A')!;

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

      // Set questions scenario
      harness.setAgentQuestions(`agent-${taskAId.slice(0, 6)}`, [
        { id: 'q1', question: 'Choose database type' },
      ]);

      // Queue and dispatch
      await harness.dispatchManager.queue(taskAId);
      const dispatchResult = await harness.dispatchManager.dispatchNext();
      await harness.advanceTimers();

      // Verify agent is waiting
      const agent = await harness.agentManager.get(dispatchResult.agentId!);
      expect(agent?.status).toBe('waiting_for_input');

      // Resume with answers map
      await harness.agentManager.resume(dispatchResult.agentId!, { q1: 'PostgreSQL' });
      await harness.advanceTimers();

      // Verify: agent completed successfully
      const agentResult = await harness.agentManager.getResult(dispatchResult.agentId!);
      expect(agentResult).not.toBeNull();
      expect(agentResult?.success).toBe(true);
      expect(agentResult?.message).toBe('Resumed and completed successfully');

      // Verify: agent status is now idle
      const finalAgent = await harness.agentManager.get(dispatchResult.agentId!);
      expect(finalAgent?.status).toBe('idle');
    });

    it('waiting agent status transitions correctly through full cycle', async () => {
      vi.useFakeTimers();
      const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
      const taskAId = seeded.tasks.get('Task A')!;

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

      // Set questions scenario
      harness.setAgentQuestions(`agent-${taskAId.slice(0, 6)}`, [
        { id: 'q1', question: 'API key format?' },
      ]);

      // Queue and dispatch
      await harness.dispatchManager.queue(taskAId);
      const dispatchResult = await harness.dispatchManager.dispatchNext();

      // Phase 1: Initially running
      let agent = await harness.agentManager.get(dispatchResult.agentId!);
      expect(agent?.status).toBe('running');

      await harness.advanceTimers();

      // Phase 2: After scenario completes, waiting_for_input
      agent = await harness.agentManager.get(dispatchResult.agentId!);
      expect(agent?.status).toBe('waiting_for_input');

      // Verify pending questions exist
      const pendingQuestions = await harness.getPendingQuestions(dispatchResult.agentId!);
      expect(pendingQuestions?.questions[0].question).toBe('API key format?');

      // Phase 3: Resume with answers map
      await harness.agentManager.resume(dispatchResult.agentId!, { q1: 'Bearer token' });

      // After resume: running again briefly
      agent = await harness.agentManager.get(dispatchResult.agentId!);
      expect(agent?.status).toBe('running');

      await harness.advanceTimers();

      // Phase 4: After completion, idle
      agent = await harness.agentManager.get(dispatchResult.agentId!);
      expect(agent?.status).toBe('idle');

      // Verify pending questions is cleared after resume
      const clearedQuestions = await harness.getPendingQuestions(dispatchResult.agentId!);
      expect(clearedQuestions).toBeNull();
    });

    it('should handle agent asking multiple questions at once', async () => {
      vi.useFakeTimers();
      const seeded = await harness.seedFixture(SIMPLE_FIXTURE);
      const taskAId = seeded.tasks.get('Task A')!;

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

      // Setup: agent asks two questions
      harness.setAgentQuestions(`agent-${taskAId.slice(0, 6)}`, [
        {
          id: 'q1',
          question: 'Which database?',
          options: [{ label: 'SQLite' }, { label: 'Postgres' }],
        },
        {
          id: 'q2',
          question: 'Include tests?',
          options: [{ label: 'Yes' }, { label: 'No' }],
        },
      ]);

      // Queue and dispatch task
      await harness.dispatchManager.queue(taskAId);
      harness.clearEvents();

      const dispatchResult = await harness.dispatchManager.dispatchNext();
      await harness.advanceTimers();

      // Verify: agent:waiting event emitted
      const waitingEvents = harness.getEventsByType('agent:waiting');
      expect(waitingEvents.length).toBe(1);
      const waitingPayload = (waitingEvents[0] as AgentWaitingEvent).payload;
      expect(waitingPayload.taskId).toBe(taskAId);

      // Verify both questions present
      const pending = await harness.getPendingQuestions(dispatchResult.agentId!);
      expect(pending?.questions).toHaveLength(2);
      expect(pending?.questions[0].id).toBe('q1');
      expect(pending?.questions[0].question).toBe('Which database?');
      expect(pending?.questions[1].id).toBe('q2');
      expect(pending?.questions[1].question).toBe('Include tests?');

      // Resume with answers for both questions
      harness.clearEvents();
      await harness.agentManager.resume(dispatchResult.agentId!, {
        q1: 'SQLite',
        q2: 'Yes',
      });
      await harness.advanceTimers();

      // Verify: agent:resumed event emitted
      const resumedEvents = harness.getEventsByType('agent:resumed');
      expect(resumedEvents.length).toBe(1);

      // Verify: agent:stopped event emitted (after resume completes)
      const stoppedEvents = harness.getEventsByType('agent:stopped');
      expect(stoppedEvents.length).toBe(1);
      const stoppedPayload = (stoppedEvents[0] as AgentStoppedEvent).payload;
      expect(stoppedPayload.taskId).toBe(taskAId);
      expect(stoppedPayload.reason).toBe('task_complete');

      // Verify task completed (agent result)
      const agentResult = await harness.agentManager.getResult(dispatchResult.agentId!);
      expect(agentResult?.success).toBe(true);

      // Verify agent is now idle
      const finalAgent = await harness.agentManager.get(dispatchResult.agentId!);
      expect(finalAgent?.status).toBe('idle');
    });
  });
});
