/**
 * Behavioral tests for P2P config mode — tests production functions directly.
 * Covers: rounds clamping via startP2pRun, extraPrompt in buildHopPrompt.
 */
import { describe, it, expect, vi } from 'vitest';
import { buildHopPrompt, buildP2pLanguageInstruction, buildPostSummaryExecutionPrompt, type P2pRun, type HopOpts } from '../../src/daemon/p2p-orchestrator.js';
import { getP2pMode } from '../../shared/p2p-modes.js';

// ── buildHopPrompt tests ──────────────────────────────────────────────────────

function makeRun(overrides: Partial<P2pRun> = {}): P2pRun {
  return {
    id: 'test-run',
    discussionId: 'dsc_test',
    mainSession: 'deck_proj',
    initiatorSession: 'deck_proj_brain',
    currentTargetSession: null,
    finalReturnSession: 'deck_proj_brain',
    remainingTargets: [],
    totalTargets: 2,
    mode: 'audit',
    status: 'running',
    runPhase: 'running',
    summaryPhase: null,
    activePhase: 'hop',
    contextFilePath: '/tmp/test-discussion.md',
    userText: 'review this code',
    locale: undefined,
    timeoutMs: 300000,
    resultSummary: null,
    completedHops: [],
    skippedHops: [],
    error: null,
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString(),
    completedAt: null,
    rounds: 1,
    currentRound: 1,
    allTargets: [],
    extraPrompt: '',
    hopStartedAt: Date.now(),
    hopStates: [],
    activeTargetSessions: [],
    _cancelled: false,
    ...overrides,
  };
}

const defaultOpts: HopOpts = {
  session: 'deck_proj_w1',
  sectionHeader: '3e031o0d — Initial Analysis',
  instruction: 'Read the discussion file and provide your initial analysis.',
  isInitial: false,
};

