<?php

namespace Drupal\languagewire_translation_provider\Test\Cron\Job;

use Drupal\languagewire_translation_provider\Adapter\SystemInterface;
use Drupal\languagewire_translation_provider\Adapter\TMGMT\LanguageMapping;
use Drupal\languagewire_translation_provider\Adapter\TMGMT\TmgmtJobInterface;
use Drupal\languagewire_translation_provider\Adapter\TMGMT\TmgmtTranslatorInterface;
use Drupal\languagewire_translation_provider\Client\LanguageWireClient;
use Drupal\languagewire_translation_provider\Client\LanguageWireClientFactory;
use Drupal\languagewire_translation_provider\Cron\Job\ContentUploadJob;
use Drupal\languagewire_translation_provider\Database\DocumentRepositoryInterface;
use Drupal\languagewire_translation_provider\Database\ProjectRepositoryInterface;
use Drupal\languagewire_translation_provider\Domain\DocumentStatus;
use Drupal\languagewire_translation_provider\Domain\ProjectStatus;
use Drupal\languagewire_translation_provider\Service\ProjectContentUploader;
use Drupal\languagewire_translation_provider\Settings\JobSettings;
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\LanguageMappingBuilder;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\Prophecy\ObjectProphecy;
use Psr\Log\LoggerInterface;

/**
 * Content Upload Job Test .
 */
class ContentUploadJobTest extends TestCase {
  use \Prophecy\PhpUnit\ProphecyTrait;

  /**
   * System .
   *
   * @var \Prophecy\Prophecy\ObjectProphecy
   */
  protected $system;

  /**
   * Content Uploader .
   *
   * @var \Prophecy\Prophecy\ObjectProphecy
   */
  protected $contentUploader;

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

  /**
   * Project Repository .
   *
   * @var \Prophecy\Prophecy\ObjectProphecy
   */
  protected $projectRepository;

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

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

  /**
   * Set Up .
   */
  protected function setUp(): void {
    $this->system = $this->prophesize(SystemInterface::class);
    $this->contentUploader = $this->prophesize(ProjectContentUploader::class);
    $this->projectRepository = $this->prophesize(ProjectRepositoryInterface::class);
    $this->documentRepository = $this->prophesize(DocumentRepositoryInterface::class);

    $this->logger = $this->prophesize(LoggerInterface::class);

    $this->system->logger()->willReturn($this->logger->reveal());

    $this->clientFactory = $this->prophesize(LanguageWireClientFactory::class);
    $this->clientFactory->createForTmgmtTranslator(Argument::type(TmgmtTranslatorInterface::class))->willReturn(
          $this->prophesize(LanguageWireClient::class)->reveal()
      );
  }

  /**
   * When Project Has No Documents .
   *
   * @test
   */
  public function whenProjectHasNoDocumentsThenProjectDocumentsAreCreatedFromTheTmgmtJob(): void {
    $item1 = (new JobItemBuilder())->withItemId(1)->withLabel('Item 1')->build();
    $item2 = (new JobItemBuilder())->withItemId(2)->withLabel('Item 2')->build();
    $job = $this->createTmgmtJobMockWithItems([$item1, $item2]);

    // Build a project without any documents.
    $project = (new ProjectBuilder())->withStatus(ProjectStatus::uploading())->withTmgmtJob($job)->build();

    $this->projectRepository->getAllByStatus(ProjectStatus::uploading())->willReturn([$project]);
    $this->projectRepository->save($project)->shouldBeCalled();

    // The document repository should be updated.
    //$this->documentRepository->saveMany(Argument::type('array'))->shouldBeCalled();

    $job = $this->getContentUploadJob();

    $job->execute();
  }

