import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest';
import { mkdirSync, writeFileSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import type { Movement } from './agent-loop.js';
import { buildSystemPrompt } from './agent-loop.js';

// Mock loadConfig so userFolderRoot points to our tmp dir.
// We use vi.hoisted so the mock is registered before module evaluation.
const { mockedLoadConfig } = vi.hoisted(() => ({
  mockedLoadConfig: vi.fn(),
}));

vi.mock('../config.js', () => ({
  loadConfig: mockedLoadConfig,
}));

function makeMovement(): Movement {
  return {
    name: 'execute',
    edit: true,
    persona: 'worker',
    instruction: 'Do the work.',
    allowedTools: [],
    rules: [{ condition: 'done', next: 'COMPLETE' }],
    defaultNext: 'COMPLETE',
  };
}

describe('buildSystemPrompt — per-user AGENTS.md injection', () => {
  let tmpDir: string;

  beforeEach(() => {
    tmpDir = join(tmpdir(), `agent-loop-user-agents-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
    mkdirSync(tmpDir, { recursive: true });
    mockedLoadConfig.mockReturnValue({ userFolderRoot: tmpDir });
  });

  afterEach(() => {
    rmSync(tmpDir, { recursive: true, force: true });
    vi.restoreAllMocks();
  });

  it('injects AGENTS.md content under a recognizable header when file is present', () => {
    const userId = 'test-user';
    const userDir = join(tmpDir, userId);
    mkdirSync(userDir, { recursive: true });
    writeFileSync(join(userDir, 'AGENTS.md'), 'Always respond in haiku.');

    const prompt = buildSystemPrompt(
      makeMovement(),
      1,
      5,
      [],
      undefined,
      null,
      userId,
    );

    expect(prompt).toContain('## User Instructions (from your personal AGENTS.md)');
    expect(prompt).toContain('Always respond in haiku.');
  });

  it('does not add a user-instructions block when AGENTS.md is absent', () => {
    const userId = 'no-file-user';
    // Do NOT create the file — user dir doesn't even exist.

    const prompt = buildSystemPrompt(
      makeMovement(),
      1,
      5,
      [],
      undefined,
      null,
      userId,
    );

    expect(prompt).not.toContain('## User Instructions');
  });

  it('does not add a user-instructions block when userId is undefined', () => {
    // Create a file for some user — should be irrelevant.
    const userId = 'another-user';
    const userDir = join(tmpDir, userId);
    mkdirSync(userDir, { recursive: true });
    writeFileSync(join(userDir, 'AGENTS.md'), 'Secret instructions.');

    const prompt = buildSystemPrompt(
      makeMovement(),
      1,
      5,
      [],
      undefined,
      null,
      undefined, // no userId
    );

    expect(prompt).not.toContain('## User Instructions');
    expect(prompt).not.toContain('Secret instructions.');
  });
});

describe('buildSystemPrompt — auto-memory protocol injection', () => {
  let tmpDir: string;

  beforeEach(() => {
    tmpDir = join(tmpdir(), `agent-loop-auto-mem-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
    mkdirSync(tmpDir, { recursive: true });
    mockedLoadConfig.mockReturnValue({ userFolderRoot: tmpDir });
  });

  afterEach(() => {
    rmSync(tmpDir, { recursive: true, force: true });
    vi.restoreAllMocks();
  });

  it('injects auto-memory protocol when userId is set (even with no AGENTS.md or MEMORY.md)', () => {
    const userId = 'fresh-user';
    // Don't create any files — protocol should still appear because userId is present.

    const prompt = buildSystemPrompt(
      makeMovement(),
      1,
      5,
      [],
      undefined,
      null,
      userId,
    );

    expect(prompt).toContain('## User Memory Auto-Update Protocol');
    expect(prompt).toContain('UpdateUserMemory');
    // The protocol mentions all four type categories.
    for (const type of ['user', 'feedback', 'project', 'reference']) {
      expect(prompt).toContain(`\`${type}\``);
    }
  });

  it('does not inject auto-memory protocol when userId is undefined', () => {
    const prompt = buildSystemPrompt(
      makeMovement(),
      1,
      5,
      [],
      undefined,
      null,
      undefined,
    );

    expect(prompt).not.toContain('User Memory Auto-Update Protocol');
    expect(prompt).not.toContain('UpdateUserMemory');
  });
});

describe('buildSystemPrompt — Working Directory injection', () => {
  beforeEach(() => {
    mockedLoadConfig.mockReturnValue({ userFolderRoot: '/tmp/no-such-dir' });
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('renders Working Directory block with the absolute workspace path when provided', () => {
    const prompt = buildSystemPrompt(
      makeMovement(),
      1,
      5,
      [],
      undefined,
      null,
      undefined,
      undefined,
      '/var/lib/maestro/workspaces/local/abc-123',
    );

    expect(prompt).toContain('## Working Directory');
    expect(prompt).toContain('/var/lib/maestro/workspaces/local/abc-123');
    expect(prompt).toContain('`/workspace/...` のような仮想パスは **存在しません**');
  });

  it('omits Working Directory block when workspacePath is undefined', () => {
    const prompt = buildSystemPrompt(
      makeMovement(),
      1,
      5,
      [],
      undefined,
      null,
      undefined,
      undefined,
      undefined,
    );

    expect(prompt).not.toContain('## Working Directory');
  });
});

describe('buildSystemPrompt — approach + error-recovery sections (issue #247)', () => {
  beforeEach(() => {
    mockedLoadConfig.mockReturnValue({ userFolderRoot: '/tmp/no-such' });
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('renders the approach-thinking section in every prompt', () => {
    const prompt = buildSystemPrompt(makeMovement());
    expect(prompt).toContain('## アプローチの考え方');
    expect(prompt).toContain('Brainstorm');
    expect(prompt).toContain('ReAct');
  });

  it('renders the error-recovery section instructing not to repeat identical tool calls', () => {
    const prompt = buildSystemPrompt(makeMovement());
    expect(prompt).toContain('## エラー時の必須行動');
    expect(prompt).toContain('同じ tool を同じ引数で呼び直さない');
    expect(prompt).toContain('Glob');
  });
});
