/**
 * ConflictResolutionService Tests
 *
 * Tests for the conflict resolution service that handles merge conflicts
 * by creating resolution tasks, updating statuses, and notifying agents.
 */

import { describe, it, expect, beforeEach, vi } from 'vitest';
import { DefaultConflictResolutionService } from './conflict-resolution-service.js';
import { DrizzleTaskRepository } from '../db/repositories/drizzle/task.js';
import { DrizzleAgentRepository } from '../db/repositories/drizzle/agent.js';
import { DrizzleMessageRepository } from '../db/repositories/drizzle/message.js';
import { DrizzlePhaseRepository } from '../db/repositories/drizzle/phase.js';
import { DrizzleInitiativeRepository } from '../db/repositories/drizzle/initiative.js';
import { createTestDatabase } from '../db/repositories/drizzle/test-helpers.js';
import type { DrizzleDatabase } from '../db/index.js';
import type { EventBus, DomainEvent } from '../events/types.js';
import type { TaskRepository } from '../db/repositories/task-repository.js';
import type { AgentRepository } from '../db/repositories/agent-repository.js';
import type { MessageRepository } from '../db/repositories/message-repository.js';

// =============================================================================
// Test Helpers
// =============================================================================

/**
 * Create a mock EventBus that captures emitted events.
 */
function createMockEventBus(): EventBus & { emittedEvents: DomainEvent[] } {
  const emittedEvents: DomainEvent[] = [];

  return {
    emittedEvents,
    emit<T extends DomainEvent>(event: T): void {
      emittedEvents.push(event);
    },
    on: vi.fn(),
    off: vi.fn(),
    once: vi.fn(),
  };
}

// =============================================================================
// Tests
// =============================================================================

