import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import type { ToolContext } from './core.js';
import { TOOL_DEFS, executeTool } from './orchestration.js';

let tmp: string;

beforeEach(() => {
  tmp = mkdtempSync(join(tmpdir(), 'orchestration-test-'));
});

afterEach(() => {
  rmSync(tmp, { recursive: true, force: true });
});

const makeCtx = (overrides: Partial<ToolContext> = {}): ToolContext =>
  ({
    workspacePath: tmp,
    spawnSubTask: vi.fn().mockResolvedValue({
      subtaskIndex: 1,
      jobId: 42,
      workspacePath: join(tmp, 'subtasks', '1'),
    }),
    ...overrides,
  }) as unknown as ToolContext;

describe('TOOL_DEFS', () => {
  it('registers SpawnSubTask with title and instruction required', () => {
    const def = TOOL_DEFS['SpawnSubTask'];
    expect(def?.function.name).toBe('SpawnSubTask');
    expect(def?.function.parameters?.['required']).toEqual(['title', 'instruction']);
  });
});

describe('executeTool', () => {
  it('returns null for unknown tool names', async () => {
    expect(await executeTool('NotATool', {}, makeCtx())).toBeNull();
  });

  it('errors when spawnSubTask is unavailable in this context', async () => {
    const res = await executeTool(
      'SpawnSubTask',
      { title: 't', instruction: 'i' },
      makeCtx({ spawnSubTask: undefined }),
    );
    expect(res?.isError).toBe(true);
    expect(res?.output).toContain('使用できません');
  });

  it.each([
    [{ instruction: 'i' }],
    [{ title: 't' }],
    [{ title: '   ', instruction: 'i' }],
    [{ title: 't', instruction: '' }],
    [{ title: 42, instruction: 'i' }],
  ])('rejects missing/blank title or instruction: %j', async input => {
    const res = await executeTool('SpawnSubTask', input as Record<string, unknown>, makeCtx());
    expect(res?.isError).toBe(true);
    expect(res?.output).toContain('必須');
  });

  it('spawns with the default piece and reports job details', async () => {
    const ctx = makeCtx();
    const res = await executeTool('SpawnSubTask', { title: ' t ', instruction: ' do it ' }, ctx);
    expect(res?.isError).toBe(false);
    expect(res?.output).toContain('サブタスク #1');
    expect(res?.output).toContain('ジョブ ID: 42');
    expect(ctx.spawnSubTask).toHaveBeenCalledWith({ title: 't', instruction: 'do it', piece: 'general' });
  });

  it('rejects a piece that exists neither builtin nor custom', async () => {
    const ctx = makeCtx();
    const res = await executeTool(
      'SpawnSubTask',
      { title: 't', instruction: 'i', piece: 'no-such-piece' },
      ctx,
    );
    expect(res?.isError).toBe(true);
    expect(res?.output).toContain('no-such-piece');
    expect(ctx.spawnSubTask).not.toHaveBeenCalled();
  });

  it('accepts a piece found in a custom pieces dir', async () => {
    const customDir = join(tmp, 'custom-pieces');
    mkdirSync(customDir, { recursive: true });
    writeFileSync(join(customDir, 'my-piece.yaml'), 'name: my-piece\n');
    const ctx = makeCtx({ customPiecesDir: customDir } as Partial<ToolContext>);
    const res = await executeTool(
      'SpawnSubTask',
      { title: 't', instruction: 'i', piece: 'my-piece' },
      ctx,
    );
    expect(res?.isError).toBe(false);
    expect(ctx.spawnSubTask).toHaveBeenCalledWith({ title: 't', instruction: 'i', piece: 'my-piece' });
  });

  it('accepts custom pieces dirs given as an array', async () => {
    const customDir = join(tmp, 'pieces-a');
    mkdirSync(customDir, { recursive: true });
    writeFileSync(join(customDir, 'arr-piece.yaml'), 'name: arr-piece\n');
    const ctx = makeCtx({ customPiecesDir: [join(tmp, 'empty'), customDir] } as Partial<ToolContext>);
    const res = await executeTool(
      'SpawnSubTask',
      { title: 't', instruction: 'i', piece: 'arr-piece' },
      ctx,
    );
    expect(res?.isError).toBe(false);
  });

  it('surfaces spawn failures as tool errors', async () => {
    const ctx = makeCtx({ spawnSubTask: vi.fn().mockRejectedValue(new Error('queue full')) });
    const res = await executeTool('SpawnSubTask', { title: 't', instruction: 'i' }, ctx);
    expect(res?.isError).toBe(true);
    expect(res?.output).toContain('queue full');
  });
});
