<?php

declare(strict_types=1);

namespace Drupal\languagewire_translation_provider\Test\Database\Mapper;

use Drupal\languagewire_translation_provider\Adapter\TMGMT\JobRepositoryInterface;
use Drupal\languagewire_translation_provider\Adapter\TMGMT\TmgmtJobInterface;
use Drupal\languagewire_translation_provider\Database\DocumentRepositoryInterface;
use Drupal\languagewire_translation_provider\Database\Mapper\ProjectMapper;
use Drupal\languagewire_translation_provider\Domain\Document;
use Drupal\languagewire_translation_provider\Domain\Project;
use Drupal\languagewire_translation_provider\Domain\ProjectStatus;
use Drupal\languagewire_translation_provider\Test\Builder\Domain\DocumentBuilder;
use Drupal\languagewire_translation_provider\Test\Builder\TmgmtJobBuilder;
use Laminas\Diactoros\Uri;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;

/**
 * Project Mapper Test .
 */
class ProjectMapperTest extends TestCase {
  use \Prophecy\PhpUnit\ProphecyTrait;

  /**
   * Tmgmt Job Repository .
   *
   * @var \Prophecy\Prophecy\ObjectProphecy
   */
  private $tmgmtJobRepository;

  /**
   * Document Repository .
   *
   * @var \Prophecy\Prophecy\ObjectProphecy
   */
  private $documentRepository;

  /**
   * Set Up .
   */
  protected function setUp(): void {
    $this->tmgmtJobRepository = $this->prophesize(JobRepositoryInterface::class);
    $this->documentRepository = $this->prophesize(DocumentRepositoryInterface::class);
  }

  /**
   * When Database Entity Properties Not Null Then Maps To Entity .
   *
   * @test
   */
  public function whenDatabaseEntityHasAllPropertiesNotNullThenMapsItToProjectDomainEntity(): void {
    $projectId = 1;
    $tmgmtJobId = 10;
    $tmgmtJob = $this->jobRepositoryReturnsJobById($tmgmtJobId);
    $documentId = 99;
    $document = $this->documentRepositoryReturnsAllDocumentsByProjectId($documentId, $projectId);
    $dbItem = [
      'correlation_id' => 'correlationID',
      'tmgmt_job_id' => $tmgmtJobId,
      'status' => 'a status',
      'platform_id' => '345',
      'platform_url' => 'https://example.com/project/345',
      'pid' => $projectId,
      'bundle_id' => NULL,
    ];
    $sut = $this->projectMapper();
    $result = $sut->toProject($dbItem);
    $this->assertSame($projectId, $result->getId());
    $this->assertSame($tmgmtJob, $result->getTmgmtJob());
    $this->assertSame('correlationID', $result->getCorrelationId());
    $this->assertEquals(new ProjectStatus('a status'), $result->getStatus());
    $this->assertSame([$document], $result->getDocuments());
    $this->assertSame(345, $result->getPlatformId());
    $this->assertEquals(new Uri('https://example.com/project/345'), $result->getPlatformUrl());
  }

  /**
   * When Database Entity Optional Properties Null Then Maps To Entity .
   *
   * @test
   */
  public function whenDatabaseEntityHasOptionalPropertiesAsNullThenMapsItToProjectDomainEntity(): void {
    $tmgmtJobId = 10;
    $tmgmtJob = $this->jobRepositoryReturnsJobById($tmgmtJobId);
    $this->documentRepository->getAllByProjectId(Argument::any())->shouldNotBeCalled();
    $dbItem = [
      'correlation_id' => 'correlationID',
      'tmgmt_job_id' => $tmgmtJobId,
      'status' => 'a status',
      'bundle_id' => NULL,
    ];
    $sut = $this->projectMapper();
    $result = $sut->toProject($dbItem);
    $this->assertNull($result->getId());
    $this->assertSame($tmgmtJob, $result->getTmgmtJob());
    $this->assertSame('correlationID', $result->getCorrelationId());
    $this->assertEquals(new ProjectStatus('a status'), $result->getStatus());
    $this->assertEmpty($result->getDocuments());
    $this->assertSame(0, $result->getPlatformId());
    $this->assertEquals(new Uri(''), $result->getPlatformUrl());
  }