describe('buildHopPrompt — production function', () => {
  it('includes mode prompt when mode is provided', () => {
    const run = makeRun();
    const mode = getP2pMode('audit');
    const prompt = buildHopPrompt(run, mode, defaultOpts);
    expect(prompt).toContain('staff-level engineer');
    expect(prompt).toContain('Prioritize correctness over speed or politeness');
    expect(prompt).toContain('code auditor');
    expect(prompt).toContain('P2P Discussion Task');
  });

  it('includes file path in prompt', () => {
    const run = makeRun({ contextFilePath: '/home/user/.imc/discussions/abc123.md' });
    const prompt = buildHopPrompt(run, getP2pMode('audit'), defaultOpts);
    expect(prompt).toContain('/home/user/.imc/discussions/abc123.md');
  });

  it('does not reference a separate context file in the instruction text', () => {
    const prompt = buildHopPrompt(makeRun(), getP2pMode('audit'), defaultOpts);
    expect(prompt).toContain('Read the discussion file and provide your initial analysis.');
    expect(prompt).not.toContain('Read the context file below');
    expect(prompt).not.toContain('Read the full context file');
  });

  it('treats the current project codebase as referenced audit context when no explicit code context is attached', () => {
    const prompt = buildHopPrompt(makeRun(), getP2pMode('audit'), defaultOpts);
    expect(prompt).toContain('For this task, if the discussion file does not explicitly provide the relevant code, diff, or file paths, treat the current project codebase in your working directory as the referenced context for the audit.');
    expect(prompt).toContain('You MUST inspect the relevant source files in the current project codebase directly and base your analysis on that code.');
    expect(prompt).toContain('Do NOT respond that code context is missing if the working-directory project codebase is available.');
  });

  it('includes section header from opts', () => {
    const prompt = buildHopPrompt(makeRun(), getP2pMode('review'), defaultOpts);
    expect(prompt).toContain('3e031o0d — Initial Analysis');
  });

  it('includes extraPrompt when set', () => {
    const run = makeRun({ extraPrompt: '使用中文回复' });
    const prompt = buildHopPrompt(run, getP2pMode('audit'), defaultOpts);
    expect(prompt).toContain('Additional instructions: 使用中文回复');
  });

  it('does NOT include extra instructions line when extraPrompt is empty', () => {
    const run = makeRun({ extraPrompt: '' });
    const prompt = buildHopPrompt(run, getP2pMode('audit'), defaultOpts);
    expect(prompt).not.toContain('Additional instructions');
  });

  it('includes round prefix when provided', () => {
    const run = makeRun();
    const prompt = buildHopPrompt(run, getP2pMode('brainstorm'), defaultOpts, '[Round 2/3 — Deepening]\n');
    expect(prompt).toContain('[Round 2/3 — Deepening]');
    expect(prompt).toContain('creative collaborator');
  });

  it('extraPrompt and roundPrefix coexist without conflict', () => {
    const run = makeRun({ extraPrompt: 'Focus on security' });
    const roundPrefix = '[Round 2/3 — Deepening]\nReview previous findings.\n\n';
    const prompt = buildHopPrompt(run, getP2pMode('audit'), defaultOpts, roundPrefix);
    expect(prompt).toContain('[Round 2/3 — Deepening]');
    expect(prompt).toContain('Additional instructions: Focus on security');
    expect(prompt).toContain('code auditor');
    // Round prefix comes before mode prompt, extra prompt comes after rules
    const roundIdx = prompt.indexOf('[Round 2/3');
    const extraIdx = prompt.indexOf('Additional instructions');
    expect(roundIdx).toBeLessThan(extraIdx);
  });

  it('includes plan final-summary instructions for model-side target inference', () => {
    const mode = getP2pMode('plan');
    const run = makeRun({
      mode: 'plan',
      userText: '根据讨论结果把完整方案写到 @docs/implementation-plan.md',
    });
    const prompt = buildHopPrompt(run, mode, {
      session: 'deck_proj_brain',
      sectionHeader: 'brain — Final Summary',
      instruction: `${mode!.summaryPrompt}\nUse the discussion evidence as source material.`,
      isInitial: false,
    });

    expect(prompt).toContain('Discussion file: /tmp/test-discussion.md');
    expect(prompt).toContain('Final summary instructions:');
    expect(prompt).toContain('Acceptance and Validation');
    expect(prompt).toContain('Infer whether the user context specifies a concrete destination file for the final plan');
    expect(prompt).toContain('If a concrete destination file is clear from the user context, write the complete plan there.');
    expect(prompt).toContain('If you wrote the plan to another file, still append a short note under "## brain — Final Summary" in the discussion file');
  });

  it('does not repeat the original user request inside final-summary instructions', () => {
    const mode = getP2pMode('plan');
    const run = makeRun({
      mode: 'plan',
      userText: 'implement the requested feature, not just summarize the discussion',
    });
    const prompt = buildHopPrompt(run, mode, {
      session: 'deck_proj_brain',
      sectionHeader: 'brain — Final Summary',
      instruction: `${mode!.summaryPrompt}\nAfter synthesizing, directly fulfill the request.`,
      isInitial: false,
    });

    expect(prompt).not.toContain('The original user request is: "implement the requested feature, not just summarize the discussion"');
    expect(prompt).toContain('After synthesizing, directly fulfill the request.');
  });

  it('does not inject the localized original-request reminder into final-summary instructions', () => {
    const mode = getP2pMode('plan');
    const run = makeRun({
      mode: 'plan',
      locale: 'zh-CN',
      userText: '根据讨论结果真正完成这个需求',
    });
    const prompt = buildHopPrompt(run, mode, {
      session: 'deck_proj_brain',
      sectionHeader: 'brain — Final Summary',
      instruction: `${mode!.summaryPrompt}\n在写总结后执行最终任务。`,
      isInitial: false,
    });

    expect(prompt).not.toContain('用户的原始请求是："根据讨论结果真正完成这个需求"');
    expect(prompt).toContain('在写总结后执行最终任务。');
  });

  it('builds a post-summary execution prompt that directly fulfills the original request', () => {
    const prompt = buildPostSummaryExecutionPrompt(makeRun({
      contextFilePath: '/tmp/test-discussion.md',
      userText: 'implement the requested feature',
    }));

    expect(prompt).toContain('/tmp/test-discussion.md');
    expect(prompt).toContain('implement the requested feature');
    expect(prompt).toContain('Do not stop at another discussion summary');
  });

  it('localizes the post-summary execution prompt when a locale is set', () => {
    const prompt = buildPostSummaryExecutionPrompt(makeRun({
      contextFilePath: '/tmp/test-discussion.md',
      locale: 'zh-CN',
      userText: '根据讨论结果真正完成这个需求',
    }));

    expect(prompt).toContain('/tmp/test-discussion.md');
    expect(prompt).toContain('根据讨论结果真正完成这个需求');
    expect(prompt).toContain('不要再次停留在讨论总结');
    expect(prompt.trimEnd().endsWith('You shall use zh-CN to reply and use zh-CN to discuss.')).toBe(true);
  });

  it('adds marker-file execution proof instructions when a marker spec is supplied', () => {
    const prompt = buildPostSummaryExecutionPrompt(makeRun({
      contextFilePath: '/tmp/test-discussion.md',
      locale: 'en',
      userText: 'implement the requested feature',
    }), {
      runId: 'run_marker',
      cycleIndex: 1,
      cycleTotal: 2,
      nonce: 'nonce_marker',
      markerPath: '/tmp/run_marker.cycle1.execution-marker.json',
    }, {
      attempt: 2,
      deadlineAt: Date.parse('2026-05-12T00:00:00.000Z'),
    });

    expect(prompt).toContain('Execution proof required');
    expect(prompt).toContain('/tmp/run_marker.cycle1.execution-marker.json');
    expect(prompt).toContain('"runId": "run_marker"');
    expect(prompt).toContain('"cycleIndex": 1');
    expect(prompt).toContain('"cycleTotal": 2');
    expect(prompt).toContain('"nonce": "nonce_marker"');
    expect(prompt).toContain('idling without the marker does not count as success');
    expect(prompt).toContain('retry attempt 2');
    expect(prompt.trimEnd().endsWith('You shall use en to reply and use en to discuss.')).toBe(true);
  });
});

