import { describe, it, expect } from 'vitest';
import { buildSystemPrompt, buildUserPrompt } from './reflection-prompt.js';
import type { ReflectionInput } from './types.js';

function makeInput(overrides: Partial<ReflectionInput> = {}): ReflectionInput {
  return {
    originalJobId: 'job-1',
    userId: 'u-1',
    pieceName: 'chat',
    pieceSource: 'builtin',
    outcome: 'succeeded',
    taskTitle: 'Summarize the report',
    taskBody: 'Please summarize quarterly-report.pdf',
    activityLogSummary: 'ReadPdf -> Write summary.md (2 iterations)',
    postCompletionComments: [],
    feedback: { rating: null, comment: null, tags: [] },
    resultText: 'Wrote output/summary.md',
    observedRevisions: {},
    memoryIndex: '- [fact one](fact_one.md)',
    memoryEntries: [],
    pieceYaml: 'movements:\n  - name: execute\n',
    ...overrides,
  };
}

describe('buildSystemPrompt', () => {
  it('forces submit_reflection tool-call-only output', () => {
    const sys = buildSystemPrompt();
    expect(sys).toContain('submit_reflection');
  });

  it('states the memory rules: non-trivial lessons, type tagging, 3-change cap, abstain path', () => {
    const sys = buildSystemPrompt();
    expect(sys).toContain('非自明');
    expect(sys).toContain('user | feedback | project | reference');
    expect(sys).toContain('最大 3 件');
    expect(sys).toContain('abstain_reason');
    expect(sys).toContain('should_edit=false');
  });

  it('requires Why/How-to-apply lines for feedback/project entries', () => {
    const sys = buildSystemPrompt();
    expect(sys).toContain('**Why:**');
    expect(sys).toContain('**How to apply:**');
  });

  it('hardens piece edits: full-YAML replacement and no engine sentinels in rules[].next', () => {
    const sys = buildSystemPrompt();
    expect(sys).toContain('完全置換');
    expect(sys).toContain('COMPLETE / ABORT / ASK');
    expect(sys).toContain('rules[].next');
  });

  it('is deterministic (no timestamps or randomness)', () => {
    expect(buildSystemPrompt()).toBe(buildSystemPrompt());
  });
});

describe('buildUserPrompt', () => {
  it('includes task title, body, activity log summary, result and outcome', () => {
    const prompt = buildUserPrompt(makeInput());
    // Task-derived text (title/body/result) is wrapped in untrusted-output
    // fences as a prompt-injection guard, so it appears on the line after the label.
    expect(prompt).toContain('title:\n<untrusted_task_output>\nSummarize the report');
    expect(prompt).toContain('body:\n<untrusted_task_output>\nPlease summarize quarterly-report.pdf');
    expect(prompt).toContain('ReadPdf -> Write summary.md (2 iterations)');
    expect(prompt).toContain('status: succeeded');
    expect(prompt).toContain('result:\n<untrusted_task_output>\nWrote output/summary.md');
  });

  it('contains all section headers', () => {
    const prompt = buildUserPrompt(makeInput());
    for (const header of [
      '## 元タスク',
      '## 活動ログ (圧縮済み)',
      '## ジョブ後のユーザーコメント',
      '## 明示フィードバック',
      '## 結果',
      '## 現在の memory スナップショット',
      '## 現在の piece YAML',
    ]) {
      expect(prompt).toContain(header);
    }
  });

  it('renders post-completion comments with timestamp and author', () => {
    const prompt = buildUserPrompt(makeInput({
      postCompletionComments: [
        { author: 'alice', body: 'wrong file was summarized', createdAt: '2026-06-10T00:00:00Z' },
        { author: 'bob', body: 'please retry', createdAt: '2026-06-10T01:00:00Z' },
      ],
    }));
    expect(prompt).toContain('- [2026-06-10T00:00:00Z] alice: wrong file was summarized');
    expect(prompt).toContain('- [2026-06-10T01:00:00Z] bob: please retry');
    expect(prompt).not.toContain('(なし)');
  });

  it('shows (なし) when there are no post-completion comments', () => {
    const prompt = buildUserPrompt(makeInput({ postCompletionComments: [] }));
    expect(prompt).toContain('(なし)');
  });

  it('shows "rating: none" when no explicit feedback exists', () => {
    const prompt = buildUserPrompt(makeInput());
    expect(prompt).toContain('rating: none');
    expect(prompt).not.toContain('rating: good');
    expect(prompt).not.toContain('rating: bad');
  });

  it('renders rating, comment and tags when feedback exists', () => {
    const prompt = buildUserPrompt(makeInput({
      feedback: { rating: 'good', comment: 'nice work', tags: ['speed', 'quality'] },
    }));
    expect(prompt).toContain('rating: good');
    expect(prompt).toContain('comment: nice work');
    expect(prompt).toContain('tags: speed, quality');
  });

  it('omits comment/tags lines when feedback has neither', () => {
    const prompt = buildUserPrompt(makeInput({
      feedback: { rating: 'good', comment: null, tags: [] },
    }));
    expect(prompt).toContain('rating: good');
    expect(prompt).not.toContain('comment:');
    expect(prompt).not.toContain('tags:');
  });

  it('appends the bad-rating investigation directive only for rating=bad', () => {
    const bad = buildUserPrompt(makeInput({
      feedback: { rating: 'bad', comment: null, tags: [] },
    }));
    expect(bad).toContain('**低評価**');

    const good = buildUserPrompt(makeInput({
      feedback: { rating: 'good', comment: null, tags: [] },
    }));
    expect(good).not.toContain('**低評価**');

    const none = buildUserPrompt(makeInput());
    expect(none).not.toContain('**低評価**');
  });

  it('embeds the memory index, or (空) when the user has no memory', () => {
    const withMemory = buildUserPrompt(makeInput());
    expect(withMemory).toContain('- [fact one](fact_one.md)');
    expect(withMemory).not.toContain('(空)');

    const empty = buildUserPrompt(makeInput({ memoryIndex: '' }));
    expect(empty).toContain('(空)');
  });

  it('wraps the current piece YAML in a yaml code fence', () => {
    const prompt = buildUserPrompt(makeInput());
    expect(prompt).toContain('```yaml\nmovements:\n  - name: execute\n\n```');
  });

  it('does not truncate the activity log summary (truncation happens upstream in load-inputs)', () => {
    const long = 'x'.repeat(20000);
    const prompt = buildUserPrompt(makeInput({ activityLogSummary: long }));
    expect(prompt).toContain(long);
  });
});