  /**
   * When Project Has Many Documents Then Project Documents Are Not Created .
   *
   * @test
   */
  public function whenProjectHasManyDocumentsThenProjectDocumentsAreNotCreated(): void {
    $item1 = (new JobItemBuilder())->withItemId(1)->withLabel('Item 1')->build();
    $item2 = (new JobItemBuilder())->withItemId(2)->withLabel('Item 2')->build();
    $job = $this->createTmgmtJobMockWithItems([$item1, $item2]);

    // Build a project with two documents.
    $document1 = (new DocumentBuilder())->withStatus(DocumentStatus::uploadingContent())->withTmgmtJobItem($item1)->build();
    $document2 = (new DocumentBuilder())->withStatus(DocumentStatus::uploadingContent())->withTmgmtJobItem($item2)->build();

    $project = (new ProjectBuilder())->withStatus(ProjectStatus::uploading())->withTmgmtJob($job)->withDocuments($document1, $document2)->build();

    $this->projectRepository->getAllByStatus(ProjectStatus::uploading())->willReturn([$project]);
    $this->projectRepository->save($project)->shouldBeCalled();

    $job = $this->getContentUploadJob();

    // The document repository should NOT be updated.
    $this->documentRepository->saveMany(Argument::type('array'))->shouldNotBeCalled();

    $job->execute();
  }

  /**
   * When Project Has One Uploaded Document And One Pending .
   *
   * @test
   */
  public function whenProjectHasOneUploadedDocumentAndOnePendingThenProjectDocumentsAreNotCreated(): void {
    $item1 = (new JobItemBuilder())->withItemId(1)->withLabel('Item 1')->build();
    $item2 = (new JobItemBuilder())->withItemId(2)->withLabel('Item 2')->build();
    $job = $this->createTmgmtJobMockWithItems([$item1, $item2]);

    // Build a project with two documents, one uploaded and one pending.
    $document1 = (new DocumentBuilder())->withTmgmtJobItem($item1)
      ->withStatus(DocumentStatus::uploadedContent())->build();
    $document2 = (new DocumentBuilder())->withTmgmtJobItem($item2)
      ->withStatus(DocumentStatus::uploadingContent())->build();

    $project = (new ProjectBuilder())->withStatus(ProjectStatus::uploading())->withTmgmtJob($job)->withDocuments($document1, $document2)->build();

    $this->projectRepository->getAllByStatus(ProjectStatus::uploading())->willReturn([$project]);
    $this->projectRepository->save($project)->shouldBeCalled();

    $job = $this->getContentUploadJob();

    // The document repository should NOT be updated
    // as the documents were created already.
    $this->documentRepository->saveMany(Argument::type('array'))->shouldNotBeCalled();

    $job->execute();
  }

  /**
   * Get Tmgmt Job Mock .
   */
  private function getTmgmtJobMock(array $jobItems, JobSettings $jobSettings, LanguageMapping $languageMapping): ObjectProphecy {
    $translator = $this->prophesize(TmgmtTranslatorInterface::class);
    $job = $this->prophesize(TmgmtJobInterface::class);
    $job->title()->willReturn('job title');
    $job->jobId()->willReturn(1);
    $job->items()->willReturn($jobItems);
    $job->settings()->willReturn($jobSettings);
    $job->translator()->willReturn($translator->reveal());
    $job->sourceLanguage()->willReturn($languageMapping);
    return $job;
  }

  /**
   * Create Tmgmt Job Mock With Items .
   *
   * @return \Drupal\languagewire_translation_provider\Adapter\TMGMT\TmgmtJobInterface
   *   Tmgmt Job Mock .
   */
  protected function createTmgmtJobMockWithItems(array $items): TmgmtJobInterface {
    $jobSettings = $this->prophesize(JobSettings::class);
    $languageMapping = (new LanguageMappingBuilder())->build();
    return $this->getTmgmtJobMock($items, $jobSettings->reveal(), $languageMapping)->reveal();
  }

  /**
   * Get Content Upload Job .
   *
   * @return \Drupal\languagewire_translation_provider\Cron\Job\ContentUploadJob
   *   Content Upload Job .
   */
  protected function getContentUploadJob(): ContentUploadJob {
    return new ContentUploadJob(
          $this->system->reveal(),
          $this->contentUploader->reveal(),
          $this->clientFactory->reveal(),
          $this->projectRepository->reveal()
      );
  }

}
