/**
 * Tests for SessionParser service.
 *
 * Tests parsing functionality:
 * - Message type grouping
 * - Sidechain vs main thread separation
 * - Task call extraction
 * - Tool result linking
 * - Time range calculation
 */

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

import {
  SessionParser,
  type ParsedSession,
} from '../../../../src/main/services/parsing/SessionParser';
import type { ParsedMessage } from '../../../../src/main/types';
import { LocalFileSystemProvider } from '../../../../src/main/services/infrastructure/LocalFileSystemProvider';

// =============================================================================
// Mock ProjectScanner
// =============================================================================

const mockProjectScanner = {
  scan: vi.fn(),
  getSessionPath: vi.fn(),
  listSessionsPaginated: vi.fn(),
  listSessions: vi.fn(),
  listSubagentFiles: vi.fn(),
  getSession: vi.fn(),
  listWorktreeSessions: vi.fn(),
  scanWithWorktreeGrouping: vi.fn(),
  getFileSystemProvider: vi.fn().mockReturnValue(new LocalFileSystemProvider()),
};

// =============================================================================
// Test Helpers
// =============================================================================

/**
 * Creates a minimal ParsedMessage for testing.
 */
function createMessage(overrides: Partial<ParsedMessage>): ParsedMessage {
  return {
    uuid: `msg-${Math.random().toString(36).slice(2, 11)}`,
    parentUuid: null,
    type: 'user',
    timestamp: new Date(),
    content: '',
    isSidechain: false,
    isMeta: false,
    toolCalls: [],
    toolResults: [],
    ...overrides,
  };
}

// =============================================================================
// Tests
// =============================================================================