  /**
   * Toproject When Job Repository Returns Null Then Maps To Null .
   *
   * @test
   */
  public function whenJobRepositoryReturnsNullThenMapsToNull(): void {
    $tmgmtJobId = 10;
    $this->jobRepositoryReturnsNull($tmgmtJobId);
    $this->documentRepository->getAllByProjectId(Argument::any())->shouldNotBeCalled();
    $dbItem = [
      'correlation_id' => 'correlationID',
      'tmgmt_job_id' => $tmgmtJobId,
      'status' => 'a status',
      'bundle_id' => NULL,
    ];
    $sut = $this->projectMapper();
    $result = $sut->toProject($dbItem);
    $this->assertNull($result);
  }

  /**
   * When Domain Entity Has Properties Not Null Then Maps To Database Values .
   *
   * @test
   */
  public function todatabaseentityWhenDomainEntityHasAllPropertiesNotNullThenMapsItToRawDatabaseValuesButIgnoresDocumentsAndProjectId(): void {
    $projectId = 1;
    $tmgmtJobId = 10;
    $tmgmtJob = $this->jobRepositoryReturnsJobById($tmgmtJobId);
    $documentId = 99;
    $document = $this->documentRepositoryReturnsAllDocumentsByProjectId($documentId, $projectId);
    $platformUrl = new Uri('https://example.com/project/345');
    $project = new Project(
      $tmgmtJob, $correlationId = 'correlationID',
      ProjectStatus::uploading(),
      [$document],
      $projectId,
      $platformId = 345,
      $platformUrl
    );
    $sut = $this->projectMapper();
    $result = $sut->toDatabaseEntity($project);
    $this->assertCount(6, $result);
    // Excluding project ID and documents there are 5 properties left.
    $this->assertSame($tmgmtJobId, $result['tmgmt_job_id']);
    $this->assertSame($correlationId, $result['correlation_id']);
    $this->assertSame(ProjectStatus::UPLOADING, $result['status']);
    $this->assertSame($platformId, $result['platform_id']);
    $this->assertSame((string) $platformUrl, $result['platform_url']);
  }

  /**
   * When Entity Has Optional Properties As Null Then Maps To Database Values .
   *
   * @test
   */
  public function todatabaseentityWhenDomainEntityHasOptionalPropertiesAsNullThenMapsItToRawDatabaseValues(): void {
    $tmgmtJobId = 10;
    $tmgmtJob = $this->jobRepositoryReturnsJobById($tmgmtJobId);
    $project = new Project($tmgmtJob, $correlationId = 'correlationID', ProjectStatus::uploading());
    $sut = $this->projectMapper();
    $result = $sut->toDatabaseEntity($project);
    $this->assertCount(6, $result);
    $this->assertSame($tmgmtJobId, $result['tmgmt_job_id']);
    $this->assertSame($correlationId, $result['correlation_id']);
    $this->assertSame(ProjectStatus::UPLOADING, $result['status']);
    $this->assertSame(0, $result['platform_id']);
    $this->assertEmpty($result['platform_url']);
  }

  /**
   * Project Mapper .
   */
  private function projectMapper(): ProjectMapper {
    return new ProjectMapper($this->tmgmtJobRepository->reveal(), $this->documentRepository->reveal());
  }

  /**
   * Job Repository Returns Job By Id .
   */
  private function jobRepositoryReturnsJobById(int $tmgmtJobId): TmgmtJobInterface {
    $tmgmtJob = (new TmgmtJobBuilder())->withJobId($tmgmtJobId)->build();
    $this->tmgmtJobRepository->getById($tmgmtJobId)->willReturn($tmgmtJob);
    return $tmgmtJob;
  }

  /**
   * Job Repository Returns Null .
   */
  private function jobRepositoryReturnsNull(int $tmgmtJobId): void {
    $this->tmgmtJobRepository->getById($tmgmtJobId)->willReturn(NULL);
  }

  /**
   * Document Repository Returns All Documents By Project Id .
   */
  private function documentRepositoryReturnsAllDocumentsByProjectId(int $documentId, int $projectId): Document {
    $document = (new DocumentBuilder())->withId($documentId)->build();
    $this->documentRepository->getAllByProjectId($projectId)->willReturn([$document]);
    return $document;
  }

}
