<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop\Unit\DTO;

use Drupal\Tests\UnitTestCase;
use Drupal\flowdrop\DTO\Output;
use Drupal\flowdrop\DTO\OutputInterface;

/**
 * Tests the Output DTO.
 *
 * @coversDefaultClass \Drupal\flowdrop\DTO\Output
 * @group flowdrop
 */
class OutputTest extends UnitTestCase {

  /**
   * Tests that Output implements OutputInterface.
   */
  public function testImplementsInterface(): void {
    $output = new Output();
    $this->assertInstanceOf(OutputInterface::class, $output);
  }

  /**
   * @covers ::setOutput
   */
  public function testSetOutput(): void {
    $output = new Output();
    $result = $output->setOutput("result", "test_value");
    $this->assertSame($output, $result);
    $this->assertEquals("test_value", $output->get("result"));
  }

  /**
   * @covers ::setOutput
   */
  public function testSetOutputWithValidator(): void {
    $output = new Output();
    $output->setOutput("num", 42, fn($v) => is_int($v));
    $this->assertEquals(42, $output->get("num"));
  }

  /**
   * @covers ::setOutput
   */
  public function testSetOutputWithFailingValidator(): void {
    $output = new Output();
    $this->expectException(\InvalidArgumentException::class);
    $this->expectExceptionMessage("Invalid output value for key: num");
    $output->setOutput("num", "not_int", fn($v) => is_int($v));
  }

  /**
   * @covers ::addMetadata
   * @covers ::getMetadata
   */
  public function testMetadata(): void {
    $output = new Output();
    $output->addMetadata("source", "test");
    $this->assertEquals("test", $output->getMetadata("source"));
    $this->assertNull($output->getMetadata("missing"));
    $this->assertEquals("default", $output->getMetadata("missing", "default"));
  }

  /**
   * @covers ::setStatus
   * @covers ::getStatus
   */
  public function testStatus(): void {
    $output = new Output();
    $this->assertEquals("unknown", $output->getStatus());
    $output->setStatus("success", "All good");
    $this->assertEquals("success", $output->getStatus());
  }

  /**
   * @covers ::isSuccess
   */
  public function testIsSuccess(): void {
    $output = new Output();
    $this->assertFalse($output->isSuccess());
    $output->setSuccess();
    $this->assertTrue($output->isSuccess());
  }

  /**
   * @covers ::isError
   * @covers ::setError
   * @covers ::getErrorMessage
   * @covers ::getErrorCode
   */
  public function testError(): void {
    $output = new Output();
    $this->assertFalse($output->isError());
    $output->setError("Something failed", "ERR_001");
    $this->assertTrue($output->isError());
    $this->assertEquals("Something failed", $output->getErrorMessage());
    $this->assertEquals("ERR_001", $output->getErrorCode());
  }

  /**
   * @covers ::setSuccess
   * @covers ::getSuccessMessage
   */
  public function testSuccess(): void {
    $output = new Output();
    $output->setSuccess("Done!");
    $this->assertTrue($output->isSuccess());
    $this->assertEquals("Done!", $output->getSuccessMessage());
  }

  /**
   * @covers ::getSuccessMessage
   */
  public function testGetSuccessMessageReturnsNullForNonSuccess(): void {
    $output = new Output();
    $output->setError("fail");
    $this->assertNull($output->getSuccessMessage());
  }

  /**
   * @covers ::addExecutionTime
   * @covers ::getExecutionTime
   */
  public function testExecutionTime(): void {
    $output = new Output();
    $this->assertNull($output->getExecutionTime());
    $output->addExecutionTime(1.23);
    $this->assertEquals(1.23, $output->getExecutionTime());
  }

  /**
   * @covers ::getAllOutput
   * @covers ::hasOutputData
   * @covers ::getOutputCount
   */
  public function testOutputDataMethods(): void {
    $output = new Output();
    $this->assertFalse($output->hasOutputData());
    $this->assertEquals(0, $output->getOutputCount());

    $output->set("a", 1);
    $output->set("b", 2);
    $this->assertTrue($output->hasOutputData());
    $this->assertEquals(2, $output->getOutputCount());
    $this->assertEquals(["a" => 1, "b" => 2], $output->getAllOutput());
  }

  /**
   * @covers ::mergeOutput
   */
  public function testMergeOutput(): void {
    $output1 = new Output(["a" => 1]);
    $output2 = new Output(["b" => 2]);
    $result = $output1->mergeOutput($output2);
    $this->assertSame($output1, $result);
    $this->assertEquals(["a" => 1, "b" => 2], $output1->toArray());
  }

}
