/**
 * Copyright 2026 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import * as assert from 'assert';
import { MessageData, Part } from 'genkit';
import { ToolDefinition } from 'genkit/model';
import { describe, it } from 'node:test';
import {
  ensureToolIds,
  fromInteraction,
  fromInteractionContent,
  toInteractionContent,
  toInteractionRole,
  toInteractionTool,
} from '../../src/vertexai/interaction-converters.js';
import {
  Content,
  GeminiInteraction,
} from '../../src/vertexai/interaction-types.js';

describe('Interaction Converters', () => {
  describe('ensureToolIds', () => {
    it('should assign IDs to tool requests without refs', () => {
      const messages: MessageData[] = [
        {
          role: 'model',
          content: [
            { toolRequest: { name: 'tool1', input: {} } },
            { toolRequest: { name: 'tool2', input: {} } },
          ],
        },
      ];
      const result = ensureToolIds(messages);
      const req1 = result[0].content[0].toolRequest!;
      const req2 = result[0].content[1].toolRequest!;
      assert.ok(req1.ref && req1.ref.startsWith('genkit-auto-id-'));
      assert.ok(req2.ref && req2.ref.startsWith('genkit-auto-id-'));
      assert.notStrictEqual(req1.ref, req2.ref);
    });

    it('should assign matching IDs to tool responses without refs based on order', () => {
      const messages: MessageData[] = [
        {
          role: 'model',
          content: [
            { toolRequest: { name: 'tool1', input: {} } },
            { toolRequest: { name: 'tool2', input: {} } },
          ],
        },
        {
          role: 'tool',
          content: [
            { toolResponse: { name: 'tool1', output: {} } },
            { toolResponse: { name: 'tool2', output: {} } },
          ],
        },
      ];
      const result = ensureToolIds(messages);
      const req1 = result[0].content[0].toolRequest!;
      const req2 = result[0].content[1].toolRequest!;
      const res1 = result[1].content[0].toolResponse!;
      const res2 = result[1].content[1].toolResponse!;

      assert.ok(req1.ref);
      assert.strictEqual(req1.ref, res1.ref);
      assert.ok(req2.ref);
      assert.strictEqual(req2.ref, res2.ref);
    });

    it('should assign orphan ID to tool response if no matching request', () => {
      const messages: MessageData[] = [
        {
          role: 'tool',
          content: [{ toolResponse: { name: 'tool1', output: {} } }],
        },
      ];
      const result = ensureToolIds(messages);
      const res1 = result[0].content[0].toolResponse!;
      assert.ok(res1.ref && res1.ref.startsWith('genkit-orphan-id-'));
    });

    it('should preserve existing refs', () => {
      const messages: MessageData[] = [
        {
          role: 'model',
          content: [
            { toolRequest: { name: 'tool1', input: {}, ref: 'existing-id' } },
          ],
        },
      ];
      const result = ensureToolIds(messages);
      const req1 = result[0].content[0].toolRequest!;
      assert.strictEqual(req1.ref, 'existing-id');
    });
  });

  describe('toInteractionRole', () => {
    it('should convert user role', () => {
      assert.strictEqual(toInteractionRole('user'), 'user');
    });
    it('should convert model role', () => {
      assert.strictEqual(toInteractionRole('model'), 'model');
    });
    it('should convert tool role to user', () => {
      assert.strictEqual(toInteractionRole('tool'), 'user');
    });
    it('should throw for system role', () => {
      assert.throws(
        () => toInteractionRole('system'),
        /System role should be handled as system_instruction/
      );
    });
  });

  describe('toInteractionTool', () => {
    it('should convert ToolDefinition to InteractionTool', () => {
      const tool: ToolDefinition = {
        name: 'myFunc',
        description: 'desc',
        inputSchema: {
          type: 'object',
          properties: { arg: { type: 'string' } },
        },
      };
      const result = toInteractionTool(tool);
      assert.deepStrictEqual(result, {
        type: 'function',
        name: 'myFunc',
        description: 'desc',
        parameters: {
          type: 'object',
          properties: { arg: { type: 'string' } },
        },
      });
    });
  });

  describe('toInteractionContent', () => {
    it('should convert TextPart', () => {
      const part: Part = { text: 'Hello' };
      const result = toInteractionContent(part);
      assert.deepStrictEqual(result, { type: 'text', text: 'Hello' });
    });

    it('should convert MediaPart (image data)', () => {
      const part: Part = {
        media: {
          url: 'data:image/png;base64,DATA',
          contentType: 'image/png',
        },
      };
      const result = toInteractionContent(part);
      assert.deepStrictEqual(result, {
        type: 'image',
        data: 'DATA',
        mime_type: 'image/png',
      });
    });

    it('should convert MediaPart (image uri)', () => {
      const part: Part = {
        media: {
          url: 'gs://bucket/image.png',
          contentType: 'image/png',
        },
      };
      const result = toInteractionContent(part);
      assert.deepStrictEqual(result, {
        type: 'image',
        uri: 'gs://bucket/image.png',
        mime_type: 'image/png',
      });
    });

    it('should convert MediaPart (audio)', () => {
      const part: Part = {
        media: {
          url: 'data:audio/mp3;base64,DATA',
          contentType: 'audio/mp3',
        },
      };
      const result = toInteractionContent(part);
      assert.deepStrictEqual(result, {
        type: 'audio',
        data: 'DATA',
        mime_type: 'audio/mp3',
      });
    });

    it('should convert MediaPart (document)', () => {
      const part: Part = {
        media: {
          url: 'gs://bucket/doc.pdf',
          contentType: 'application/pdf',
        },
      };
      const result = toInteractionContent(part);
      assert.deepStrictEqual(result, {
        type: 'document',
        uri: 'gs://bucket/doc.pdf',
        mime_type: 'application/pdf',
      });
    });

    it('should convert ToolRequestPart', () => {
      const part: Part = {
        toolRequest: {
          name: 'func',
          input: { a: 1 },
          ref: 'ref1',
        },
      };
      const result = toInteractionContent(part);
      assert.deepStrictEqual(result, {
        type: 'function_call',
        name: 'func',
        arguments: { a: 1 },
        id: 'ref1',
      });
    });

    it('should convert ToolResponsePart', () => {
      const part: Part = {
        toolResponse: {
          name: 'func',
          output: { result: 'ok' },
          ref: 'ref1',
        },
      };
      const result = toInteractionContent(part);
      assert.deepStrictEqual(result, {
        type: 'function_result',
        name: 'func',
        result: { result: 'ok' },
        call_id: 'ref1',
      });
    });
  });

  describe('fromInteractionContent', () => {
    it('should convert TextContent', () => {
      const content: Content = {
        type: 'text',
        text: 'Hello world',
        annotations: [{ start_index: 0, end_index: 5, source: 'source' }],
      };
      const result = fromInteractionContent(content);
      assert.deepStrictEqual(result, {
        text: 'Hello world',
        metadata: {
          annotations: [{ start_index: 0, end_index: 5, source: 'source' }],
        },
      });
    });

    it('should convert ImageContent with data', () => {
      const content: Content = {
        type: 'image',
        data: 'BASE64DATA',
        mime_type: 'image/png',
      };
      const result = fromInteractionContent(content);
      assert.deepStrictEqual(result, {
        media: {
          url: 'data:image/png;base64,BASE64DATA',
          contentType: 'image/png',
        },
      });
    });

    it('should convert ImageContent with uri', () => {
      const content: Content = {
        type: 'image',
        uri: 'gs://bucket/image.png',
        mime_type: 'image/png',
      };
      const result = fromInteractionContent(content);
      assert.deepStrictEqual(result, {
        media: {
          url: 'gs://bucket/image.png',
          contentType: 'image/png',
        },
      });
    });

    it('should convert ImageContent with resolution', () => {
      const content: Content = {
        type: 'image',
        uri: 'gs://bucket/image.png',
        mime_type: 'image/png',
        resolution: 'high',
      };
      const result = fromInteractionContent(content);
      assert.deepStrictEqual(result, {
        media: {
          url: 'gs://bucket/image.png',
          contentType: 'image/png',
        },
        metadata: { resolution: 'high' },
      });
    });

    it('should convert ThoughtContent', () => {
      const content: Content = {
        type: 'thought',
        signature: 'SIG',
        summary: [{ type: 'text', text: 'Thinking...' }],
      };
      const result = fromInteractionContent(content);
      assert.deepStrictEqual(result, {
        reasoning: 'Thinking...',
        metadata: {
          thoughtSignature: 'SIG',
        },
        custom: {
          thought: content,
        },
      });
    });

    it('should convert ThoughtContent with mixed summary', () => {
      const content: Content = {
        type: 'thought',
        signature: 'SIG',
        summary: [
          { type: 'text', text: 'Thinking about...' },
          { type: 'image', uri: 'gs://image.png' },
          { type: 'text', text: '...this image.' },
        ],
      };
      const result = fromInteractionContent(content);
      assert.deepStrictEqual(result, {
        reasoning: 'Thinking about...\n[Image]\n...this image.',
        metadata: {
          thoughtSignature: 'SIG',
        },
        custom: {
          thought: content,
        },
      });
    });

    it('should convert FunctionCallContent', () => {
      const content: Content = {
        type: 'function_call',
        name: 'get_weather',
        arguments: { location: 'Paris' },
        id: 'call_123',
      };
      const result = fromInteractionContent(content);
      assert.deepStrictEqual(result, {
        toolRequest: {
          name: 'get_weather',
          input: { location: 'Paris' },
          ref: 'call_123',
        },
      });
    });

    it('should convert FunctionResultContent', () => {
      const content: Content = {
        type: 'function_result',
        name: 'get_weather',
        result: { temperature: 20 },
        call_id: 'call_123',
      };
      const result = fromInteractionContent(content);
      assert.deepStrictEqual(result, {
        toolResponse: {
          name: 'get_weather',
          output: { temperature: 20 },
          ref: 'call_123',
        },
      });
    });
  });

  describe('fromInteraction', () => {
    it('should convert cancelled interaction', () => {
      const interaction: GeminiInteraction = {
        id: '123',
        status: 'cancelled',
      };
      const result = fromInteraction(interaction);
      assert.strictEqual(result.done, true);
      assert.strictEqual(result.output?.finishReason, 'aborted');
      assert.strictEqual(result.output?.finishMessage, 'Operation cancelled');
      assert.deepStrictEqual(result.output?.message?.content, [
        { text: 'Operation cancelled.' },
      ]);
    });
  });
});
