<?php

namespace Drupal\Tests\acquia_contenthub_publisher\Unit;

use Acquia\ContentHubClient\ContentHubClient;
use Drupal\acquia_contenthub\Client\ClientFactory;
use Drupal\acquia_contenthub_publisher\ContentHubReoriginator;
use Drupal\Tests\acquia_contenthub\Unit\Helpers\LoggerMock;
use Drupal\Tests\acquia_contenthub_publisher\Traits\MockTrait;
use Drupal\Tests\UnitTestCase;
use Prophecy\PhpUnit\ProphecyTrait;

/**
 * ContentHubReoriginatorTest tests reoriginator service.
 *
 * @coversDefaultClass \Drupal\acquia_contenthub_publisher\ContentHubReoriginator
 */
class ContentHubReoriginatorTest extends UnitTestCase {

  use ProphecyTrait;
  use MockTrait;

  /**
   * Content hub client.
   *
   * @var \Acquia\ContentHubClient\ContentHubClient|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $client;

  /**
   * CH reoriginator.
   *
   * @var \Drupal\acquia_contenthub_publisher\ContentHubReoriginator
   */
  protected $reoriginator;

  /**
   * Logger channel mock.
   *
   * @var \Drupal\Tests\acquia_contenthub\Unit\Helpers\LoggerMock
   */
  protected $loggerMock;

  /**
   * Target origin.
   */
  public const TARGET = 'b729de7e-913d-4dc1-aacf-c40cd5fef034';

  /**
   * {@inheritDoc}
   *
   * @throws \Exception
   */
  public function setUp(): void {
    parent::setUp();
    $this->client = $this->getMockBuilder(ContentHubClient::class)
      ->disableOriginalConstructor()
      ->disableOriginalClone()
      ->disableArgumentCloning()
      ->disallowMockingUnknownTypes()
      ->onlyMethods(
        [
          'reoriginateEntity',
          'reoriginateAllEntities',
          'cacheRemoteSettings',
          'getClients',
          'get',
          'post',
          'delete',
          'put',
        ])
      ->getMock();
    $client_factory = $this->prophesize(ClientFactory::class);
    $client_factory
      ->getClient()
      ->shouldBeCalled()
      ->willReturn($this->client);

    $this->loggerMock = new LoggerMock();
    $this->reoriginator = new ContentHubReoriginator($client_factory->reveal(), $this->loggerMock);
  }

  /**
   * @covers ::reoriginateEntities
   *
   * @throws \Exception
   */
  public function testReoriginateEntities(): void {
    $entities = [
      '621a4715-9ef5-4942-538c-cc1f091de756',
      '621a4715-9ef5-4942-538c-cc1f091de778',
      '621a4715-9ef5-4942-538c-cc1f091de790',
    ];
    $parameters = array_map(static function ($entity) {
      return [$entity, self::TARGET];
    }, $entities);
    $this
      ->client
      ->method('reoriginateEntity')
      ->with(...self::consecutiveCalls(...$parameters));

    $this->assertEmpty($this->loggerMock->getInfoMessages());
    $this->reoriginator->reoriginateEntities($entities, self::TARGET);
    $message = sprintf('Entities %s have been reoriginated to target %s', implode(', ', $entities), self::TARGET);
    $this->assertSame($message, $this->loggerMock->getInfoMessages()[0]);
  }

  /**
   * @covers ::reoriginateAllEntities
   *
   * @dataProvider dataProvider
   *
   * @throws \Exception
   */
  public function testReoriginateAllEntities(array $sources, array $existing_origins, bool $delete_created_origins, array $log_messages): void {
    $clients = array_map(static function ($origin) {
      return [
        'name' => $origin,
        'uuid' => $origin,
      ];
    }, $existing_origins);
    $missing_clients = array_diff($sources, $existing_origins);

    $missing_clients_parameters_for_post = array_map(static function ($missing_client) {
      return [
        'register',
        [
          'body' => json_encode(
            ['name' => $missing_client, 'originUUID' => $missing_client]
          ),
        ],
      ];
    }, $missing_clients);
    if ($delete_created_origins) {
      $missing_clients_parameters_for_delete = array_map(static function ($missing_client) {
        return ["settings/client/uuid/$missing_client"];
      }, $missing_clients);
      $this
        ->client
        ->method('delete')
        ->with(...self::consecutiveCalls(...$missing_clients_parameters_for_delete));
      $this
        ->client
        ->method('cacheRemoteSettings')
        ->with(...self::consecutiveCalls([FALSE], [FALSE]));
    }
    $this
      ->client
      ->method('post')
      ->with(...self::consecutiveCalls(...$missing_clients_parameters_for_post));

    $reorigination_parameters = array_map(static function ($source) {
      return [$source, self::TARGET];
    }, $sources);
    $this
      ->client
      ->method('reoriginateEntity')
      ->with(...self::consecutiveCalls(...$reorigination_parameters));

    $this
      ->client
      ->method('getClients')
      ->willReturn($clients);

    $this->reoriginator->reoriginateAllEntities(self::TARGET, $delete_created_origins, ...$sources);
    $this->assertEqualsCanonicalizing($this->loggerMock->getInfoMessages(), $log_messages);
  }

  /**
   * Data provider for testReoriginateAllEntities.
   *
   * @return array[]
   *   Data provider array.
   */
  public static function dataProvider(): array {
    $sources = [
      '621a4715-9ef5-4942-538c-cc1f091de756',
      '621a4715-9ef5-4942-538c-cc1f091de778',
      '621a4715-9ef5-4942-538c-cc1f091de790',
    ];
    return [
      [
        // Source origins.
        $sources,
        // Source origins that exist in CH.
        $sources,
        // Delete created origins.
        TRUE,
        // Log messages.
        ['All entities with these sources ' . implode(', ', $sources) . ' have been reoriginated to ' . self::TARGET . ' origin.'],
      ],
      [
        // Source origins.
        $sources,
        // Source origins that exist in CH.
        array_slice($sources, 1),
        // Delete created origins.
        FALSE,
        // Log messages.
        [
          sprintf(
            'All entities with these sources %s have been reoriginated to %s origin.',
            implode(', ', $sources),
            self::TARGET
          ),
          sprintf(
            'These source origins %s didn\'t exist so were created for reorigination.',
            $sources[0]
          ),
        ],
      ],
      [
        // Source origins.
        $sources,
        // Source origins that exist in CH.
        array_slice($sources, 1),
        // Delete created origins.
        TRUE,
        // Log messages.
        [
          sprintf(
            'All entities with these sources %s have been reoriginated to %s origin.',
            implode(', ', $sources),
            self::TARGET
          ),
          sprintf(
            'These source origins %s didn\'t exist so were created for reorigination.',
            $sources[0]
          ),
          sprintf(
            'These created origins %s have been deleted after reorigination.',
            $sources[0]
          ),
        ],
      ],
    ];
  }

}
