import { describe, it, expect, vi } from 'vitest';
import { compactHistory, DEFAULT_COMPACTION_CONFIG } from './compaction.js';
import type { CompactionConfig } from './compaction.js';
import type { AgentOrchestrator } from '../backends/native/orchestrator.js';
import type { Message } from '../models/types.js';

function makeMockOrchestrator(summaryText = 'Summary of conversation'): AgentOrchestrator {
  return {
    getDelegationTier: vi.fn().mockReturnValue('fast'),
    delegate: vi.fn().mockResolvedValue({
      content: summaryText,
      usage: { inputTokens: 100, outputTokens: 50 },
      tier: 'fast',
    }),
  } as unknown as AgentOrchestrator;
}

function makeMessages(count: number): Message[] {
  const msgs: Message[] = [];
  for (let i = 0; i < count; i++) {
    msgs.push({
      role: i % 2 === 0 ? 'user' : 'assistant',
      content: `Message ${i}`,
    });
  }
  return msgs;
}

describe('compactHistory', () => {
  const config: CompactionConfig = {
    thresholdPct: 80,
    keepTurns: 2,  // keeps last 4 messages
    summaryMaxTokens: 1024,
    importanceThreshold: 1,
  };

  it('returns no-op when messages count is at or below keepTurns threshold', async () => {
    const messages = makeMessages(4);  // keepTurns=2 → keep 4 messages
    const orchestrator = makeMockOrchestrator();

    const result = await compactHistory({ messages, orchestrator, config });

    expect(result.compactedCount).toBe(0);
    expect(result.messages).toEqual(messages);
    expect(orchestrator.delegate).not.toHaveBeenCalled();
  });

  it('compacts older messages and keeps recent ones', async () => {
    const messages = makeMessages(10);  // 10 messages, keep last 4, compact 6
    const orchestrator = makeMockOrchestrator('Summarized conversation');

    const result = await compactHistory({ messages, orchestrator, config });

    expect(result.compactedCount).toBe(6);
    expect(result.messages).toHaveLength(5);  // 1 summary + 4 kept
    expect(result.messages[0].role).toBe('assistant');
    expect(result.messages[0].content).toContain('[Summary of earlier conversation]');
    expect(result.messages[0].content).toContain('Summarized conversation');
    // Last 4 messages should be preserved
    expect(result.messages.slice(1)).toEqual(messages.slice(-4));
  });

  it('calls delegate with compaction tier and correct params', async () => {
    const messages = makeMessages(10);
    const orchestrator = makeMockOrchestrator();

    await compactHistory({ messages, orchestrator, config });

    expect(orchestrator.getDelegationTier).toHaveBeenCalledWith('compaction');
    expect(orchestrator.delegate).toHaveBeenCalledWith(
      expect.objectContaining({
        tier: 'fast',
        maxTokens: 1024,
      }),
    );
  });

  it('populates token counts in result', async () => {
    const messages = makeMessages(10);
    const orchestrator = makeMockOrchestrator();

    const result = await compactHistory({ messages, orchestrator, config });

    expect(result.tokensBefore).toBeGreaterThan(0);
    expect(result.tokensAfter).toBeGreaterThan(0);
    expect(result.tokensAfter).toBeLessThan(result.tokensBefore);
  });

  it('handles single turn above keepTurns threshold', async () => {
    // 3 turns = 6 messages, keepTurns=2 keeps 4, compacts 2
    const messages = makeMessages(6);
    const orchestrator = makeMockOrchestrator();

    const result = await compactHistory({ messages, orchestrator, config });

    expect(result.compactedCount).toBe(2);
    expect(result.messages).toHaveLength(5);  // 1 summary + 4 kept
  });

  it('uses DEFAULT_COMPACTION_CONFIG values correctly', () => {
    expect(DEFAULT_COMPACTION_CONFIG.thresholdPct).toBe(80);
    expect(DEFAULT_COMPACTION_CONFIG.keepTurns).toBe(4);
    expect(DEFAULT_COMPACTION_CONFIG.summaryMaxTokens).toBe(1024);
    expect(DEFAULT_COMPACTION_CONFIG.importanceThreshold).toBe(1);
  });

  it('shifts leading assistant messages from toKeep into toCompact to ensure user-first', async () => {
    // 9 messages: user(0), assistant(1), ..., user(8)
    // With keepTurns=2 (keepCount=4), toKeep starts as [assistant(5), user(6), assistant(7), user(8)]
    // The fix shifts assistant(5) into toCompact, so toKeep = [user(6), assistant(7), user(8)]
    const messages = makeMessages(9);
    const orchestrator = makeMockOrchestrator();

    const result = await compactHistory({ messages, orchestrator, config });

    // summary(assistant) + user(6) + assistant(7) + user(8) = 4 messages
    expect(result.messages).toHaveLength(4);
    expect(result.compactedCount).toBe(6);
    // First is summary (assistant), second must be user
    expect(result.messages[0].role).toBe('assistant');
    expect(result.messages[0].content).toContain('[Summary of earlier conversation]');
    expect(result.messages[1].role).toBe('user');
    expect(result.messages[1].content).toBe('Message 6');
  });

  it('preserves high-importance older turns instead of compacting them', async () => {
    const messages: Message[] = [
      { role: 'user', content: 'hello' },
      { role: 'assistant', content: 'hi' },
      { role: 'user', content: 'I prefer concise responses and markdown tables.' },
      { role: 'assistant', content: 'noted' },
      { role: 'user', content: 'Message 4' },
      { role: 'assistant', content: 'Message 5' },
      { role: 'user', content: 'Message 6' },
      { role: 'assistant', content: 'Message 7' },
    ];

    const orchestrator = makeMockOrchestrator();
    const result = await compactHistory({
      messages,
      orchestrator,
      config: { ...config, importanceThreshold: 0.45 },
    });

    expect(result.messages.some(msg => typeof msg.content === 'string' && msg.content.includes('I prefer concise responses'))).toBe(true);
    expect(result.messages.some(msg => typeof msg.content === 'string' && msg.content.includes('[Summary of earlier conversation]'))).toBe(true);
    expect(result.messages.length).toBeGreaterThan(5);
  });
});
