import {describe, expect, it} from 'vitest';

import type {ChatTimelineMessageItem} from '@easyflow/common-ui';

import {
  applyAgentSseEnvelope,
  parseAgentSseMessage,
  recordsToTimelineItems,
} from './agentTimelineAdapter';

describe('agentTimelineAdapter', () => {
  it('projects history records to chat timeline items', () => {
    const items = recordsToTimelineItems([
      {
        id: '1',
        senderRole: 'user',
        contentText: '帮我查一下',
        roundId: 'r1',
      },
      {
        id: '2',
        senderRole: 'assistant',
        contentText: '查到了',
        roundId: 'r1',
        contentPayload: {
          agentResult: {
            reasoning: '先检索',
            text: '查到了',
            knowledgeReferences: [
              {
                documentName: '手册',
                chunkContent: '内容片段',
              },
            ],
          },
          chains: [
            {
              id: 'tool-1',
              name: 'search',
              status: 'TOOL_RESULT',
              arguments: { q: 'EasyFlow' },
              result: 'ok',
            },
          ],
        },
      },
    ]);

    expect(
      items.some((item) => item.type === 'message' && item.role === 'user'),
    ).toBe(true);
    const assistant = items.find(
      (item): item is ChatTimelineMessageItem =>
        item.type === 'message' && item.role === 'assistant',
    );
    expect(assistant?.parts.some((part) => part.type === 'thinking')).toBe(
      true,
    );
    expect(assistant?.parts.some((part) => part.type === 'text')).toBe(true);
    expect(items.some((item) => item.type === 'tool')).toBe(true);
    expect(assistant?.knowledgeItems?.[0]?.documentName).toBe('手册');
  });

  it('keeps stable ids when history has reasoning, tools and final text', () => {
    const items = recordsToTimelineItems([
      {
        id: '42',
        senderRole: 'assistant',
        contentText: '最终回答',
        roundId: 'r2',
        contentPayload: {
          agentResult: {
            text: '最终回答',
          },
          chains: [
            {
              reasoning_content: '先思考',
            },
            {
              id: 'tool-2',
              name: 'search',
              status: 'TOOL_RESULT',
              result: 'ok',
            },
          ],
          messageChain: [
            {
              role: 'assistant',
              reasoningContent: '中间思考',
              toolCalls: [{ id: 'tool-2', name: 'search', arguments: '{}' }],
            },
            {
              role: 'tool',
              toolCallId: 'tool-2',
              content: 'ok',
            },
          ],
        },
      },
    ]);

    const ids = items.map((item) => item.id);
    expect(new Set(ids).size).toBe(ids.length);
    expect(
      items.filter(
        (item) => item.type === 'message' && item.role === 'assistant',
      ),
    ).toHaveLength(2);
    expect(
      items.some((item) => item.type === 'tool' && item.status === 'success'),
    ).toBe(true);
  });

  it('uses tool display name when restoring aliased MCP tools from history', () => {
    const items = recordsToTimelineItems([
      {
        id: 'mcp-history',
        senderRole: 'assistant',
        contentText: '已完成',
        roundId: 'round-mcp',
        contentPayload: {
          chains: [
            {
              id: 'tool-mcp-1',
              name: 'mcp_123_search',
              toolDisplayName: 'Context MCP - search',
              status: 'TOOL_RESULT',
              result: 'ok',
            },
          ],
          messageChain: [
            {
              role: 'assistant',
              toolCalls: [
                {
                  id: 'tool-mcp-1',
                  name: 'mcp_123_search',
                  toolDisplayName: 'Context MCP - search',
                  arguments: '{}',
                },
              ],
            },
            {
              role: 'tool',
              toolCallId: 'tool-mcp-1',
              content: 'ok',
            },
          ],
        },
      },
    ]);

    const tool = items.find((item) => item.type === 'tool');
    expect(tool?.toolName).toBe('Context MCP - search');
  });

  it('hides knowledge retrieval cards when restoring agent chat history', () => {
    const items = recordsToTimelineItems([
      {
        id: '417197643647811584',
        senderRole: 'assistant',
        contentText:
          '根据知识库中的信息，2026年暑假的时间安排是：7月1日到8月15日。',
        roundId: '417197622424633344',
        contentPayload: {
          chains: [
            {
              id: 'call_0fc660e9d203416983ccca7e',
              name: 'retrieve_knowledge',
              result: 'Retrieved 2 relevant document(s)',
              status: 'TOOL_RESULT',
              arguments: {
                query: '暑假时间',
              },
            },
          ],
          agentResult: {
            text: '根据知识库中的信息，2026年暑假的时间安排是：7月1日到8月15日。',
            knowledgeReferences: [
              {
                chunkContent:
                  '问题：2026 年暑假安排\n答案：2026 年7 月 1 日到 8 月 15 日',
                documentId: '411358369563336704',
                knowledgeName: 'faq',
                knowledgeType: 'FAQ',
              },
            ],
          },
          messageChain: [
            {
              role: 'assistant',
              toolCalls: [
                {
                  id: 'call_0fc660e9d203416983ccca7e',
                  name: 'retrieve_knowledge',
                  arguments: '{query=暑假时间}',
                },
              ],
            },
            {
              role: 'tool',
              content: 'Retrieved 2 relevant document(s)',
              toolCallId: 'call_0fc660e9d203416983ccca7e',
            },
            {
              role: 'assistant',
              content:
                '根据知识库中的信息，2026年暑假的时间安排是：7月1日到8月15日。',
            },
          ],
        },
      },
    ]);

    expect(items.some((item) => item.type === 'tool')).toBe(false);
    expect(
      items.some(
        (item) => item.type === 'status' && item.label === '已检索知识库',
      ),
    ).toBe(true);
    expect(
      items.some((item) => item.type === 'message' && item.role === 'assistant'),
    ).toBe(true);
    const assistant = items.find(
      (item): item is ChatTimelineMessageItem =>
        item.type === 'message' && item.role === 'assistant',
    );
    expect(assistant?.knowledgeItems?.[0]?.knowledgeName).toBe('faq');
  });

  it('hides internal fragment and context reload tools from history', () => {
    const items = recordsToTimelineItems([
      {
        id: 'internal-tools',
        senderRole: 'assistant',
        contentText: '已处理',
        roundId: 'round-internal',
        contentPayload: {
          chains: [
            {
              id: 'fragment-1',
              name: '__fragment__',
              status: 'TOOL_RESULT',
              result: 'fragment',
            },
            {
              id: 'context-1',
              name: 'context_reload',
              status: 'TOOL_RESULT',
              result: 'reload',
            },
          ],
          agentResult: {
            text: '已处理',
          },
          messageChain: [
            {
              role: 'assistant',
              toolCalls: [
                { id: 'fragment-1', name: '__fragment__' },
                { id: 'context-1', name: 'context_reload' },
              ],
            },
            {
              role: 'tool',
              toolCallId: 'fragment-1',
              content: 'fragment',
            },
            {
              role: 'tool',
              toolCallId: 'context-1',
              content: 'reload',
            },
          ],
        },
      },
    ]);

    expect(items.some((item) => item.type === 'tool')).toBe(false);
    expect(items.some((item) => item.type === 'status')).toBe(false);
    expect(
      items.some((item) => item.type === 'message' && item.role === 'assistant'),
    ).toBe(true);
  });

  it('parses raw SSE text as message delta', () => {
    const envelope = parseAgentSseMessage({
      data: 'hello',
      event: '',
      id: '',
      retry: undefined,
    });

    expect(envelope).toMatchObject({
      domain: 'LLM',
      type: 'MESSAGE',
      payload: { delta: 'hello' },
    });
  });

  it('applies streaming text, HITL approval and error envelopes', () => {
    const items: any[] = [];

    applyAgentSseEnvelope(items, {
      domain: 'LLM',
      type: 'MESSAGE',
      payload: { delta: '你好' },
    });
    applyAgentSseEnvelope(items, {
      domain: 'TOOL',
      type: 'FORM_REQUEST',
      payload: {
        requestId: 'req-1',
        resumeToken: 'token-1',
        toolCallId: 'tool-1',
        toolName: 'workflow',
        input: { name: 'demo' },
      },
    });
    applyAgentSseEnvelope(items, {
      domain: 'ERROR',
      type: 'ERROR',
      payload: { message: '失败' },
    });

    const assistant = items.find(
      (item): item is ChatTimelineMessageItem =>
        item.type === 'message' && item.role === 'assistant',
    );
    const tool = items.find((item) => item.type === 'tool');
    const error = items.find((item) => item.type === 'error');

    expect(assistant?.parts[0]?.content).toBe('你好');
    expect(tool?.status).toBe('pending_approval');
    expect(tool?.approval?.resumeToken).toBe('token-1');
    expect(error?.message).toBe('失败');
  });

  it('keeps assistant text and approval card when a tool request is rejected', () => {
    const items: any[] = [];

    applyAgentSseEnvelope(items, {
      domain: 'LLM',
      type: 'MESSAGE',
      payload: { delta: '正在处理' },
    });
    applyAgentSseEnvelope(items, {
      domain: 'TOOL',
      type: 'FORM_REQUEST',
      payload: {
        requestId: 'req-2',
        resumeToken: 'token-2',
        toolCallId: 'tool-2',
        toolName: '审批工具',
        input: { name: 'demo' },
      },
    });
    applyAgentSseEnvelope(items, {
      domain: 'TOOL',
      type: 'FORM_REJECTED',
      payload: {
        requestId: 'req-2',
        resumeToken: 'token-2',
        toolCallId: 'tool-2',
        reason: '用户拒绝执行',
      },
    });

    const assistant = items.find(
      (item): item is ChatTimelineMessageItem =>
        item.type === 'message' && item.role === 'assistant',
    );
    const tool = items.find((item) => item.type === 'tool');

    expect(assistant?.parts[0]?.content).toBe('正在处理');
    expect(items).toHaveLength(2);
    expect(tool?.status).toBe('rejected');
    expect(tool?.rejectReason).toBe('用户拒绝执行');
  });

  it('applies streaming round metadata to assistant messages for action toolbar anchoring', () => {
    const items: any[] = [];

    applyAgentSseEnvelope(
      items,
      {
        domain: 'LLM',
        type: 'MESSAGE',
        payload: { delta: '准备调用工具' },
      },
      { roundId: 'runtime-round-1' },
    );

    const assistant = items.find(
      (item): item is ChatTimelineMessageItem =>
        item.type === 'message' && item.role === 'assistant',
    );

    expect(assistant?.roundId).toBe('runtime-round-1');
    expect(assistant?.parts[0]?.content).toBe('准备调用工具');
  });

  it('updates memory compression status within the current round', () => {
    const items: any[] = [];

    applyAgentSseEnvelope(
      items,
      {
        domain: 'BUSINESS',
        type: 'STATUS',
        payload: {
          label: '正在整理上下文',
          phase: 'started',
          status: 'running',
          statusKey: 'memory-compression',
        },
      },
      { roundId: 'round-a' },
    );
    applyAgentSseEnvelope(
      items,
      {
        domain: 'BUSINESS',
        type: 'STATUS',
        payload: {
          compressed: true,
          label: '已整理上下文',
          phase: 'completed',
          status: 'done',
          statusKey: 'memory-compression',
        },
      },
      { roundId: 'round-a' },
    );

    const statuses = items.filter((item) => item.type === 'status');
    expect(statuses).toHaveLength(1);
    expect(statuses[0]?.label).toBe('已整理上下文');
    expect(statuses[0]?.status).toBe('done');
    expect(statuses[0]?.statusKey).toBe('memory-compression:round-a');
  });

  it('keeps memory compression statuses isolated by round', () => {
    const items: any[] = [];

    applyAgentSseEnvelope(
      items,
      {
        domain: 'BUSINESS',
        type: 'STATUS',
        payload: {
          compressed: true,
          label: '已整理上下文',
          phase: 'completed',
          status: 'done',
          statusKey: 'memory-compression',
        },
      },
      { roundId: 'round-a' },
    );
    applyAgentSseEnvelope(
      items,
      {
        domain: 'BUSINESS',
        type: 'STATUS',
        payload: {
          compressed: false,
          label: '无需压缩上下文',
          phase: 'completed',
          status: 'done',
          statusKey: 'memory-compression',
        },
      },
      { roundId: 'round-b' },
    );

    const statuses = items.filter((item) => item.type === 'status');
    expect(statuses).toHaveLength(1);
    expect(statuses[0]?.statusKey).toBe('memory-compression:round-a');
    expect(statuses[0]?.label).toBe('已整理上下文');
  });

  it('does not show no-compression status before a later compression run', () => {
    const items: any[] = [];

    applyAgentSseEnvelope(
      items,
      {
        domain: 'BUSINESS',
        type: 'STATUS',
        payload: {
          compressed: false,
          label: '无需压缩上下文',
          phase: 'completed',
          status: 'done',
          statusKey: 'memory-compression',
        },
      },
      { roundId: 'round-a' },
    );
    applyAgentSseEnvelope(
      items,
      {
        domain: 'BUSINESS',
        type: 'STATUS',
        payload: {
          label: '正在整理上下文',
          phase: 'started',
          status: 'running',
          statusKey: 'memory-compression',
        },
      },
      { roundId: 'round-a' },
    );

    const statuses = items.filter((item) => item.type === 'status');
    expect(statuses).toHaveLength(1);
    expect(statuses[0]?.label).toBe('正在整理上下文');
    expect(statuses[0]?.status).toBe('running');
  });
});