// ── Rounds clamping (via P2P_MAX_ROUNDS constant in orchestrator) ─────────────

describe('P2P_MAX_ROUNDS clamping — production constant', () => {
  // We can't easily call startP2pRun without full mocking of file I/O and session state,
  // but we CAN verify the constant is applied by checking the run object shape.
  // The orchestrator does: Math.min(P2P_MAX_ROUNDS, Math.max(1, rounds ?? 1))
  // where P2P_MAX_ROUNDS = 6. We verify this indirectly via the run interface.

  it('P2pRun.rounds field exists and is typed as number', () => {
    const run = makeRun({ rounds: 3 });
    expect(typeof run.rounds).toBe('number');
    expect(run.rounds).toBe(3);
  });

  it('P2P_MAX_ROUNDS = 6 is enforced (verified via grep of production code)', async () => {
    // Direct verification: read the production file and check the constant
    const fs = await import('node:fs/promises');
    const code = await fs.readFile('src/daemon/p2p-orchestrator.ts', 'utf8');
    expect(code).toContain('P2P_MAX_ROUNDS = 6');
    expect(code).toContain('Math.min(P2P_MAX_ROUNDS');
  });
});

// ── R3 v2 PR-ν — End-of-prompt i18n discussion-language reminder ─────────

describe('buildP2pLanguageInstruction — final locale-code reminder', () => {
  it('returns the standard final reminder for en', () => {
    expect(buildP2pLanguageInstruction('en')).toBe('You shall use en to reply and use en to discuss.');
  });

  it('returns the standard final reminder for zh-CN', () => {
    expect(buildP2pLanguageInstruction('zh-CN')).toBe('You shall use zh-CN to reply and use zh-CN to discuss.');
  });

  it('returns the standard final reminder for zh-TW', () => {
    expect(buildP2pLanguageInstruction('zh-TW')).toBe('You shall use zh-TW to reply and use zh-TW to discuss.');
  });

  it('returns the standard final reminder for ja', () => {
    expect(buildP2pLanguageInstruction('ja')).toBe('You shall use ja to reply and use ja to discuss.');
  });

  it('returns the standard final reminder for ko', () => {
    expect(buildP2pLanguageInstruction('ko')).toBe('You shall use ko to reply and use ko to discuss.');
  });

  it('returns the standard final reminder for es', () => {
    expect(buildP2pLanguageInstruction('es')).toBe('You shall use es to reply and use es to discuss.');
  });

  it('returns the standard final reminder for ru', () => {
    expect(buildP2pLanguageInstruction('ru')).toBe('You shall use ru to reply and use ru to discuss.');
  });

  it('returns empty string for missing locale (caller skips line)', () => {
    expect(buildP2pLanguageInstruction(undefined)).toBe('');
  });

  it('returns empty string for unknown locale (graceful fallback)', () => {
    expect(buildP2pLanguageInstruction('klingon')).toBe('');
  });
});

describe('buildHopPrompt — language reminder injection', () => {
  it('appends the standard locale reminder at the very end of the prompt', () => {
    const run = makeRun({ locale: 'zh-CN' });
    const mode = getP2pMode('audit');
    const prompt = buildHopPrompt(run, mode, defaultOpts);
    const line = 'You shall use zh-CN to reply and use zh-CN to discuss.';
    expect(prompt).toContain(line);
    const langIdx = prompt.indexOf(line);
    const modeIdx = prompt.indexOf(mode!.prompt);
    expect(langIdx).toBeGreaterThan(-1);
    expect(modeIdx).toBeGreaterThan(-1);
    expect(langIdx).toBeGreaterThan(modeIdx);
    expect(prompt.trimEnd().endsWith(line)).toBe(true);
  });

  it('omits the language line entirely when locale is undefined', () => {
    const run = makeRun({ locale: undefined });
    const mode = getP2pMode('audit');
    const prompt = buildHopPrompt(run, mode, defaultOpts);
    expect(prompt).not.toContain('You shall use');
  });

  it('does NOT pollute extraPrompt with the language hint (it is now structured)', () => {
    const run = makeRun({ locale: 'en', extraPrompt: 'focus on security' });
    const mode = getP2pMode('audit');
    const prompt = buildHopPrompt(run, mode, defaultOpts);
    // extraPrompt is unchanged: only the user-supplied "focus on security"
    // appears in the "Additional instructions:" trailer.
    expect(prompt).toContain('Additional instructions: focus on security');
    expect(prompt).not.toContain("Use the user's selected i18n language");
    // The standard language line is last, after user-supplied extras.
    const line = 'You shall use en to reply and use en to discuss.';
    expect(prompt).toContain(line);
    expect(prompt.indexOf(line)).toBeGreaterThan(prompt.indexOf('Additional instructions: focus on security'));
    expect(prompt.trimEnd().endsWith(line)).toBe(true);
  });
});
