/**
 * Focused test for completion handler mutex functionality.
 * Tests the race condition fix without complex mocking.
 */

import { describe, it, beforeEach, expect } from 'vitest';
import { OutputHandler } from './output-handler.js';
import type { AgentRepository } from '../db/repositories/agent-repository.js';

describe('OutputHandler completion mutex', () => {
  let outputHandler: OutputHandler;
  let completionCallCount: number;
  let callOrder: string[];

  // Default agent for update return value
  const defaultAgent = {
    id: 'test-agent',
    name: 'test-agent',
    taskId: null,
    provider: 'claude',
    mode: 'execute' as const,
    status: 'idle' as const,
    worktreeId: 'test-worktree',
    outputFilePath: null,
    sessionId: null,
    result: null,
    pendingQuestions: null,
    initiativeId: null,
    accountId: null,
    userDismissedAt: null,
    pid: null,
    exitCode: null,
    createdAt: new Date(),
    updatedAt: new Date(),
  };

  // Simple mock that tracks completion attempts
  const mockRepository: AgentRepository = {
    async findById() {
      return null; // Return null to cause early exit after mutex check
    },
    async update(_id: string, data: any) { return { ...defaultAgent, ...data }; },
    async create() { throw new Error('Not implemented'); },
    async findAll() { throw new Error('Not implemented'); },
    async findByStatus() { throw new Error('Not implemented'); },
    async findByTaskId() { throw new Error('Not implemented'); },
    async findByName() { throw new Error('Not implemented'); },
    async findBySessionId() { throw new Error('Not implemented'); },
    async delete() { throw new Error('Not implemented'); }
  };

  beforeEach(() => {
    outputHandler = new OutputHandler(mockRepository);
    completionCallCount = 0;
    callOrder = [];
  });

  it('should prevent concurrent completion handling with mutex', async () => {
    const agentId = 'test-agent';

    // Mock the findById method to track calls and simulate processing time
    let firstCallCompleted = false;
    (mockRepository as any).findById = async (id: string) => {
      completionCallCount++;
      const callIndex = completionCallCount;
      callOrder.push(`call-${callIndex}-start`);

      if (callIndex === 1) {
        // First call - simulate some processing time
        await new Promise(resolve => setTimeout(resolve, 50));
        firstCallCompleted = true;
      }

      callOrder.push(`call-${callIndex}-end`);
      return null; // Return null to exit early
    };

    // Start two concurrent completion handlers
    const getAgentWorkdir = () => '/test/workdir';
    const completion1Promise = outputHandler.handleCompletion(agentId, undefined, getAgentWorkdir);
    const completion2Promise = outputHandler.handleCompletion(agentId, undefined, getAgentWorkdir);

    await Promise.all([completion1Promise, completion2Promise]);

    // Verify only one completion handler executed
    expect(completionCallCount, 'Should only execute one completion handler').toBe(1);
    expect(firstCallCompleted, 'First handler should have completed').toBe(true);
    expect(callOrder).toEqual(['call-1-start', 'call-1-end']);
  });

  it('should allow sequential completion handling after first completes', async () => {
    const agentId = 'test-agent';

    // Mock findById to track calls
    (mockRepository as any).findById = async (id: string) => {
      completionCallCount++;
      callOrder.push(`call-${completionCallCount}`);
      return null; // Return null to exit early
    };

    const getAgentWorkdir = () => '/test/workdir';

    // First completion
    await outputHandler.handleCompletion(agentId, undefined, getAgentWorkdir);

    // Second completion (after first is done)
    await outputHandler.handleCompletion(agentId, undefined, getAgentWorkdir);

    // Both should execute sequentially
    expect(completionCallCount, 'Should execute both handlers sequentially').toBe(2);
    expect(callOrder).toEqual(['call-1', 'call-2']);
  });

  it('should clean up mutex lock even when exception is thrown', async () => {
    const agentId = 'test-agent';

    let firstCallMadeThrowCall = false;
    let secondCallCompleted = false;

    // First call throws an error
    (mockRepository as any).findById = async (id: string) => {
      if (!firstCallMadeThrowCall) {
        firstCallMadeThrowCall = true;
        throw new Error('Database error');
      } else {
        secondCallCompleted = true;
        return null;
      }
    };

    const getAgentWorkdir = () => '/test/workdir';

    // First call should throw but clean up mutex
    await expect(outputHandler.handleCompletion(agentId, undefined, getAgentWorkdir))
      .rejects.toThrow('Database error');

    expect(firstCallMadeThrowCall, 'First call should have thrown').toBe(true);

    // Second call should succeed (proving mutex was cleaned up)
    await outputHandler.handleCompletion(agentId, undefined, getAgentWorkdir);
    expect(secondCallCompleted, 'Second call should have completed').toBe(true);
  });

  it('should use agent ID as mutex key', async () => {
    const agentId1 = 'agent-1';
    const agentId2 = 'agent-2';

    // Both agents can process concurrently since they have different IDs
    let agent1Started = false;
    let agent2Started = false;

    (mockRepository as any).findById = async (id: string) => {
      if (id === agentId1) {
        agent1Started = true;
        await new Promise(resolve => setTimeout(resolve, 30));
      } else if (id === agentId2) {
        agent2Started = true;
        await new Promise(resolve => setTimeout(resolve, 30));
      }
      return null;
    };

    const getAgentWorkdir = () => '/test/workdir';

    // Start both agents concurrently - they should NOT block each other
    const agent1Promise = outputHandler.handleCompletion(agentId1, undefined, getAgentWorkdir);
    const agent2Promise = outputHandler.handleCompletion(agentId2, undefined, getAgentWorkdir);

    await Promise.all([agent1Promise, agent2Promise]);

    expect(agent1Started, 'Agent 1 should have started').toBe(true);
    expect(agent2Started, 'Agent 2 should have started').toBe(true);
  });
});