<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_memory\Unit\Entity;

use Drupal\Component\Serialization\Json;
use Drupal\flowdrop_memory\Entity\MemoryRecord;
use Drupal\Tests\UnitTestCase;

/**
 * Tests the MemoryRecord entity methods.
 *
 * Uses a partial mock to test accessor/mutator logic without bootstrapping
 * the full entity system.
 *
 * @coversDefaultClass \Drupal\flowdrop_memory\Entity\MemoryRecord
 * @group flowdrop_memory
 */
class MemoryRecordTest extends UnitTestCase {

  /**
   * The entity under test.
   */
  private MemoryRecord $entity;

  /**
   * Field values storage for the mock entity.
   *
   * @var array<string, mixed>
   */
  private array $fieldValues = [];

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();

    // Build a test entity using a partial mock so we can control field values
    // without bootstrapping the Drupal entity system.
    $this->entity = $this->getMockBuilder(MemoryRecord::class)
      ->disableOriginalConstructor()
      ->onlyMethods(['get', 'set'])
      ->getMock();

    $this->fieldValues = [
      'scope' => 'pipeline',
      'scope_id' => 'p-123',
      'memory_key' => 'counter',
      'value' => Json::encode(['count' => 42]),
      'expires' => NULL,
    ];

    $this->entity->method('get')
      ->willReturnCallback(function (string $field) {
        $value = $this->fieldValues[$field] ?? NULL;
        return (object) ['value' => $value];
      });

    $this->entity->method('set')
      ->willReturnCallback(function (string $field, mixed $value) {
        $this->fieldValues[$field] = $value;
        return $this->entity;
      });
  }

  /**
   * @covers ::getScope
   */
  public function testGetScope(): void {
    $this->assertSame('pipeline', $this->entity->getScope());
  }

  /**
   * @covers ::getScopeId
   */
  public function testGetScopeId(): void {
    $this->assertSame('p-123', $this->entity->getScopeId());
  }

  /**
   * @covers ::getScopeId
   */
  public function testGetScopeIdReturnsEmptyStringWhenNull(): void {
    $this->fieldValues['scope_id'] = NULL;
    $this->assertSame('', $this->entity->getScopeId());
  }

  /**
   * @covers ::getMemoryKey
   */
  public function testGetMemoryKey(): void {
    $this->assertSame('counter', $this->entity->getMemoryKey());
  }

  /**
   * @covers ::getValue
   */
  public function testGetValueDecodesJson(): void {
    $this->assertSame(['count' => 42], $this->entity->getValue());
  }

  /**
   * @covers ::getValue
   */
  public function testGetValueReturnsNullWhenEmpty(): void {
    $this->fieldValues['value'] = '';
    $this->assertNull($this->entity->getValue());
  }

  /**
   * @covers ::getValue
   */
  public function testGetValueReturnsNullWhenFieldNull(): void {
    $this->fieldValues['value'] = NULL;
    $this->assertNull($this->entity->getValue());
  }

  /**
   * @covers ::getValue
   */
  public function testGetValueHandlesStringValue(): void {
    $this->fieldValues['value'] = Json::encode('hello');
    $this->assertSame('hello', $this->entity->getValue());
  }

  /**
   * @covers ::getValue
   */
  public function testGetValueHandlesIntegerValue(): void {
    $this->fieldValues['value'] = Json::encode(123);
    $this->assertSame(123, $this->entity->getValue());
  }

  /**
   * @covers ::getValue
   */
  public function testGetValueHandlesBooleanValue(): void {
    $this->fieldValues['value'] = Json::encode(TRUE);
    $this->assertTrue($this->entity->getValue());
  }

  /**
   * @covers ::getValue
   */
  public function testGetValueHandlesNullJsonValue(): void {
    $this->fieldValues['value'] = Json::encode(NULL);
    $this->assertNull($this->entity->getValue());
  }

  /**
   * @covers ::setValue
   */
  public function testSetValueEncodesJson(): void {
    $result = $this->entity->setValue(['new' => 'data']);
    $this->assertSame(Json::encode(['new' => 'data']), $this->fieldValues['value']);
    $this->assertSame($this->entity, $result);
  }

  /**
   * @covers ::setValue
   */
  public function testSetValueEncodesString(): void {
    $this->entity->setValue('simple');
    $this->assertSame('"simple"', $this->fieldValues['value']);
  }

  /**
   * @covers ::setValue
   */
  public function testSetValueEncodesNull(): void {
    $this->entity->setValue(NULL);
    $this->assertSame('null', $this->fieldValues['value']);
  }

  /**
   * @covers ::getExpiry
   */
  public function testGetExpiryReturnsNullWhenNotSet(): void {
    $this->assertNull($this->entity->getExpiry());
  }

  /**
   * @covers ::getExpiry
   */
  public function testGetExpiryReturnsTimestamp(): void {
    $this->fieldValues['expires'] = 1700000000;
    $this->assertSame(1700000000, $this->entity->getExpiry());
  }

  /**
   * @covers ::getExpiry
   */
  public function testGetExpiryCastsStringToInt(): void {
    $this->fieldValues['expires'] = '1700000000';
    $this->assertSame(1700000000, $this->entity->getExpiry());
  }

  /**
   * @covers ::setExpiry
   */
  public function testSetExpiryStoresTimestamp(): void {
    $result = $this->entity->setExpiry(1700000000);
    $this->assertSame(1700000000, $this->fieldValues['expires']);
    $this->assertSame($this->entity, $result);
  }

  /**
   * @covers ::setExpiry
   */
  public function testSetExpiryStoresNull(): void {
    $this->fieldValues['expires'] = 1700000000;
    $this->entity->setExpiry(NULL);
    $this->assertNull($this->fieldValues['expires']);
  }

  /**
   * @covers ::isExpired
   */
  public function testIsExpiredReturnsFalseWhenNoExpiry(): void {
    $this->assertFalse($this->entity->isExpired());
  }

  /**
   * @covers ::isExpired
   */
  public function testIsExpiredReturnsTrueWhenInPast(): void {
    $this->fieldValues['expires'] = time() - 100;
    $this->assertTrue($this->entity->isExpired());
  }

  /**
   * @covers ::isExpired
   */
  public function testIsExpiredReturnsFalseWhenInFuture(): void {
    $this->fieldValues['expires'] = time() + 3600;
    $this->assertFalse($this->entity->isExpired());
  }

  /**
   * @covers ::isExpired
   */
  public function testIsExpiredReturnsFalseWhenExactlyNow(): void {
    $this->fieldValues['expires'] = time();
    $this->assertFalse($this->entity->isExpired());
  }

}
