<?php

declare(strict_types=1);

namespace Drupal\Tests\ai\Unit\Symfony;

use Drupal\ai\Dto\StructuredOutputSchema;
use Drupal\ai\OperationType\Chat\ChatInput;
use Drupal\ai\OperationType\Chat\ChatMessage;
use Drupal\ai\OperationType\Chat\Tools\ToolsFunctionInput;
use Drupal\ai\OperationType\Chat\Tools\ToolsInput;
use Drupal\ai\OperationType\Chat\Tools\ToolsPropertyInput;
use Drupal\ai\Symfony\InputOutputConverter;
use PHPUnit\Framework\TestCase;
use Symfony\AI\Platform\Message\AssistantMessage;
use Symfony\AI\Platform\Message\SystemMessage;
use Symfony\AI\Platform\Message\UserMessage;
use Symfony\AI\Platform\Tool\Tool;

/**
 * Tests the InputOutputConverter service.
 *
 * @group ai
 * @coversDefaultClass \Drupal\ai\Symfony\InputOutputConverter
 */
class InputOutputConverterTest extends TestCase {

  /**
   * The converter under test.
   *
   * @var \Drupal\ai\Symfony\InputOutputConverter
   */
  protected InputOutputConverter $converter;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->converter = new InputOutputConverter();
  }

  /**
   * Tests basic role mapping to MessageBag.
   *
   * @covers ::toMessageBag
   */
  public function testToMessageBag(): void {
    $input = new ChatInput([
      new ChatMessage('user', 'Hello'),
      new ChatMessage('assistant', 'Hi there'),
    ]);
    $input->setSystemPrompt('You are helpful.');

    $messageBag = $this->converter->toMessageBag($input);

    $messages = $messageBag->getMessages();
    // System prompt + user + assistant = 3.
    $this->assertCount(3, $messages);
    $this->assertInstanceOf(SystemMessage::class, $messages[0]);
    $this->assertEquals('You are helpful.', $messages[0]->getContent());
    $this->assertInstanceOf(UserMessage::class, $messages[1]);
    $this->assertInstanceOf(AssistantMessage::class, $messages[2]);
    $this->assertEquals('Hi there', $messages[2]->getContent());
  }

  /**
   * Tests that system messages in the message list are converted.
   *
   * @covers ::toMessageBag
   */
  public function testToMessageBagWithSystemRole(): void {
    $input = new ChatInput([
      new ChatMessage('system', 'Be concise.'),
      new ChatMessage('user', 'Hello'),
    ]);

    $messageBag = $this->converter->toMessageBag($input);

    $messages = $messageBag->getMessages();
    $this->assertCount(2, $messages);
    $this->assertInstanceOf(SystemMessage::class, $messages[0]);
    $this->assertEquals('Be concise.', $messages[0]->getContent());
    $this->assertInstanceOf(UserMessage::class, $messages[1]);
  }

  /**
   * Tests building options from ChatInput.
   *
   * @covers ::buildOptions
   */
  public function testBuildOptions(): void {
    $input = new ChatInput([new ChatMessage('user', 'Test')]);
    $input->setStreamedOutput(TRUE);
    $structuredSchema = new StructuredOutputSchema(
      name: 'test_schema',
      description: 'Test schema',
      strict: TRUE,
      json_schema: [
        'properties' => [
          'temperature' => ['type' => 'number'],
          'location' => ['type' => 'string'],
        ],
      ],
    );
    $input->setChatStructuredJsonSchema($structuredSchema);

    $options = $this->converter->buildOptions($input);

    $this->assertTrue($options['stream']);
    $this->assertEquals('json_schema', $options['response_format']['type']);
    $this->assertTrue($options['response_format']['json_schema']['strict']);
    $this->assertEquals('test_schema', $options['response_format']['json_schema']['name']);
  }

  /**
   * Tests that options are empty when nothing is set.
   *
   * @covers ::buildOptions
   */
  public function testBuildOptionsEmpty(): void {
    $input = new ChatInput([new ChatMessage('user', 'Test')]);

    $options = $this->converter->buildOptions($input);

    $this->assertEmpty($options);
  }

  /**
   * Tests converting tool definitions.
   *
   * @covers ::convertToolDefinitions
   */
  public function testConvertToolDefinitions(): void {
    $property = new ToolsPropertyInput('location');
    $property->setType('string');
    $property->setDescription('The city name');
    $property->setRequired(TRUE);

    $function = new ToolsFunctionInput('get_weather');
    $function->setDescription('Get weather for a city');
    $function->setProperties(['location' => $property]);

    $toolsInput = new ToolsInput([$function]);

    $symfonyTools = $this->converter->convertToolDefinitions($toolsInput);

    $this->assertCount(1, $symfonyTools);
    $this->assertInstanceOf(Tool::class, $symfonyTools[0]);
    $this->assertEquals('get_weather', $symfonyTools[0]->getName());
    $this->assertEquals('Get weather for a city', $symfonyTools[0]->getDescription());

    $params = $symfonyTools[0]->getParameters();
    $this->assertEquals('object', $params['type']);
    $this->assertArrayHasKey('location', $params['properties']);
    $this->assertEquals(['location'], $params['required']);
  }

  /**
   * Tests that tools are included in options when set.
   *
   * @covers ::buildOptions
   */
  public function testBuildOptionsWithTools(): void {
    $function = new ToolsFunctionInput('test_func');
    $function->setDescription('A test function');
    $toolsInput = new ToolsInput([$function]);

    $input = new ChatInput([new ChatMessage('user', 'Test')]);
    $input->setChatTools($toolsInput);

    $options = $this->converter->buildOptions($input);

    $this->assertArrayHasKey('tools', $options);
    $this->assertCount(1, $options['tools']);
    $this->assertInstanceOf(Tool::class, $options['tools'][0]);
  }

  /**
   * Tests converting user message without text (empty fallback).
   *
   * @covers ::toMessageBag
   */
  public function testToMessageBagUserEmptyText(): void {
    $input = new ChatInput([
      new ChatMessage('user', ''),
    ]);

    $messageBag = $this->converter->toMessageBag($input);

    $messages = $messageBag->getMessages();
    $this->assertCount(1, $messages);
    $this->assertInstanceOf(UserMessage::class, $messages[0]);
  }

}
