import { describe, it, expect } from 'vitest';
import { prune, filterCompacted } from '../../../src/context/prune.js';
import {
  COMPACTED_PLACEHOLDER,
  SUMMARY_MARKER,
  COMPACTED_MARKER,
  type CompressionConfig,
} from '../../../src/context/types.js';
import type { ModelMessage } from 'ai';

// 创建测试用的工具结果消息
function createToolResultMessage(
  toolCallId: string,
  result: unknown,
  compacted = false
): ModelMessage {
  const content: Record<string, unknown> = {
    type: 'tool-result',
    toolCallId,
    toolName: 'test_tool',
    result,
  };

  if (compacted) {
    content[COMPACTED_MARKER] = {
      compactedAt: Date.now(),
      originalSize: 100,
    };
  }

  return {
    role: 'tool',
    content: [content],
  } as ModelMessage;
}

// 创建测试用的工具调用消息
function createToolCallMessage(toolCallId: string): ModelMessage {
  return {
    role: 'assistant',
    content: [
      {
        type: 'tool-call',
        toolCallId,
        toolName: 'test_tool',
        args: { param: 'value' },
      },
    ],
  } as ModelMessage;
}

// 创建用户消息
function createUserMessage(content: string): ModelMessage {
  return { role: 'user', content };
}

// 创建助手消息
function createAssistantMessage(content: string): ModelMessage {
  return { role: 'assistant', content };
}

// 创建摘要消息
function createSummaryMessage(): ModelMessage {
  return {
    role: 'assistant',
    content: `${SUMMARY_MARKER}\n## 对话摘要\n\n这是一个摘要\n${SUMMARY_MARKER}`,
  };
}

// 创建大工具结果（指定 token 数）
function createLargeToolResult(toolCallId: string, sizeInChars: number): ModelMessage {
  // 约 4 字符/token（英文）
  const content = 'a'.repeat(sizeInChars);
  return createToolResultMessage(toolCallId, { output: content });
}

describe('prune - 消息裁剪策略', () => {
  // 测试配置：小值便于测试
  const testConfig: CompressionConfig = {
    contextLimit: 1000,
    outputReserve: 100,
    pruneProtect: 200, // 保护最近 200 tokens
    pruneMinimum: 50, // 至少释放 50 tokens 才执行
    overflowThreshold: 0.85,
  };

  describe('基本裁剪行为', () => {
    it('空消息数组不裁剪', () => {
      const result = prune([], testConfig);

      expect(result.messages).toHaveLength(0);
      expect(result.freedTokens).toBe(0);
    });

    it('只有用户消息不裁剪', () => {
      const messages: ModelMessage[] = [
        createUserMessage('Hello'),
        createUserMessage('How are you?'),
      ];

      const result = prune(messages, testConfig);

      expect(result.messages).toEqual(messages);
      expect(result.freedTokens).toBe(0);
    });

    it('保护范围内的工具结果不裁剪', () => {
      // 创建小的工具结果（在保护范围内）
      const messages: ModelMessage[] = [
        createUserMessage('Use tool'),
        createToolCallMessage('call_1'),
        createToolResultMessage('call_1', { output: 'small result' }),
      ];

      const result = prune(messages, testConfig);

      // 不应该裁剪
      expect(result.freedTokens).toBe(0);
    });

    it('裁剪超出保护范围的工具结果', () => {
      // 创建多个工具结果，超出保护范围
      const messages: ModelMessage[] = [
        createUserMessage('Task 1'),
        createToolCallMessage('call_1'),
        createLargeToolResult('call_1', 1000), // ~250 tokens, 超出 pruneProtect
        createUserMessage('Task 2'),
        createToolCallMessage('call_2'),
        createLargeToolResult('call_2', 400), // ~100 tokens
        createUserMessage('Task 3'),
        createToolCallMessage('call_3'),
        createToolResultMessage('call_3', { output: 'recent' }), // 最近的，在保护范围内
      ];

      const result = prune(messages, testConfig);

      // 应该裁剪旧的大工具结果
      expect(result.freedTokens).toBeGreaterThan(0);
    });
  });

  describe('摘要消息边界', () => {
    it('遇到摘要消息停止裁剪', () => {
      const messages: ModelMessage[] = [
        createSummaryMessage(), // 摘要消息
        createUserMessage('After summary'),
        createToolCallMessage('call_1'),
        createLargeToolResult('call_1', 2000), // 大结果
      ];

      const result = prune(messages, testConfig);

      // 因为遇到摘要消息，不会继续向前裁剪
      // 但是摘要后面的大工具结果如果超出保护范围仍会被裁剪
      expect(result.messages[0]).toEqual(messages[0]); // 摘要保留
    });

    it('摘要消息前的内容不裁剪', () => {
      const messages: ModelMessage[] = [
        createUserMessage('Before summary'),
        createToolCallMessage('call_old'),
        createLargeToolResult('call_old', 2000), // 摘要前的大结果
        createSummaryMessage(),
        createUserMessage('After summary'),
      ];

      const result = prune(messages, testConfig);

      // 摘要前的内容因为遇到摘要边界停止
      expect(result.messages.length).toBe(messages.length);
    });
  });

  describe('已压缩内容处理', () => {
    it('遇到已压缩的工具结果停止', () => {
      const messages: ModelMessage[] = [
        createUserMessage('Task 1'),
        createToolCallMessage('call_1'),
        createToolResultMessage('call_1', COMPACTED_PLACEHOLDER, true), // 已压缩
        createUserMessage('Task 2'),
        createToolCallMessage('call_2'),
        createLargeToolResult('call_2', 1000), // 新的大结果
      ];

      const result = prune(messages, testConfig);

      // 遇到已压缩的结果应该停止继续向前
      expect(result.messages).toBeDefined();
    });
  });

  describe('最小裁剪量检查', () => {
    it('释放量不足最小值时不执行裁剪', () => {
      // 使用较大的 pruneMinimum
      const strictConfig: CompressionConfig = {
        ...testConfig,
        pruneMinimum: 10000, // 要求至少释放 10000 tokens
      };

      const messages: ModelMessage[] = [
        createUserMessage('Task'),
        createToolCallMessage('call_1'),
        createLargeToolResult('call_1', 400), // 只有 ~100 tokens
      ];

      const result = prune(messages, strictConfig);

      expect(result.freedTokens).toBe(0);
      expect(result.messages).toEqual(messages);
    });
  });

  describe('深拷贝验证', () => {
    it('不修改原消息数组', () => {
      const messages: ModelMessage[] = [
        createUserMessage('Task'),
        createToolCallMessage('call_1'),
        createLargeToolResult('call_1', 2000),
        createUserMessage('Recent'),
      ];

      const originalLength = messages.length;
      const originalFirstMessage = { ...messages[0] };

      prune(messages, testConfig);

      expect(messages.length).toBe(originalLength);
      expect(messages[0]).toEqual(originalFirstMessage);
    });
  });
});

