import { describe, it, expect, vi, beforeEach } from 'vitest';
import { adaptSDKMessage } from './claude-messages';
import type { InitContent, TextContent, ThinkingContent, ToolCallContent, ToolResultContent, StatusContent, CostContent } from './claude-messages';

// Mock crypto.randomUUID for deterministic IDs when uuid is missing
beforeEach(() => {
  vi.stubGlobal('crypto', {
    randomUUID: () => 'fallback-uuid',
  });
});

describe('adaptSDKMessage', () => {
  describe('system/init messages', () => {
    it('adapts a system init message', () => {
      const raw = {
        type: 'system',
        subtype: 'init',
        uuid: 'sys-001',
        session_id: 'sess-abc',
        model: 'claude-sonnet-4-20250514',
        cwd: '/home/user/project',
        tools: ['Read', 'Write', 'Bash'],
      };

      const result = adaptSDKMessage(raw);

      expect(result).toHaveLength(1);
      expect(result[0].id).toBe('sys-001');
      expect(result[0].type).toBe('init');
      const content = result[0].content as InitContent;
      expect(content.sessionId).toBe('sess-abc');
      expect(content.model).toBe('claude-sonnet-4-20250514');
      expect(content.cwd).toBe('/home/user/project');
      expect(content.tools).toEqual(['Read', 'Write', 'Bash']);
    });

    it('defaults tools to empty array when missing', () => {
      const raw = {
        type: 'system',
        subtype: 'init',
        uuid: 'sys-002',
        session_id: 'sess-abc',
        model: 'claude-sonnet-4-20250514',
        cwd: '/tmp',
      };

      const result = adaptSDKMessage(raw);
      const content = result[0].content as InitContent;
      expect(content.tools).toEqual([]);
    });
  });

  describe('system/status messages (non-init subtypes)', () => {
    it('adapts a system status message', () => {
      const raw = {
        type: 'system',
        subtype: 'api_key_check',
        uuid: 'sys-003',
        status: 'API key is valid',
      };

      const result = adaptSDKMessage(raw);

      expect(result).toHaveLength(1);
      expect(result[0].type).toBe('status');
      const content = result[0].content as StatusContent;
      expect(content.subtype).toBe('api_key_check');
      expect(content.message).toBe('API key is valid');
    });

    it('handles missing status field', () => {
      const raw = {
        type: 'system',
        subtype: 'some_event',
        uuid: 'sys-004',
      };

      const result = adaptSDKMessage(raw);
      const content = result[0].content as StatusContent;
      expect(content.subtype).toBe('some_event');
      expect(content.message).toBeUndefined();
    });
  });

  describe('assistant/text messages', () => {
    it('adapts a single text block', () => {
      const raw = {
        type: 'assistant',
        uuid: 'asst-001',
        message: {
          content: [{ type: 'text', text: 'Hello, world!' }],
        },
      };

      const result = adaptSDKMessage(raw);

      expect(result).toHaveLength(1);
      expect(result[0].type).toBe('text');
      expect(result[0].id).toBe('asst-001-text-0');
      const content = result[0].content as TextContent;
      expect(content.text).toBe('Hello, world!');
    });

    it('preserves parentId on assistant messages', () => {
      const raw = {
        type: 'assistant',
        uuid: 'asst-002',
        parent_tool_use_id: 'tool-parent-123',
        message: {
          content: [{ type: 'text', text: 'subagent response' }],
        },
      };

      const result = adaptSDKMessage(raw);
      expect(result[0].parentId).toBe('tool-parent-123');
    });
  });

  describe('assistant/thinking messages', () => {
    it('adapts a thinking block with thinking field', () => {
      const raw = {
        type: 'assistant',
        uuid: 'asst-003',
        message: {
          content: [{ type: 'thinking', thinking: 'Let me consider...', text: 'fallback' }],
        },
      };

      const result = adaptSDKMessage(raw);

      expect(result).toHaveLength(1);
      expect(result[0].type).toBe('thinking');
      expect(result[0].id).toBe('asst-003-think-0');
      const content = result[0].content as ThinkingContent;
      expect(content.text).toBe('Let me consider...');
    });

    it('falls back to text field when thinking is absent', () => {
      const raw = {
        type: 'assistant',
        uuid: 'asst-004',
        message: {
          content: [{ type: 'thinking', text: 'Thinking via text field' }],
        },
      };

      const result = adaptSDKMessage(raw);
      const content = result[0].content as ThinkingContent;
      expect(content.text).toBe('Thinking via text field');
    });
  });

  describe('assistant/tool_use messages', () => {
    it('adapts a tool_use block', () => {
      const raw = {
        type: 'assistant',
        uuid: 'asst-005',
        message: {
          content: [{
            type: 'tool_use',
            id: 'toolu_abc123',
            name: 'Read',
            input: { file_path: '/src/main.ts' },
          }],
        },
      };

      const result = adaptSDKMessage(raw);

      expect(result).toHaveLength(1);
      expect(result[0].type).toBe('tool_call');
      expect(result[0].id).toBe('asst-005-tool-0');
      const content = result[0].content as ToolCallContent;
      expect(content.toolUseId).toBe('toolu_abc123');
      expect(content.name).toBe('Read');
      expect(content.input).toEqual({ file_path: '/src/main.ts' });
    });
  });

  describe('assistant messages with multiple content blocks', () => {
    it('produces one AgentMessage per content block', () => {
      const raw = {
        type: 'assistant',
        uuid: 'asst-multi',
        message: {
          content: [
            { type: 'thinking', thinking: 'Hmm...' },
            { type: 'text', text: 'Here is the answer.' },
            { type: 'tool_use', id: 'toolu_xyz', name: 'Bash', input: { command: 'ls' } },
          ],
        },
      };

      const result = adaptSDKMessage(raw);

      expect(result).toHaveLength(3);
      expect(result[0].type).toBe('thinking');
      expect(result[0].id).toBe('asst-multi-think-0');
      expect(result[1].type).toBe('text');
      expect(result[1].id).toBe('asst-multi-text-1');
      expect(result[2].type).toBe('tool_call');
      expect(result[2].id).toBe('asst-multi-tool-2');
    });

    it('skips unknown content block types silently', () => {
      const raw = {
        type: 'assistant',
        uuid: 'asst-unk-block',
        message: {
          content: [
            { type: 'text', text: 'Hello' },
            { type: 'image', data: 'base64...' },
          ],
        },
      };

      const result = adaptSDKMessage(raw);
      expect(result).toHaveLength(1);
      expect(result[0].type).toBe('text');
    });
  });

  describe('user/tool_result messages', () => {
    it('adapts a tool_result block', () => {
      const raw = {
        type: 'user',
        uuid: 'user-001',
        message: {
          content: [{
            type: 'tool_result',
            tool_use_id: 'toolu_abc123',
            content: 'file contents here',
          }],
        },
      };

      const result = adaptSDKMessage(raw);

      expect(result).toHaveLength(1);
      expect(result[0].type).toBe('tool_result');
      expect(result[0].id).toBe('user-001-result-0');
      const content = result[0].content as ToolResultContent;
      expect(content.toolUseId).toBe('toolu_abc123');
      expect(content.output).toBe('file contents here');
    });

    it('falls back to tool_use_result when block content is missing', () => {
      const raw = {
        type: 'user',
        uuid: 'user-002',
        tool_use_result: { status: 'success', output: 'done' },
        message: {
          content: [{
            type: 'tool_result',
            tool_use_id: 'toolu_def456',
            // no content field
          }],
        },
      };

      const result = adaptSDKMessage(raw);
      const content = result[0].content as ToolResultContent;
      expect(content.output).toEqual({ status: 'success', output: 'done' });
    });

    it('preserves parentId on user messages', () => {
      const raw = {
        type: 'user',
        uuid: 'user-003',
        parent_tool_use_id: 'parent-tool-id',
        message: {
          content: [{
            type: 'tool_result',
            tool_use_id: 'toolu_ghi',
            content: 'ok',
          }],
        },
      };

      const result = adaptSDKMessage(raw);
      expect(result[0].parentId).toBe('parent-tool-id');
    });
  });

  describe('result/cost messages', () => {
    it('adapts a full result message', () => {
      const raw = {
        type: 'result',
        uuid: 'res-001',
        total_cost_usd: 0.0125,
        duration_ms: 4500,
        usage: { input_tokens: 1000, output_tokens: 500 },
        num_turns: 3,
        is_error: false,
        result: 'Task completed successfully.',
      };

      const result = adaptSDKMessage(raw);

      expect(result).toHaveLength(1);
      expect(result[0].type).toBe('cost');
      expect(result[0].id).toBe('res-001');
      const content = result[0].content as CostContent;
      expect(content.totalCostUsd).toBe(0.0125);
      expect(content.durationMs).toBe(4500);
      expect(content.inputTokens).toBe(1000);
      expect(content.outputTokens).toBe(500);
      expect(content.numTurns).toBe(3);
      expect(content.isError).toBe(false);
      expect(content.result).toBe('Task completed successfully.');
      expect(content.errors).toBeUndefined();
    });

    it('defaults numeric fields to 0 when missing', () => {
      const raw = {
        type: 'result',
        uuid: 'res-002',
      };

      const result = adaptSDKMessage(raw);
      const content = result[0].content as CostContent;
      expect(content.totalCostUsd).toBe(0);
      expect(content.durationMs).toBe(0);
      expect(content.inputTokens).toBe(0);
      expect(content.outputTokens).toBe(0);
      expect(content.numTurns).toBe(0);
      expect(content.isError).toBe(false);
    });

    it('includes errors array when present', () => {
      const raw = {
        type: 'result',
        uuid: 'res-003',
        is_error: true,
        errors: ['Rate limit exceeded', 'Retry failed'],
      };

      const result = adaptSDKMessage(raw);
      const content = result[0].content as CostContent;
      expect(content.isError).toBe(true);
      expect(content.errors).toEqual(['Rate limit exceeded', 'Retry failed']);
    });
  });

  describe('edge cases', () => {
    it('returns unknown type for unrecognized message types', () => {
      const raw = {
        type: 'something_new',
        uuid: 'unk-001',
        data: 'arbitrary',
      };

      const result = adaptSDKMessage(raw);

      expect(result).toHaveLength(1);
      expect(result[0].type).toBe('unknown');
      expect(result[0].id).toBe('unk-001');
      expect(result[0].content).toBe(raw);
    });

    it('uses crypto.randomUUID when uuid is missing', () => {
      const raw = {
        type: 'result',
        total_cost_usd: 0.001,
      };

      const result = adaptSDKMessage(raw);
      expect(result[0].id).toBe('fallback-uuid');
    });

    it('returns empty array when assistant message has no message field', () => {
      const raw = {
        type: 'assistant',
        uuid: 'asst-empty',
      };

      const result = adaptSDKMessage(raw);
      expect(result).toHaveLength(0);
    });

    it('returns empty array when assistant message.content is not an array', () => {
      const raw = {
        type: 'assistant',
        uuid: 'asst-bad-content',
        message: { content: 'not-an-array' },
      };

      const result = adaptSDKMessage(raw);
      expect(result).toHaveLength(0);
    });

    it('returns empty array when user message has no message field', () => {
      const raw = {
        type: 'user',
        uuid: 'user-empty',
      };

      const result = adaptSDKMessage(raw);
      expect(result).toHaveLength(0);
    });

    it('returns empty array when user message.content is not an array', () => {
      const raw = {
        type: 'user',
        uuid: 'user-bad',
        message: { content: 'string' },
      };

      const result = adaptSDKMessage(raw);
      expect(result).toHaveLength(0);
    });

    it('ignores non-tool_result blocks in user messages', () => {
      const raw = {
        type: 'user',
        uuid: 'user-text',
        message: {
          content: [
            { type: 'text', text: 'User typed something' },
            { type: 'tool_result', tool_use_id: 'toolu_1', content: 'ok' },
          ],
        },
      };

      const result = adaptSDKMessage(raw);
      expect(result).toHaveLength(1);
      expect(result[0].type).toBe('tool_result');
    });

    it('sets timestamp on every message', () => {
      const before = Date.now();
      const result = adaptSDKMessage({
        type: 'system',
        subtype: 'init',
        uuid: 'ts-test',
        session_id: 's',
        model: 'm',
        cwd: '/',
      });
      const after = Date.now();

      expect(result[0].timestamp).toBeGreaterThanOrEqual(before);
      expect(result[0].timestamp).toBeLessThanOrEqual(after);
    });
  });
});
