<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_stategraph\Unit\DTO;

use Drupal\Tests\UnitTestCase;
use Drupal\flowdrop_orchestration\DTO\Snapshot\Message;

/**
 * Tests the Message DTO.
 *
 * @coversDefaultClass \Drupal\flowdrop_orchestration\DTO\Snapshot\Message
 * @group flowdrop_stategraph
 */
class MessageTest extends UnitTestCase {

  /**
   * Tests constructor with basic values.
   *
   * @covers ::__construct
   */
  public function testConstructor(): void {
    $message = new Message(
      role: "user",
      content: "Hello world",
      metadata: ["key" => "value"],
      timestamp: 1234567890,
    );

    $this->assertEquals("user", $message->role);
    $this->assertEquals("Hello world", $message->content);
    $this->assertEquals(["key" => "value"], $message->metadata);
    $this->assertEquals(1234567890, $message->timestamp);
  }

  /**
   * Tests constructor with default values.
   *
   * @covers ::__construct
   */
  public function testConstructorDefaults(): void {
    $message = new Message("assistant", "Response");

    $this->assertEquals("assistant", $message->role);
    $this->assertEquals("Response", $message->content);
    $this->assertEquals([], $message->metadata);
    $this->assertNull($message->timestamp);
  }

  /**
   * Tests system message factory method.
   *
   * @covers ::system
   */
  public function testSystemFactory(): void {
    $message = Message::system("You are a helpful assistant.");

    $this->assertEquals("system", $message->role);
    $this->assertEquals("You are a helpful assistant.", $message->content);
    $this->assertEquals([], $message->metadata);
    $this->assertNotNull($message->timestamp);
  }

  /**
   * Tests user message factory method.
   *
   * @covers ::user
   */
  public function testUserFactory(): void {
    $message = Message::user("What is the weather?");

    $this->assertEquals("user", $message->role);
    $this->assertEquals("What is the weather?", $message->content);
    $this->assertEquals([], $message->metadata);
    $this->assertNotNull($message->timestamp);
  }

  /**
   * Tests assistant message factory method.
   *
   * @covers ::assistant
   */
  public function testAssistantFactory(): void {
    $message = Message::assistant("Let me check that for you.");

    $this->assertEquals("assistant", $message->role);
    $this->assertEquals("Let me check that for you.", $message->content);
    $this->assertEquals([], $message->metadata);
    $this->assertNotNull($message->timestamp);
  }

  /**
   * Tests assistant message with metadata.
   *
   * @covers ::assistant
   */
  public function testAssistantFactoryWithMetadata(): void {
    $toolCalls = [
      ["name" => "get_weather", "args" => ["location" => "NYC"]],
    ];

    $message = Message::assistant("", ["tool_calls" => $toolCalls]);

    $this->assertEquals("assistant", $message->role);
    $this->assertEquals("", $message->content);
    $this->assertEquals(["tool_calls" => $toolCalls], $message->metadata);
  }

  /**
   * Tests tool message factory method.
   *
   * @covers ::tool
   */
  public function testToolFactory(): void {
    $message = Message::tool("72°F, sunny", "call_123");

    $this->assertEquals("tool", $message->role);
    $this->assertEquals("72°F, sunny", $message->content);
    $this->assertEquals(["tool_call_id" => "call_123"], $message->metadata);
    $this->assertNotNull($message->timestamp);
  }

  /**
   * Tests getToolCalls when present.
   *
   * @covers ::getToolCalls
   */
  public function testGetToolCallsPresent(): void {
    $toolCalls = [
      ["name" => "search", "args" => ["query" => "weather"]],
      ["name" => "calculate", "args" => ["expression" => "2+2"]],
    ];

    $message = new Message("assistant", "", ["tool_calls" => $toolCalls]);

    $this->assertEquals($toolCalls, $message->getToolCalls());
  }

  /**
   * Tests getToolCalls when empty.
   *
   * @covers ::getToolCalls
   */
  public function testGetToolCallsEmpty(): void {
    $message = new Message("assistant", "Just text, no tools");

    $this->assertEquals([], $message->getToolCalls());
  }

