/**
 * Unit tests for orchestrator-scaffold.ts
 * ARCHITECTURE: Tests file I/O side effects + returned data structure.
 * Uses a real temp directory to verify actual file creation.
 */

import { existsSync, mkdirSync, readFileSync, rmSync } from 'fs';
import { tmpdir } from 'os';
import path from 'path';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { scaffoldCustomOrchestrator } from '../../../src/core/orchestrator-scaffold.js';
import { readStateFile } from '../../../src/core/orchestrator-state.js';

// Override getStateDir to use a temp dir so we don't litter ~/.autobeat in tests
const TEST_STATE_DIR = path.join(tmpdir(), `autobeat-scaffold-test-${process.pid}`);

vi.mock('../../../src/core/orchestrator-state.js', async (importOriginal) => {
  const original = await importOriginal<typeof import('../../../src/core/orchestrator-state.js')>();
  return {
    ...original,
    getStateDir: () => TEST_STATE_DIR,
  };
});

describe('scaffoldCustomOrchestrator', () => {
  beforeEach(() => {
    mkdirSync(TEST_STATE_DIR, { recursive: true });
  });

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

  it('returns ok result with all required fields', () => {
    const result = scaffoldCustomOrchestrator({
      goal: 'Build auth system',
    });

    expect(result.ok).toBe(true);
    if (!result.ok) return;

    expect(result.value.template).toBe('standard');
    expect(result.value.stateFilePath).toBeTruthy();
    expect(result.value.instructions.delegation).toBeTruthy();
    expect(result.value.instructions.stateManagement).toBeTruthy();
    expect(result.value.instructions.constraints).toBeTruthy();
    if (result.value.template === 'standard') {
      expect(result.value.exitConditionScript).toBeTruthy();
      expect(result.value.suggestedExitCondition).toBeTruthy();
    }
  });

  it('creates state file on disk with initial content', () => {
    const result = scaffoldCustomOrchestrator({
      goal: 'Test goal',
    });

    expect(result.ok).toBe(true);
    if (!result.ok) return;

    expect(existsSync(result.value.stateFilePath)).toBe(true);

    const stateResult = readStateFile(result.value.stateFilePath);
    expect(stateResult.ok).toBe(true);
    if (!stateResult.ok) return;

    expect(stateResult.value.goal).toBe('Test goal');
    expect(stateResult.value.status).toBe('planning');
    expect(stateResult.value.version).toBe(1);
    expect(stateResult.value.plan).toEqual([]);
    expect(stateResult.value.iterationCount).toBe(0);
  });

  it('creates exit condition script on disk', () => {
    const result = scaffoldCustomOrchestrator({
      goal: 'Test goal',
    });

    expect(result.ok).toBe(true);
    if (!result.ok) return;
    expect(result.value.template).toBe('standard');
    if (result.value.template !== 'standard') return;

    expect(existsSync(result.value.exitConditionScript)).toBe(true);

    const scriptContent = readFileSync(result.value.exitConditionScript, 'utf-8');
    expect(scriptContent).toContain('process.exit');
    expect(scriptContent).toContain(result.value.stateFilePath);
  });

  it('suggestedExitCondition is "node <quoted scriptPath>"', () => {
    const result = scaffoldCustomOrchestrator({
      goal: 'Test goal',
    });

    expect(result.ok).toBe(true);
    if (!result.ok) return;
    expect(result.value.template).toBe('standard');
    if (result.value.template !== 'standard') return;

    // Path is JSON-quoted so spaces in home directory paths (e.g. /Users/John Doe/) don't break shell execution
    expect(result.value.suggestedExitCondition).toBe(`node ${JSON.stringify(result.value.exitConditionScript)}`);
  });

  it('suggestedCommand uses --eval-mode agent --strategy retry for standard template', () => {
    const result = scaffoldCustomOrchestrator({
      goal: 'Test goal',
    });

    expect(result.ok).toBe(true);
    if (!result.ok) return;
    expect(result.value.template).toBe('standard');
    if (result.value.template !== 'standard') return;

    expect(result.value.suggestedCommand).toContain('--eval-mode agent');
    expect(result.value.suggestedCommand).toContain('--strategy retry');
    // Old shell eval flag should not be present
    expect(result.value.suggestedCommand).not.toContain('--until');
  });

  it('suggestedCommand still generates state file and exit script for scaffold mode', () => {
    const result = scaffoldCustomOrchestrator({
      goal: 'Test goal',
    });

    expect(result.ok).toBe(true);
    if (!result.ok) return;
    expect(result.value.template).toBe('standard');
    if (result.value.template !== 'standard') return;

    // Scaffold mode still creates state file + exit condition script for users who opted in
    expect(result.value.stateFilePath).toBeTruthy();
    expect(result.value.exitConditionScript).toBeTruthy();
  });

  it('defaults maxWorkers to 5 and maxDepth to 3 in constraints snippet', () => {
    const result = scaffoldCustomOrchestrator({
      goal: 'Test goal',
    });

    expect(result.ok).toBe(true);
    if (!result.ok) return;

    expect(result.value.instructions.constraints).toContain('Max concurrent workers: 5');
    expect(result.value.instructions.constraints).toContain('Max delegation depth: 3');
  });

  it('respects explicit maxWorkers and maxDepth', () => {
    const result = scaffoldCustomOrchestrator({
      goal: 'Test goal',
      maxWorkers: 10,
      maxDepth: 2,
    });

    expect(result.ok).toBe(true);
    if (!result.ok) return;

    expect(result.value.instructions.constraints).toContain('Max concurrent workers: 10');
    expect(result.value.instructions.constraints).toContain('Max delegation depth: 2');
  });

  it('threads agent flag into delegation snippet', () => {
    const result = scaffoldCustomOrchestrator({
      goal: 'Test goal',
      agent: 'claude',
    });

    expect(result.ok).toBe(true);
    if (!result.ok) return;

    expect(result.value.instructions.delegation).toContain('--agent claude');
  });

  it('threads model flag into delegation snippet', () => {
    const result = scaffoldCustomOrchestrator({
      goal: 'Test goal',
      model: 'claude-opus-4-5',
    });

    expect(result.ok).toBe(true);
    if (!result.ok) return;

    expect(result.value.instructions.delegation).toContain('--model claude-opus-4-5');
  });

  it('threads both agent and model flags into delegation snippet', () => {
    const result = scaffoldCustomOrchestrator({
      goal: 'Test goal',
      agent: 'codex',
      model: 'gpt-4o',
    });

    expect(result.ok).toBe(true);
    if (!result.ok) return;

    expect(result.value.instructions.delegation).toContain('--agent codex --model gpt-4o');
  });

  it('includes state file path in state management snippet', () => {
    const result = scaffoldCustomOrchestrator({
      goal: 'Test goal',
    });

    expect(result.ok).toBe(true);
    if (!result.ok) return;

    expect(result.value.instructions.stateManagement).toContain(result.value.stateFilePath);
  });

  it('generates unique state files for concurrent calls', () => {
    const result1 = scaffoldCustomOrchestrator({ goal: 'Goal 1' });
    const result2 = scaffoldCustomOrchestrator({ goal: 'Goal 2' });

    expect(result1.ok).toBe(true);
    expect(result2.ok).toBe(true);
    if (!result1.ok || !result2.ok) return;

    expect(result1.value.stateFilePath).not.toBe(result2.value.stateFilePath);
    // Both standard — exit condition scripts are also unique
    if (result1.value.template === 'standard' && result2.value.template === 'standard') {
      expect(result1.value.exitConditionScript).not.toBe(result2.value.exitConditionScript);
    }
  });

  it('returns a Result object (never throws)', () => {
    // The function contract: any internal error must be caught and returned as err(Error).
    // We verify this by calling with valid input — the Result type is always returned.
    // Testing the error branch via module mocking is avoided here because vitest's
    // isolate:false config causes doMock() to pollute the module cache across files.
    const result = scaffoldCustomOrchestrator({ goal: 'Test' });
    expect(typeof result).toBe('object');
    expect('ok' in result).toBe(true);
  });
});
