<?php

declare(strict_types=1);

namespace Drupal\languagewire_translation_provider\Test\Service;

use Drupal\languagewire_translation_provider\Adapter\TMGMT\TmgmtJobInterface;
use Drupal\languagewire_translation_provider\Client\ClientInterface;
use Drupal\languagewire_translation_provider\Client\RecoverableException;
use Drupal\languagewire_translation_provider\Content\SanitizerInterface;
use Drupal\languagewire_translation_provider\Content\Translation\Import\TranslationImportFallbackInterface;
use Drupal\languagewire_translation_provider\Domain\Document;
use Drupal\languagewire_translation_provider\Domain\DocumentStatus;
use Drupal\languagewire_translation_provider\Domain\Project;
use Drupal\languagewire_translation_provider\Domain\ProjectStatus;
use Drupal\languagewire_translation_provider\Platform\Document\TranslatedDocument;
use Drupal\languagewire_translation_provider\Service\ProjectImporter;
use Drupal\languagewire_translation_provider\Test\Builder\Domain\DocumentBuilder;
use Drupal\languagewire_translation_provider\Test\Builder\Domain\ProjectBuilder;
use Drupal\languagewire_translation_provider\Test\Builder\JobItemBuilder;
use Drupal\languagewire_translation_provider\Test\Builder\TranslatedContentBuilder;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\Prophecy\ObjectProphecy;
use Psr\Log\LoggerInterface;
use Throwable;

/**
 * Project Importer Test .
 */
class ProjectImporterTest extends TestCase {
  use \Prophecy\PhpUnit\ProphecyTrait;

  /**
   * Client .
   *
   * @var \Prophecy\Prophecy\ObjectProphecy
   */
  private $client;

  /**
   * Logger .
   *
   * @var \Prophecy\Prophecy\ObjectProphecy
   */
  private $logger;

  /**
   * Sanitizer .
   *
   * @var \Prophecy\Prophecy\ObjectProphecy
   */
  private $sanitizer;

  /**
   * Translation Import Fallback .
   *
   * @var \Prophecy\Prophecy\ObjectProphecy
   */
  private $translationImportFallback;

  /**
   * Set Up .
   */
  protected function setUp(): void {
    $this->client = $this->prophesize(ClientInterface::class);
    $this->logger = $this->prophesize(LoggerInterface::class);
    $this->translationImportFallback = $this->prophesize(TranslationImportFallbackInterface::class);
    $this->sanitizer = $this->prophesize(SanitizerInterface::class);
    $this->sanitizer->desanitize(Argument::type('string'))->willReturnArgument(0);
  }

  /**
   * When Client Throws Recoverable Exception .
   *
   * @test
   */
  public function whenClientThrowsRecoverableExceptionThenServiceFinishesWithoutAnyUpdates(): void {
    $project = (new ProjectBuilder())->withStatus(ProjectStatus::uploading())->build();
    $this->clientThrowsWhenFetchingDocuments($project, new RecoverableException());
    $sut = $this->projectImporter();
    $sut->importProject($project, $this->client->reveal());
    $this->logger->warning(Argument::type('string'))->shouldHaveBeenCalled();
    $this->assertEquals(ProjectStatus::cancelled(), $project->getStatus());
  }

  /**
   * When Client Throws Nonrecoverable Exception .
   *
   * @test
   */
  public function whenClientThrowsNonrecoverableExceptionThenErrorIsLoggedAndFailureMessageIsAddedToJobAndProjectHasErrorState(): void {
    $tmgmtJob = $this->getTmgmtJobMock();
    $tmgmtJob->countMessage(Argument::type('string'))->willReturn(1);
    $project = (new ProjectBuilder())->withStatus(ProjectStatus::uploading())->withTmgmtJob($tmgmtJob->reveal())->build();
    $this->clientThrowsWhenFetchingDocuments($project, new \Exception());
    $sut = $this->projectImporter();
    $sut->importProject($project, $this->client->reveal());
    $this->logger->error(Argument::type('string'))->shouldHaveBeenCalled();
    $this->assertEquals(ProjectStatus::cancelled(), $project->getStatus());
    $tmgmtJob->addMessage(Argument::type('string'))->shouldHaveBeenCalled();
  }

  /**
   * When Translations Are Fetched Successfully .
   *
   * @test
   */
  public function whenTranslationsAreFetchedSuccessfullyThenTranslationAreAppliedViaTmgmtJob(): void {
    $tmgmtJob = $this->getTmgmtJobMock();
    $tmgmtJob->countMessage(Argument::type('string'))->willReturn(1);
    $document = (new DocumentBuilder())->build();
    $project = (new ProjectBuilder())->withStatus(ProjectStatus::uploading())->withTmgmtJob($tmgmtJob->reveal())->withDocuments($document)->build();
    $this->clientReturnsTranslatedDocuments($document, $project);
    $this->translationImportFallback->applyTranslationFallback(Argument::type('array'), $document, $project)->willReturnArgument(0);
    $sut = $this->projectImporter();
    $sut->importProject($project, $this->client->reveal());
    $tmgmtJob->applyTranslations(Argument::type('array'))->shouldHaveBeenCalled();
    //$tmgmtJob->addMessage(Argument::containingString('Translations have been imported'))->shouldHaveBeenCalled();
    $this->assertEquals(ProjectStatus::uploading(), $project->getStatus());
    $this->assertEquals(DocumentStatus::uploadingContent(), $document->getStatus());
  }

  /**
   * Project Importer .
   */
  private function projectImporter(): ProjectImporter {
    return new ProjectImporter($this->sanitizer->reveal(), $this->logger->reveal(), $this->translationImportFallback->reveal());
  }

  /**
   * Translated Document .
   */
  private function translatedDocument(Document $document): TranslatedDocument {
    $content = [(new TranslatedContentBuilder())->build()];
    return new TranslatedDocument($content, $document);
  }

  /**
   * Get Tmgmt Job Mock .
   */
  private function getTmgmtJobMock(): ObjectProphecy {
    $tmgmtJob = $this->prophesize(TmgmtJobInterface::class);
    $tmgmtJob->title()->willReturn('TMGMT Job');
    $tmgmtJob->jobId()->willReturn(1);
    $tmgmtItem = (new JobItemBuilder())->build();
    $tmgmtJob->items()->willReturn([$tmgmtItem]);
    return $tmgmtJob;
  }

  /**
   * Client Returns Translated Documents .
   */
  private function clientReturnsTranslatedDocuments(Document $document, Project $project): void {
    $translatedDocument = $this->translatedDocument($document);
    $this->client->fetchTranslations($project)->willReturn([$translatedDocument]);
  }

  /**
   * Client Throws When Fetching Documents .
   */
  private function clientThrowsWhenFetchingDocuments(Project $project, Throwable $exception): void {
    $this->client->fetchTranslations($project)->willThrow($exception);
  }

}