  /**
   * Tests hasToolCalls returns true when present.
   *
   * @covers ::hasToolCalls
   */
  public function testHasToolCallsTrue(): void {
    $message = new Message("assistant", "", [
      "tool_calls" => [["name" => "test", "args" => []]],
    ]);

    $this->assertTrue($message->hasToolCalls());
  }

  /**
   * Tests hasToolCalls returns false when empty.
   *
   * @covers ::hasToolCalls
   */
  public function testHasToolCallsFalse(): void {
    $message = new Message("assistant", "No tools");

    $this->assertFalse($message->hasToolCalls());
  }

  /**
   * Tests hasToolCalls returns false for empty array.
   *
   * @covers ::hasToolCalls
   */
  public function testHasToolCallsFalseEmptyArray(): void {
    $message = new Message("assistant", "", ["tool_calls" => []]);

    $this->assertFalse($message->hasToolCalls());
  }

  /**
   * Tests getToolCallId when present.
   *
   * @covers ::getToolCallId
   */
  public function testGetToolCallIdPresent(): void {
    $message = Message::tool("Result data", "call_abc123");

    $this->assertEquals("call_abc123", $message->getToolCallId());
  }

  /**
   * Tests getToolCallId when not present.
   *
   * @covers ::getToolCallId
   */
  public function testGetToolCallIdNotPresent(): void {
    $message = new Message("user", "Hello");

    $this->assertNull($message->getToolCallId());
  }

  /**
   * Tests fromArray creates message correctly.
   *
   * @covers ::fromArray
   */
  public function testFromArray(): void {
    $data = [
      "role" => "assistant",
      "content" => "Test content",
      "metadata" => ["key" => "value"],
      "timestamp" => 1234567890,
    ];

    $message = Message::fromArray($data);

    $this->assertEquals("assistant", $message->role);
    $this->assertEquals("Test content", $message->content);
    $this->assertEquals(["key" => "value"], $message->metadata);
    $this->assertEquals(1234567890, $message->timestamp);
  }

  /**
   * Tests fromArray with minimal data.
   *
   * @covers ::fromArray
   */
  public function testFromArrayMinimal(): void {
    $message = Message::fromArray([]);

    $this->assertEquals("user", $message->role);
    $this->assertEquals("", $message->content);
    $this->assertEquals([], $message->metadata);
    $this->assertNull($message->timestamp);
  }

  /**
   * Tests toArray serialization.
   *
   * @covers ::toArray
   */
  public function testToArray(): void {
    $message = new Message(
      role: "tool",
      content: "Tool result",
      metadata: ["tool_call_id" => "call_xyz"],
      timestamp: 1234567890,
    );

    $array = $message->toArray();

    $this->assertEquals("tool", $array["role"]);
    $this->assertEquals("Tool result", $array["content"]);
    $this->assertEquals(["tool_call_id" => "call_xyz"], $array["metadata"]);
    $this->assertEquals(1234567890, $array["timestamp"]);
  }

  /**
   * Tests JSON serialization.
   *
   * @covers ::jsonSerialize
   */
  public function testJsonSerialize(): void {
    $message = new Message("user", "Hello", [], 1234567890);

    $json = json_encode($message);
    $this->assertIsString($json);

    $decoded = json_decode($json, TRUE);
    $this->assertEquals("user", $decoded["role"]);
    $this->assertEquals("Hello", $decoded["content"]);
    $this->assertEquals(1234567890, $decoded["timestamp"]);
  }

  /**
   * Tests round-trip serialization.
   *
   * @covers ::toArray
   * @covers ::fromArray
   */
  public function testRoundTripSerialization(): void {
    $original = new Message(
      role: "assistant",
      content: "Complex response",
      metadata: [
        "tool_calls" => [
          ["name" => "search", "args" => ["query" => "test"]],
        ],
        "model" => "gpt-4",
      ],
      timestamp: 1234567890,
    );

    $restored = Message::fromArray($original->toArray());

    $this->assertEquals($original->role, $restored->role);
    $this->assertEquals($original->content, $restored->content);
    $this->assertEquals($original->metadata, $restored->metadata);
    $this->assertEquals($original->timestamp, $restored->timestamp);
  }

}