describe('SessionParser', () => {
  let parser: SessionParser;

  beforeEach(() => {
    vi.clearAllMocks();
    // @ts-expect-error - Using partial mock
    parser = new SessionParser(mockProjectScanner);
  });

  describe('processMessages (via public methods)', () => {
    // Since processMessages is private, we test its behavior through the query methods

    describe('message type grouping', () => {
      it('should group user messages correctly', () => {
        const messages = [
          createMessage({ type: 'user', content: 'User message 1' }),
          createMessage({ type: 'assistant', content: [{ type: 'text', text: 'Response' }] }),
          createMessage({ type: 'user', content: 'User message 2' }),
        ];

        // Access processMessages result through getUserMessages
        const processedResult = {
          messages,
          metrics: {
            durationMs: 0,
            totalTokens: 0,
            inputTokens: 0,
            outputTokens: 0,
            cacheReadTokens: 0,
            cacheCreationTokens: 0,
            messageCount: messages.length,
          },
          taskCalls: [],
          byType: {
            user: messages.filter((m) => m.type === 'user'),
            realUser: messages.filter((m) => m.type === 'user' && !m.isMeta),
            internalUser: messages.filter((m) => m.type === 'user' && m.isMeta),
            assistant: messages.filter((m) => m.type === 'assistant'),
            system: [],
            other: [],
          },
          sidechainMessages: [],
          mainMessages: messages,
        };

        const userMessages = parser.getUserMessages(processedResult);
        expect(userMessages).toHaveLength(2);
      });

      it('should separate real user vs internal user messages', () => {
        const messages = [
          createMessage({ type: 'user', content: 'Real user input', isMeta: false }),
          createMessage({
            type: 'user',
            content: [{ type: 'tool_result', tool_use_id: 't1', content: 'result' }],
            isMeta: true,
          }),
        ];

        const processedResult: ParsedSession = {
          messages,
          metrics: {
            durationMs: 0,
            totalTokens: 0,
            inputTokens: 0,
            outputTokens: 0,
            cacheReadTokens: 0,
            cacheCreationTokens: 0,
            messageCount: messages.length,
          },
          taskCalls: [],
          byType: {
            user: messages.filter((m) => m.type === 'user'),
            realUser: messages.filter((m) => m.type === 'user' && !m.isMeta),
            internalUser: messages.filter((m) => m.type === 'user' && m.isMeta),
            assistant: [],
            system: [],
            other: [],
          },
          sidechainMessages: [],
          mainMessages: messages,
        };

        expect(processedResult.byType.realUser).toHaveLength(1);
        expect(processedResult.byType.internalUser).toHaveLength(1);
      });
    });

    describe('sidechain separation', () => {
      it('should separate sidechain from main thread messages', () => {
        const messages = [
          createMessage({ type: 'user', content: 'Main', isSidechain: false }),
          createMessage({
            type: 'assistant',
            content: [{ type: 'text', text: 'Sidechain' }],
            isSidechain: true,
          }),
          createMessage({
            type: 'assistant',
            content: [{ type: 'text', text: 'Main' }],
            isSidechain: false,
          }),
        ];

        const sidechainMessages = messages.filter((m) => m.isSidechain);
        const mainMessages = messages.filter((m) => !m.isSidechain);

        expect(sidechainMessages).toHaveLength(1);
        expect(mainMessages).toHaveLength(2);
      });
    });
  });

  describe('getResponses', () => {
    it('should get assistant responses after user message', () => {
      const userMsgUuid = 'user-1';
      const messages = [
        createMessage({ uuid: userMsgUuid, type: 'user', content: 'Question' }),
        createMessage({
          uuid: 'asst-1',
          type: 'assistant',
          content: [{ type: 'text', text: 'Answer 1' }],
        }),
        createMessage({
          uuid: 'asst-2',
          type: 'assistant',
          content: [{ type: 'text', text: 'Answer 2' }],
        }),
        createMessage({ uuid: 'user-2', type: 'user', content: 'Next question' }),
      ];

      const responses = parser.getResponses(messages, userMsgUuid);
      expect(responses).toHaveLength(2);
      expect(responses[0].uuid).toBe('asst-1');
      expect(responses[1].uuid).toBe('asst-2');
    });

    it('should stop at next user message', () => {
      const userMsgUuid = 'user-1';
      const messages = [
        createMessage({ uuid: userMsgUuid, type: 'user', content: 'Q1' }),
        createMessage({
          uuid: 'asst-1',
          type: 'assistant',
          content: [{ type: 'text', text: 'A1' }],
        }),
        createMessage({ uuid: 'user-2', type: 'user', content: 'Q2' }),
        createMessage({
          uuid: 'asst-2',
          type: 'assistant',
          content: [{ type: 'text', text: 'A2' }],
        }),
      ];

      const responses = parser.getResponses(messages, userMsgUuid);
      expect(responses).toHaveLength(1);
      expect(responses[0].uuid).toBe('asst-1');
    });

    it('should return empty for non-existent message', () => {
      const messages = [createMessage({ uuid: 'user-1', type: 'user', content: 'Q' })];

      const responses = parser.getResponses(messages, 'non-existent');
      expect(responses).toEqual([]);
    });
  });

  describe('getTaskCalls', () => {
    it('should extract Task tool calls from messages', () => {
      const messages = [
        createMessage({
          type: 'assistant',
          content: [
            { type: 'text', text: 'Spawning agent' },
            {
              type: 'tool_use',
              id: 'task-1',
              name: 'Task',
              input: { prompt: 'Do something', subagent_type: 'explore' },
            },
          ],
          toolCalls: [
            {
              id: 'task-1',
              name: 'Task',
              input: { prompt: 'Do something', subagent_type: 'explore' },
              isTask: true,
              taskDescription: 'Do something',
              taskSubagentType: 'explore',
            },
          ],
        }),
        createMessage({
          type: 'assistant',
          content: [
            { type: 'tool_use', id: 'read-1', name: 'Read', input: { file_path: 'test.ts' } },
          ],
          toolCalls: [
            { id: 'read-1', name: 'Read', input: { file_path: 'test.ts' }, isTask: false },
          ],
        }),
      ];

      const taskCalls = parser.getTaskCalls(messages);
      expect(taskCalls).toHaveLength(1);
      expect(taskCalls[0].name).toBe('Task');
      expect(taskCalls[0].isTask).toBe(true);
    });
  });

  describe('getToolCallsByName', () => {
    it('should get tool calls by name', () => {
      const messages = [
        createMessage({
          type: 'assistant',
          toolCalls: [
            { id: 'read-1', name: 'Read', input: { file_path: 'a.ts' }, isTask: false },
            {
              id: 'write-1',
              name: 'Write',
              input: { file_path: 'b.ts', content: '' },
              isTask: false,
            },
            { id: 'read-2', name: 'Read', input: { file_path: 'c.ts' }, isTask: false },
          ],
        }),
      ];

      const readCalls = parser.getToolCallsByName(messages, 'Read');
      expect(readCalls).toHaveLength(2);
      expect(readCalls[0].id).toBe('read-1');
      expect(readCalls[1].id).toBe('read-2');
    });
  });

  describe('findToolResult', () => {
    it('should find tool result by tool call ID', () => {
      const toolCallId = 'tool-1';
      const messages = [
        createMessage({
          type: 'user',
          isMeta: true,
          toolResults: [{ toolUseId: toolCallId, content: 'result content', isError: false }],
        }),
      ];

      const found = parser.findToolResult(messages, toolCallId);
      expect(found).not.toBeNull();
      expect(found?.result.toolUseId).toBe(toolCallId);
      expect(found?.result.content).toBe('result content');
    });

    it('should return null for non-existent tool call', () => {
      const messages = [
        createMessage({
          type: 'user',
          isMeta: true,
          toolResults: [{ toolUseId: 'other-id', content: '', isError: false }],
        }),
      ];

      const found = parser.findToolResult(messages, 'non-existent');
      expect(found).toBeNull();
    });
  });

  describe('getTimeRange', () => {
    it('should calculate time range correctly', () => {
      const start = new Date('2024-01-01T10:00:00Z');
      const end = new Date('2024-01-01T10:05:00Z');
      const messages = [
        createMessage({ timestamp: start }),
        createMessage({ timestamp: new Date('2024-01-01T10:02:00Z') }),
        createMessage({ timestamp: end }),
      ];

      const range = parser.getTimeRange(messages);
      expect(range.start.getTime()).toBe(start.getTime());
      expect(range.end.getTime()).toBe(end.getTime());
      expect(range.durationMs).toBe(5 * 60 * 1000); // 5 minutes
    });

    it('should handle empty messages', () => {
      const range = parser.getTimeRange([]);
      expect(range.durationMs).toBe(0);
    });

    it('should handle single message', () => {
      const timestamp = new Date('2024-01-01T10:00:00Z');
      const messages = [createMessage({ timestamp })];

      const range = parser.getTimeRange(messages);
      expect(range.start.getTime()).toBe(timestamp.getTime());
      expect(range.end.getTime()).toBe(timestamp.getTime());
      expect(range.durationMs).toBe(0);
    });
  });

  describe('buildMessageTree', () => {
    it('should build parent-child tree', () => {
      const messages = [
        createMessage({ uuid: 'root', parentUuid: null }),
        createMessage({ uuid: 'child1', parentUuid: 'root' }),
        createMessage({ uuid: 'child2', parentUuid: 'root' }),
        createMessage({ uuid: 'grandchild', parentUuid: 'child1' }),
      ];

      const tree = parser.buildMessageTree(messages);

      expect(tree.get('root')?.map((m) => m.uuid)).toContain('child1');
      expect(tree.get('root')?.map((m) => m.uuid)).toContain('child2');
      expect(tree.get('child1')?.map((m) => m.uuid)).toContain('grandchild');
    });
  });

  describe('getChildMessages', () => {
    it('should get direct children', () => {
      const messages = [
        createMessage({ uuid: 'parent', parentUuid: null }),
        createMessage({ uuid: 'child1', parentUuid: 'parent' }),
        createMessage({ uuid: 'child2', parentUuid: 'parent' }),
        createMessage({ uuid: 'other', parentUuid: 'other-parent' }),
      ];

      const children = parser.getChildMessages(messages, 'parent');
      expect(children).toHaveLength(2);
      expect(children.map((m) => m.uuid)).toContain('child1');
      expect(children.map((m) => m.uuid)).toContain('child2');
    });
  });

  describe('extractText', () => {
    it('should extract text from string content', () => {
      const message = createMessage({ content: 'Hello world' });
      expect(parser.extractText(message)).toBe('Hello world');
    });
  });

  describe('getMessagePreview', () => {
    it('should truncate long messages', () => {
      const longText = 'A'.repeat(200);
      const message = createMessage({ content: longText });

      const preview = parser.getMessagePreview(message, 50);
      expect(preview.length).toBe(53); // 50 chars + '...'
      expect(preview.endsWith('...')).toBe(true);
    });

    it('should not truncate short messages', () => {
      const message = createMessage({ content: 'Short' });
      const preview = parser.getMessagePreview(message, 50);
      expect(preview).toBe('Short');
    });
  });
});
