import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, rmSync, existsSync, readFileSync, writeFileSync, cpSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { executeTool } from './pieces.js';
import type { ToolContext } from './core.js';

/**
 * The pieces tool resolves BUILTIN_PIECES_DIR from process.cwd() at module
 * load time. Vitest workers don't support process.chdir, so we run the
 * suite against the project's actual `pieces/` directory and use a known
 * real built-in name (`chat`) for the "refuse to overwrite built-in" path.
 * CreatePiece writes go to a fresh customPiecesDir each test.
 */

const VALID_NEW_PIECE_YAML = `name: test-fresh-piece
description: example
max_movements: 10
initial_movement: do
movements:
  - name: do
    edit: false
    persona: tester
    instruction: do
    allowed_tools: []
    rules:
      - condition: done
        next: do
`;

let customDir: string;

function ctx(): ToolContext {
  return {
    workspacePath: '/tmp/dummy',
    editAllowed: false,
    customPiecesDir: customDir,
  };
}

beforeEach(() => {
  customDir = mkdtempSync(join(tmpdir(), 'pieces-custom-'));
});

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

describe('CreatePiece', () => {
  it('rejects YAML missing max_movements (regression for "Exceeded max movements (undefined)")', async () => {
    const yaml = `name: test-no-mm
description: missing the cap
initial_movement: do
movements:
  - name: do
    edit: false
    persona: t
    instruction: t
    allowed_tools: []
    rules:
      - condition: done
        next: do
`;
    const result = await executeTool('CreatePiece', { yaml_content: yaml }, ctx());
    expect(result?.isError).toBe(true);
    expect(result?.output).toMatch(/max_movements is required/);
    expect(existsSync(join(customDir, 'test-no-mm.yaml'))).toBe(false);
  });

  it('rejects max_movements: 0', async () => {
    const yaml = VALID_NEW_PIECE_YAML
      .replace('max_movements: 10', 'max_movements: 0')
      .replace('test-fresh-piece', 'test-zero-mm');
    const result = await executeTool('CreatePiece', { yaml_content: yaml }, ctx());
    expect(result?.isError).toBe(true);
    expect(result?.output).toMatch(/max_movements/);
  });

  it('accepts a valid piece and writes to customPiecesDir', async () => {
    const result = await executeTool(
      'CreatePiece',
      { yaml_content: VALID_NEW_PIECE_YAML },
      ctx(),
    );
    expect(result?.isError).toBe(false);
    expect(existsSync(join(customDir, 'test-fresh-piece.yaml'))).toBe(true);
  });
});

describe('UpdatePiece', () => {
  // Uses the real bundled `chat.yaml` to exercise the built-in guard. The
  // file's pre-update content is captured up-front so we can detect any
  // accidental write.
  const BUILTIN_NAME = 'chat';
  const builtinPath = join(process.cwd(), 'pieces', `${BUILTIN_NAME}.yaml`);
  const originalBuiltin = readFileSync(builtinPath, 'utf-8');

  afterEach(() => {
    // Defense in depth: if a future regression lets the built-in be
    // overwritten, this restores it so the rest of the suite isn't poisoned.
    writeFileSync(builtinPath, originalBuiltin, 'utf-8');
  });

  it('refuses to overwrite a built-in piece (regression: agent corrupted game-tweet-generator)', async () => {
    const malicious = VALID_NEW_PIECE_YAML.replace('test-fresh-piece', BUILTIN_NAME);
    const result = await executeTool(
      'UpdatePiece',
      { name: BUILTIN_NAME, yaml_content: malicious },
      ctx(),
    );
    expect(result?.isError).toBe(true);
    expect(result?.output).toMatch(/組み込み|built-in/);
    // Built-in file must remain untouched.
    expect(readFileSync(builtinPath, 'utf-8')).toBe(originalBuiltin);
  });

  it('allows update when a custom override already exists', async () => {
    const customPath = join(customDir, `${BUILTIN_NAME}.yaml`);
    cpSync(builtinPath, customPath);
    const updated = VALID_NEW_PIECE_YAML.replace('test-fresh-piece', BUILTIN_NAME);
    const result = await executeTool(
      'UpdatePiece',
      { name: BUILTIN_NAME, yaml_content: updated },
      ctx(),
    );
    expect(result?.isError).toBe(false);
    // Custom override took the write; built-in stays clean.
    expect(readFileSync(customPath, 'utf-8')).toContain('description: example');
    expect(readFileSync(builtinPath, 'utf-8')).toBe(originalBuiltin);
  });
});
