<?php

declare(strict_types=1);

namespace Drupal\Tests\ai\Kernel\AiPlatform;

use Drupal\ai\OperationType\Chat\ChatInput;
use Drupal\ai\OperationType\Chat\ChatMessage;
use Drupal\ai\OperationType\Chat\ChatOutput;
use Drupal\ai\OperationType\Chat\StreamedChatMessageIteratorInterface;
use Drupal\ai\PlatformProxy;
use Drupal\KernelTests\KernelTestBase;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;
use Symfony\AI\Platform\Result\DeferredResult;
use Symfony\AI\Platform\Tool\ExecutionReference;
use Symfony\AI\Platform\Tool\Tool;

/**
 * Tests the PlatformProxy class.
 *
 * @coversDefaultClass \Drupal\ai\PlatformProxy
 * @group ai
 */
class PlatformProxyTest extends KernelTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'ai',
    'ai_test',
    'key',
    'file',
    'user',
    'field',
    'system',
    'symfony_http_client',
  ];

  /**
   * The platform proxy under test.
   *
   * @var \Drupal\ai\PlatformProxy
   */
  protected PlatformProxy $proxy;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->installEntitySchema('user');
    $this->installEntitySchema('file');
    $this->installSchema('file', ['file_usage']);
    $this->installConfig(['ai', 'ai_test']);
    $this->installEntitySchema('ai_mock_provider_result');

    $platformConfig = \Drupal::entityTypeManager()
      ->getStorage('ai_platform')
      ->load('echo_test');
    $platform = $platformConfig->getPlatformInstance();
    $converter = \Drupal::service('ai.symfony.input_output_converter');
    $this->proxy = new PlatformProxy($platform, $converter);
  }

  /**
   * Tests invoke() with MessageBag returns DeferredResult.
   *
   * @covers ::invoke
   */
  public function testInvoke(): void {
    $messages = new MessageBag(
      Message::ofUser('What is the capital of France?')
    );

    $result = $this->proxy->invoke('echo-model', $messages);

    $this->assertInstanceOf(DeferredResult::class, $result);
    $this->assertStringContainsString('What is the capital of France?', $result->asText());
  }

  /**
   * Tests invoke() with tools.
   *
   * @covers ::invoke
   */
  public function testInvokeWithTools(): void {
    $messages = new MessageBag(
      Message::ofUser('What is the weather?')
    );

    $tools = [
      new Tool(
        new ExecutionReference(self::class, 'noOp'),
        'get_weather',
        'Get the weather',
        [
          'type' => 'object',
          'properties' => [
            'location' => ['type' => 'string'],
          ],
        ],
      ),
    ];

    $result = $this->proxy->invoke('echo-model', $messages, ['tools' => $tools]);

    $this->assertInstanceOf(DeferredResult::class, $result);
    $toolCalls = $result->asToolCalls();
    $this->assertNotEmpty($toolCalls);
    $this->assertEquals('get_weather', $toolCalls[0]->getName());
  }

  /**
   * Tests BC chat() with ChatInput.
   *
   * @covers ::chat
   */
  public function testChat(): void {
    $input = new ChatInput([
      new ChatMessage('user', 'Hello world'),
    ]);

    $output = $this->proxy->chat($input, 'echo-model');

    $this->assertInstanceOf(ChatOutput::class, $output);
    $normalized = $output->getNormalized();
    $this->assertInstanceOf(ChatMessage::class, $normalized);
    $this->assertEquals('assistant', $normalized->getRole());
    $this->assertStringContainsString('Hello world', $normalized->getText());
  }

  /**
   * Tests BC chat() with string input.
   *
   * @covers ::chat
   */
  public function testChatWithString(): void {
    $output = $this->proxy->chat('Hello from string', 'echo-model');

    $this->assertInstanceOf(ChatOutput::class, $output);
    $this->assertStringContainsString('Hello from string', $output->getNormalized()->getText());
  }

  /**
   * Tests BC chat() with system prompt.
   *
   * @covers ::chat
   */
  public function testChatWithSystemPrompt(): void {
    $input = new ChatInput([
      new ChatMessage('user', 'Hi'),
    ]);
    $input->setSystemPrompt('You are a pirate.');

    $output = $this->proxy->chat($input, 'echo-model');

    $this->assertInstanceOf(ChatOutput::class, $output);
    $this->assertStringContainsString('Hi', $output->getNormalized()->getText());
  }

  /**
   * Tests BC chat() with streaming.
   *
   * @covers ::chat
   */
  public function testChatStreamed(): void {
    $input = new ChatInput([
      new ChatMessage('user', 'Stream test'),
    ]);
    $input->setStreamedOutput(TRUE);

    $output = $this->proxy->chat($input, 'echo-model');

    $this->assertInstanceOf(ChatOutput::class, $output);
    $normalized = $output->getNormalized();
    $this->assertInstanceOf(StreamedChatMessageIteratorInterface::class, $normalized);

    $text = '';
    foreach ($normalized as $chunk) {
      $text .= $chunk->getText();
    }
    $this->assertStringContainsString('Stream test', $text);
  }

  /**
   * Tests that the proxy implements AiProviderInterface.
   *
   * @covers ::getPluginId
   * @covers ::getPluginDefinition
   * @covers ::isUsable
   */
  public function testAiProviderInterface(): void {
    $this->assertEquals('echo', $this->proxy->getPluginId());
    $this->assertEquals('Echo', (string) $this->proxy->getPluginDefinition()['label']);
    $this->assertTrue($this->proxy->isUsable());
  }

  /**
   * No-op method for tool execution reference.
   */
  public static function noOp(): string {
    return '';
  }

}