describe('DefaultConflictResolutionService', () => {
  let db: DrizzleDatabase;
  let taskRepository: TaskRepository;
  let agentRepository: AgentRepository;
  let messageRepository: MessageRepository;
  let eventBus: EventBus & { emittedEvents: DomainEvent[] };
  let service: DefaultConflictResolutionService;
  let testPhaseId: string;
  let testInitiativeId: string;

  beforeEach(async () => {
    // Set up test database
    db = createTestDatabase();
    taskRepository = new DrizzleTaskRepository(db);
    agentRepository = new DrizzleAgentRepository(db);
    messageRepository = new DrizzleMessageRepository(db);

    // Create required hierarchy for tasks
    const initiativeRepo = new DrizzleInitiativeRepository(db);
    const phaseRepo = new DrizzlePhaseRepository(db);

    const initiative = await initiativeRepo.create({
      name: 'Test Initiative',
    });
    testInitiativeId = initiative.id;

    const phase = await phaseRepo.create({
      initiativeId: initiative.id,
      name: 'Test Phase',
    });
    testPhaseId = phase.id;

    // Create mocks
    eventBus = createMockEventBus();

    // Create service
    service = new DefaultConflictResolutionService(
      taskRepository,
      agentRepository,
      messageRepository,
      eventBus
    );
  });

  // ===========================================================================
  // handleConflict() Tests
  // ===========================================================================

  describe('handleConflict', () => {
    it('should create conflict resolution task with correct properties', async () => {
      // Create original task
      const originalTask = await taskRepository.create({
        phaseId: testPhaseId,
        initiativeId: testInitiativeId,
        name: 'Original Task',
        description: 'Original task description',
        priority: 'medium',
        order: 1,
      });

      // Create agent for task
      const agent = await agentRepository.create({
        name: 'agent-test',
        taskId: originalTask.id,
        worktreeId: 'wt-test',
      });

      const conflicts = ['src/file1.ts', 'src/file2.ts'];

      await service.handleConflict(originalTask.id, conflicts);

      // Check resolution task was created
      const tasks = await taskRepository.findByPhaseId(testPhaseId);
      const resolutionTask = tasks.find(t => t.name.startsWith('Resolve conflicts:'));

      expect(resolutionTask).toBeDefined();
      expect(resolutionTask!.name).toBe('Resolve conflicts: Original Task');
      expect(resolutionTask!.priority).toBe('high');
      expect(resolutionTask!.type).toBe('auto');
      expect(resolutionTask!.status).toBe('pending');
      expect(resolutionTask!.order).toBe(originalTask.order + 1);
      expect(resolutionTask!.phaseId).toBe(testPhaseId);
      expect(resolutionTask!.initiativeId).toBe(testInitiativeId);
      expect(resolutionTask!.parentTaskId).toBe(originalTask.parentTaskId);

      // Check description contains conflict files
      expect(resolutionTask!.description).toContain('src/file1.ts');
      expect(resolutionTask!.description).toContain('src/file2.ts');
      expect(resolutionTask!.description).toContain('Original Task');
    });

    it('should update original task status to blocked', async () => {
      const originalTask = await taskRepository.create({
        phaseId: testPhaseId,
        initiativeId: testInitiativeId,
        name: 'Task To Block',
        status: 'in_progress',
        order: 1,
      });

      await agentRepository.create({
        name: 'agent-block',
        taskId: originalTask.id,
        worktreeId: 'wt-block',
      });

      await service.handleConflict(originalTask.id, ['conflict.ts']);

      // Check original task is blocked
      const updatedTask = await taskRepository.findById(originalTask.id);
      expect(updatedTask!.status).toBe('blocked');
    });

    it('should create message to agent about conflict', async () => {
      const originalTask = await taskRepository.create({
        phaseId: testPhaseId,
        initiativeId: testInitiativeId,
        name: 'Message Task',
        order: 1,
      });

      const agent = await agentRepository.create({
        name: 'agent-msg',
        taskId: originalTask.id,
        worktreeId: 'wt-msg',
      });

      const conflicts = ['conflict.ts'];

      await service.handleConflict(originalTask.id, conflicts);

      // Check message was created
      const messages = await messageRepository.findByRecipient('agent', agent.id);
      expect(messages.length).toBe(1);
      expect(messages[0].recipientType).toBe('agent');
      expect(messages[0].recipientId).toBe(agent.id);
      expect(messages[0].senderType).toBe('user');
      expect(messages[0].type).toBe('info');
      expect(messages[0].requiresResponse).toBe(false);

      // Check message content
      expect(messages[0].content).toContain('Merge conflict detected');
      expect(messages[0].content).toContain('Message Task');
      expect(messages[0].content).toContain('conflict.ts');
      expect(messages[0].content).toContain('Resolve conflicts: Message Task');
    });

    it('should emit TaskQueuedEvent for resolution task', async () => {
      const originalTask = await taskRepository.create({
        phaseId: testPhaseId,
        initiativeId: testInitiativeId,
        name: 'Event Task',
        order: 1,
      });

      await agentRepository.create({
        name: 'agent-event',
        taskId: originalTask.id,
        worktreeId: 'wt-event',
      });

      await service.handleConflict(originalTask.id, ['event.ts']);

      // Check TaskQueuedEvent was emitted
      expect(eventBus.emittedEvents.length).toBe(1);
      expect(eventBus.emittedEvents[0].type).toBe('task:queued');

      const event = eventBus.emittedEvents[0] as any;
      expect(event.payload.priority).toBe('high');
      expect(event.payload.dependsOn).toEqual([]);

      // Check taskId matches the created resolution task
      const tasks = await taskRepository.findByPhaseId(testPhaseId);
      const resolutionTask = tasks.find(t => t.name.startsWith('Resolve conflicts:'));
      expect(event.payload.taskId).toBe(resolutionTask!.id);
    });

    it('should work without messageRepository', async () => {
      // Create service without messageRepository
      const serviceNoMsg = new DefaultConflictResolutionService(
        taskRepository,
        agentRepository,
        undefined, // No message repository
        eventBus
      );

      const originalTask = await taskRepository.create({
        phaseId: testPhaseId,
        initiativeId: testInitiativeId,
        name: 'No Message Task',
        order: 1,
      });

      await agentRepository.create({
        name: 'agent-no-msg',
        taskId: originalTask.id,
        worktreeId: 'wt-no-msg',
      });

      // Should not throw and should still create task
      await expect(serviceNoMsg.handleConflict(originalTask.id, ['test.ts']))
        .resolves.not.toThrow();

      // Check resolution task was still created
      const tasks = await taskRepository.findByPhaseId(testPhaseId);
      const resolutionTask = tasks.find(t => t.name.startsWith('Resolve conflicts:'));
      expect(resolutionTask).toBeDefined();
    });

    it('should work without eventBus', async () => {
      // Create service without eventBus
      const serviceNoEvents = new DefaultConflictResolutionService(
        taskRepository,
        agentRepository,
        messageRepository,
        undefined // No event bus
      );

      const originalTask = await taskRepository.create({
        phaseId: testPhaseId,
        initiativeId: testInitiativeId,
        name: 'No Events Task',
        order: 1,
      });

      await agentRepository.create({
        name: 'agent-no-events',
        taskId: originalTask.id,
        worktreeId: 'wt-no-events',
      });

      // Should not throw and should still create task
      await expect(serviceNoEvents.handleConflict(originalTask.id, ['test.ts']))
        .resolves.not.toThrow();

      // Check resolution task was still created
      const tasks = await taskRepository.findByPhaseId(testPhaseId);
      const resolutionTask = tasks.find(t => t.name.startsWith('Resolve conflicts:'));
      expect(resolutionTask).toBeDefined();
    });

    it('should throw error when task not found', async () => {
      await expect(service.handleConflict('non-existent-id', ['test.ts']))
        .rejects.toThrow('Original task not found: non-existent-id');
    });

    it('should throw error when no agent found for task', async () => {
      const task = await taskRepository.create({
        phaseId: testPhaseId,
        initiativeId: testInitiativeId,
        name: 'Orphan Task',
        order: 1,
      });

      await expect(service.handleConflict(task.id, ['test.ts']))
        .rejects.toThrow(`No agent found for task: ${task.id}`);
    });

    it('should handle multiple conflict files correctly', async () => {
      const originalTask = await taskRepository.create({
        phaseId: testPhaseId,
        initiativeId: testInitiativeId,
        name: 'Multi-Conflict Task',
        order: 1,
      });

      await agentRepository.create({
        name: 'agent-multi',
        taskId: originalTask.id,
        worktreeId: 'wt-multi',
      });

      const conflicts = [
        'src/components/Header.tsx',
        'src/utils/helpers.ts',
        'package.json',
        'README.md'
      ];

      await service.handleConflict(originalTask.id, conflicts);

      // Check all conflict files are in the description
      const tasks = await taskRepository.findByPhaseId(testPhaseId);
      const resolutionTask = tasks.find(t => t.name.startsWith('Resolve conflicts:'));

      expect(resolutionTask!.description).toContain('src/components/Header.tsx');
      expect(resolutionTask!.description).toContain('src/utils/helpers.ts');
      expect(resolutionTask!.description).toContain('package.json');
      expect(resolutionTask!.description).toContain('README.md');
    });

    it('should preserve parentTaskId from original task', async () => {
      // Create parent task first
      const parentTask = await taskRepository.create({
        phaseId: testPhaseId,
        initiativeId: testInitiativeId,
        name: 'Parent Task',
        order: 1,
      });

      // Create child task
      const childTask = await taskRepository.create({
        phaseId: testPhaseId,
        initiativeId: testInitiativeId,
        parentTaskId: parentTask.id,
        name: 'Child Task',
        order: 2,
      });

      await agentRepository.create({
        name: 'agent-child',
        taskId: childTask.id,
        worktreeId: 'wt-child',
      });

      await service.handleConflict(childTask.id, ['conflict.ts']);

      // Check resolution task has same parentTaskId
      const tasks = await taskRepository.findByPhaseId(testPhaseId);
      const resolutionTask = tasks.find(t => t.name.startsWith('Resolve conflicts:'));

      expect(resolutionTask!.parentTaskId).toBe(parentTask.id);
    });
  });
});