/**
 * PieceEngine integration tests: blocked handling scenarios.
 *
 * Covers:
 * - Blocked without onUserInput callback (abort)
 * - Blocked with onUserInput returning null (abort)
 * - Blocked with onUserInput providing input (continue)
 */

import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { existsSync, rmSync } from 'node:fs';

// --- Mock setup (must be before imports that use these modules) ---

vi.mock('../agents/runner.js', () => ({
  runAgent: vi.fn(),
}));

vi.mock('../core/piece/evaluation/index.js', () => ({
  detectMatchedRule: vi.fn(),
}));

vi.mock('../core/piece/phase-runner.js', () => ({
  needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
  runReportPhase: vi.fn().mockResolvedValue(undefined),
  runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
}));

vi.mock('../shared/utils/index.js', async (importOriginal) => ({
  ...(await importOriginal<Record<string, unknown>>()),
  generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
}));

// --- Imports (after mocks) ---

import { PieceEngine } from '../core/piece/index.js';
import {
  makeResponse,
  buildDefaultPieceConfig,
  mockRunAgentSequence,
  mockDetectMatchedRuleSequence,
  createTestTmpDir,
  applyDefaultMocks,
} from './engine-test-helpers.js';

describe('PieceEngine Integration: Blocked Handling', () => {
  let tmpDir: string;

  beforeEach(() => {
    vi.resetAllMocks();
    applyDefaultMocks();
    tmpDir = createTestTmpDir();
  });

  afterEach(() => {
    if (existsSync(tmpDir)) {
      rmSync(tmpDir, { recursive: true, force: true });
    }
  });

  it('should abort when blocked and no onUserInput callback', async () => {
    const config = buildDefaultPieceConfig();
    const engine = new PieceEngine(config, tmpDir, 'test task', { projectCwd: tmpDir });

    mockRunAgentSequence([
      makeResponse({ persona: 'plan', status: 'blocked', content: 'Need clarification' }),
    ]);

    mockDetectMatchedRuleSequence([
      { index: 0, method: 'phase1_tag' },
    ]);

    const blockedFn = vi.fn();
    const abortFn = vi.fn();
    engine.on('movement:blocked', blockedFn);
    engine.on('piece:abort', abortFn);

    const state = await engine.run();

    expect(state.status).toBe('aborted');
    expect(blockedFn).toHaveBeenCalledOnce();
    expect(abortFn).toHaveBeenCalledOnce();
  });

  it('should abort when blocked and onUserInput returns null', async () => {
    const config = buildDefaultPieceConfig();
    const onUserInput = vi.fn().mockResolvedValue(null);
    const engine = new PieceEngine(config, tmpDir, 'test task', { projectCwd: tmpDir, onUserInput });

    mockRunAgentSequence([
      makeResponse({ persona: 'plan', status: 'blocked', content: 'Need info' }),
    ]);

    mockDetectMatchedRuleSequence([
      { index: 0, method: 'phase1_tag' },
    ]);

    const state = await engine.run();

    expect(state.status).toBe('aborted');
    expect(onUserInput).toHaveBeenCalledOnce();
  });

  it('should continue when blocked and onUserInput provides input', async () => {
    const config = buildDefaultPieceConfig();
    const onUserInput = vi.fn().mockResolvedValueOnce('User provided clarification');
    const engine = new PieceEngine(config, tmpDir, 'test task', { projectCwd: tmpDir, onUserInput });

    mockRunAgentSequence([
      // First: plan is blocked
      makeResponse({ persona: 'plan', status: 'blocked', content: 'Need info' }),
      // Second: plan succeeds after user input
      makeResponse({ persona: 'plan', content: 'Plan done with user input' }),
      makeResponse({ persona: 'implement', content: 'Impl done' }),
      makeResponse({ persona: 'ai_review', content: 'OK' }),
      makeResponse({ persona: 'arch-review', content: 'OK' }),
      makeResponse({ persona: 'security-review', content: 'OK' }),
      makeResponse({ persona: 'supervise', content: 'All passed' }),
    ]);

    mockDetectMatchedRuleSequence([
      // First plan call: blocked, rule matched but blocked handling takes over
      { index: 0, method: 'phase1_tag' },
      // Second plan call: success
      { index: 0, method: 'phase1_tag' },  // plan → implement
      { index: 0, method: 'phase1_tag' },  // implement → ai_review
      { index: 0, method: 'phase1_tag' },  // ai_review → reviewers
      { index: 0, method: 'phase1_tag' },  // arch-review → approved
      { index: 0, method: 'phase1_tag' },  // security-review → approved
      { index: 0, method: 'aggregate' },   // reviewers → supervise
      { index: 0, method: 'phase1_tag' },  // supervise → COMPLETE
    ]);

    const userInputFn = vi.fn();
    engine.on('movement:user_input', userInputFn);

    const state = await engine.run();

    expect(state.status).toBe('completed');
    expect(onUserInput).toHaveBeenCalledOnce();
    expect(userInputFn).toHaveBeenCalledOnce();
    expect(state.userInputs).toContain('User provided clarification');
  });

  it('should abort immediately when movement returns error status', async () => {
    const config = buildDefaultPieceConfig();
    const onUserInput = vi.fn().mockResolvedValueOnce('should not be called');
    const engine = new PieceEngine(config, tmpDir, 'test task', { projectCwd: tmpDir, onUserInput });

    mockRunAgentSequence([
      makeResponse({ persona: 'plan', status: 'error', content: 'Transport error', error: 'Transport error' }),
    ]);

    mockDetectMatchedRuleSequence([
      { index: 0, method: 'phase1_tag' },
    ]);

    const abortFn = vi.fn();
    engine.on('piece:abort', abortFn);

    const state = await engine.run();

    expect(state.status).toBe('aborted');
    expect(onUserInput).not.toHaveBeenCalled();
    expect(abortFn).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('Transport error'));
  });

});