describe('filterCompacted - 过滤已压缩内容', () => {
  it('不修改普通消息', () => {
    const messages: ModelMessage[] = [
      createUserMessage('Hello'),
      createAssistantMessage('Hi there'),
    ];

    const result = filterCompacted(messages);

    expect(result).toEqual(messages);
  });

  it('不修改未压缩的工具结果', () => {
    const messages: ModelMessage[] = [
      createToolResultMessage('call_1', { output: 'normal result' }),
    ];

    const result = filterCompacted(messages);

    expect(result).toEqual(messages);
  });

  it('将已压缩工具结果替换为占位符', () => {
    const messages: ModelMessage[] = [
      createToolResultMessage('call_1', 'original', true), // 已压缩
    ];

    const result = filterCompacted(messages);

    const content = result[0].content as { result: unknown }[];
    expect(content[0].result).toBe(COMPACTED_PLACEHOLDER);
  });

  it('混合内容正确处理', () => {
    const messages: ModelMessage[] = [
      createUserMessage('Task'),
      createToolResultMessage('call_1', 'original', true), // 已压缩
      createToolResultMessage('call_2', { output: 'normal' }), // 未压缩
      createAssistantMessage('Done'),
    ];

    const result = filterCompacted(messages);

    expect(result).toHaveLength(4);
    // 第一个工具结果应该被替换
    const compactedContent = result[1].content as { result: unknown }[];
    expect(compactedContent[0].result).toBe(COMPACTED_PLACEHOLDER);
    // 第二个工具结果应该保持不变
    const normalContent = result[2].content as { result: unknown }[];
    expect(normalContent[0].result).toEqual({ output: 'normal' });
  });

  it('保留消息的其他属性', () => {
    const messages: ModelMessage[] = [
      {
        role: 'tool',
        content: [
          {
            type: 'tool-result',
            toolCallId: 'call_1',
            toolName: 'my_tool',
            result: 'data',
            [COMPACTED_MARKER]: { compactedAt: 123, originalSize: 100 },
          },
        ],
      } as ModelMessage,
    ];

    const result = filterCompacted(messages);

    const content = result[0].content as { toolCallId: string; toolName: string }[];
    expect(content[0].toolCallId).toBe('call_1');
    expect(content[0].toolName).toBe('my_tool');
  });
});
