import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import { agentRunnerInternals, createMindSpaceAgentRunner } from './mindspace-agent-runner.mjs';

test('buildAgentJobPrompt includes asset excerpts and strict JSON instructions', () => {
  const prompt = agentRunnerInternals.buildAgentJobPrompt(
    { instruction: '根据资料生成周报' },
    [
      {
        displayName: 'weekly.md',
        mimeType: 'text/markdown',
        excerpt: '# Weekly\nDone.',
      },
    ],
  );

  assert.match(prompt, /合法 JSON 对象/);
  assert.match(prompt, /weekly\.md/);
  assert.match(prompt, /# Weekly/);
});

test('extractJsonObject and normalizeStructuredResult parse structured agent output', () => {
  const payload = agentRunnerInternals.extractJsonObject(`
这里是结果
\`\`\`json
{"title":"项目周报","summary":"本周进展","content":"# 周报\\n\\n完成了任务","content_format":"markdown"}
\`\`\`
`);
  const result = agentRunnerInternals.normalizeStructuredResult(payload);
  assert.equal(result.title, '项目周报');
  assert.equal(result.contentFormat, 'markdown');
});

test('extractJsonObject repairs multiline content and trailing commas', () => {
  const multiline = agentRunnerInternals.extractJsonObject(`{
"title":"项目周报",
"summary":"本周进展",
"content":"# 周报

完成了任务",
"content_format":"markdown"
}`);
  assert.equal(multiline.title, '项目周报');
  assert.match(multiline.content, /完成了任务/);

  const trailingComma = agentRunnerInternals.extractJsonObject(
    '{"title":"页面","summary":"摘要","content":"正文","content_format":"markdown",}',
  );
  assert.equal(trailingComma.title, '页面');
});

test('extractJsonObject keeps braces inside content strings', () => {
  const payload = agentRunnerInternals.extractJsonObject(
    '{"title":"页面","summary":"摘要","content":"function demo() { return 1; }","content_format":"markdown"}',
  );
  assert.match(payload.content, /return 1/);
});

test('readAssetContext reads and truncates text assets', async () => {
  const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-agent-runner-'));
  const file = path.join(dir, 'note.md');
  await fs.writeFile(file, 'A'.repeat(64), 'utf8');

  const context = await agentRunnerInternals.readAssetContext(
    { assetId: 'a1', displayName: 'note.md', mimeType: 'text/markdown', path: file },
    16,
  );
  assert.equal(context.excerpt.length, 16);
  assert.equal(context.truncated, true);
});

test('runner claims job, executes reply, bills usage, and completes job', async () => {
  const calls = [];
  const runner = createMindSpaceAgentRunner({
    apiTarget: 'http://example.test',
    apiSecret: 'secret',
    userAuth: {
      async canUseChat() {
        return { ok: true };
      },
      async resolveWorkingDir() {
        return '/tmp/user-space';
      },
      async getAgentSessionPolicy() {
        return { enableContextMemory: false, unrestricted: true };
      },
      async getUserPublishLayout() {
        return null;
      },
      async registerAgentSession(userId, sessionId) {
        calls.push(['registerAgentSession', userId, sessionId]);
      },
      async billSessionUsage(userId, sessionId, tokenState, requestId) {
        calls.push(['billSessionUsage', userId, sessionId, tokenState, requestId]);
        return { ok: true, costCents: 1 };
      },
    },
    agentJobService: {
      async claimJob(jobId) {
        calls.push(['claimJob', jobId]);
        return {
          jobId,
          userId: 'user-1',
          jobToken: 'job-token',
          instruction: '生成周报',
          allowedAssets: [{ assetId: 'asset-1', displayName: 'weekly.md', mimeType: 'text/markdown' }],
        };
      },
      async getAssetForJob(jobId, token, assetId) {
        calls.push(['getAssetForJob', jobId, token, assetId]);
        const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-agent-job-'));
        const file = path.join(dir, 'weekly.md');
        await fs.writeFile(file, '# Weekly\nAll good.', 'utf8');
        return {
          assetId,
          displayName: 'weekly.md',
          mimeType: 'text/markdown',
          path: file,
        };
      },
      async completeJob(jobId, token, payload) {
        calls.push(['completeJob', jobId, token, payload]);
        return { id: jobId, status: payload.status ?? 'completed', resultPageId: 'page-1' };
      },
    },
    executeSessionReply: async (_apiFetch, sessionId, requestId, prompt) => {
      calls.push(['executeSessionReply', sessionId, requestId, prompt]);
      return {
        text: '{"title":"项目周报","summary":"本周完成情况","content":"# 周报\\n\\n完成了全部任务","content_format":"markdown"}',
        tokenState: {
          inputTokens: 10,
          outputTokens: 20,
          totalTokens: 30,
          accumulatedInputTokens: 10,
          accumulatedOutputTokens: 20,
          accumulatedTotalTokens: 30,
        },
      };
    },
    apiFetchImpl: async (_pathname, _init) => ({
      ok: true,
      async text() {
        return JSON.stringify({ id: 'session-1' });
      },
    }),
  });

  const result = await runner.runJob('job-1');
  assert.equal(result.status, 'completed');
  assert.equal(calls.some((item) => item[0] === 'billSessionUsage'), true);
  const completeCall = calls.find((item) => item[0] === 'completeJob');
  assert.equal(completeCall[3].title, '项目周报');
});
