<?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\Adapter\TMGMT\TmgmtJobItemInterface;
use Drupal\languagewire_translation_provider\Database\ProjectRepositoryInterface;
use Drupal\languagewire_translation_provider\Domain\Project;
use Drupal\languagewire_translation_provider\Domain\ProjectStatus;
use Drupal\languagewire_translation_provider\Service\ProjectStateHandler;
use Drupal\languagewire_translation_provider\Test\Builder\Domain\ProjectBuilder;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\Prophecy\ObjectProphecy;

/**
 * Project State Handler Test .
 */
class ProjectStateHandlerTest extends TestCase {
  use \Prophecy\PhpUnit\ProphecyTrait;

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

  /**
   * Set Up .
   */
  protected function setUp(): void {
    $this->projectRepository = $this->prophesize(ProjectRepositoryInterface::class);
  }

  /**
   * When Job Is Set To Error State Then Projects Are Set To Error State .
   *
   * @test
   */
  public function whenJobIsSetToErrorStateThenProjectsAreSetToErrorState(): void {
    $jobMock = $this->getSuccessfulTmgmtJobMock();
    $job = $jobMock->reveal();
    $project1 = $this->createProject($job, ProjectStatus::inProgress());
    $project2 = $this->createProject($job, ProjectStatus::inProgress());

    $this->projectRepository->getAllByTmgmtJobId($job->jobId())->willReturn([$project1, $project2]);
    $this->projectRepository->save($project1)->willReturnArgument(0);
    $this->projectRepository->save($project2)->willReturnArgument(0);

    $jobErrorMessage = 'error message';

    $spc = $this->projectStateHandler();
    $spc->setJobToErrorState($job, $jobErrorMessage);

    $this->assertEquals(ProjectStatus::error(), $project1->getStatus());
    $this->assertEquals(ProjectStatus::error(), $project2->getStatus());

    $jobMock->addMessage($jobErrorMessage)->shouldHaveBeenCalled();
  }

  /**
   * When Job Is Set To In Progress State .
   *
   * @test
   */
  public function whenJobIsSetToInProgressStateThenReimportableProjectsAreSetToInProgress(): void {
    $jobMock = $this->getSuccessfulTmgmtJobMock();
    $job = $jobMock->reveal();
    $reimportableProject = $this->createProject($job, ProjectStatus::error());
    $notReimportableProject = $this->createProject($job, ProjectStatus::inProgress());
    $this->projectRepository->getAllByTmgmtJobId($job->jobId())->willReturn(
        [
          $reimportableProject,
          $notReimportableProject,
        ]
    );
    // We're using Argument::is(), because it will do an identity match.
    // By default the ::exact() is used which
    // is just comparing property values and was leading to failure,
    // because $reimportableProject's status
    // was being changed to "in-progress",
    // making that object essentially identical to $notReimportableProject.
    $this->projectRepository->save(Argument::is($reimportableProject))->shouldBeCalled();
    $this->projectRepository->save(Argument::is($notReimportableProject))->shouldNotBeCalled();

    $spc = $this->projectStateHandler();
    $spc->setJobToInProgressState($job);

    $this->assertEquals(ProjectStatus::inProgress(), $reimportableProject->getStatus());
    $this->assertEquals(ProjectStatus::inProgress(), $notReimportableProject->getStatus());

    $jobMock->addMessage(ProjectStateHandler::REIMPORT_MESSAGE)->shouldHaveBeenCalled();
    $jobMock->makeActive()->shouldHaveBeenCalled();
    $jobMock->makeItemsActive()->shouldHaveBeenCalled();
    $jobMock->save()->shouldHaveBeenCalled();
  }

  /**
   * When Job Is Set To In Progress State Without Reimportable .
   *
   * @test
   */
  public function whenJobIsSetToInProgressStateWithoutReimportableProjectsThenJobMessageIsNotAdded(): void {
    $jobMock = $this->getSuccessfulTmgmtJobMock();
    $job = $jobMock->reveal();
    $project1 = $this->createProject($job, ProjectStatus::inProgress());
    $project2 = $this->createProject($job, ProjectStatus::cancelled());
    $this->projectRepository->getAllByTmgmtJobId($job->jobId())->willReturn([$project1, $project2]);

    $spc = $this->projectStateHandler();
    $spc->setJobToInProgressState($job);

    $jobMock->addMessage(ProjectStateHandler::REIMPORT_MESSAGE)->shouldNotHaveBeenCalled();
    $jobMock->makeActive()->shouldNotHaveBeenCalled();
    $this->projectRepository->save($project1)->shouldNotHaveBeenCalled();
    $this->projectRepository->save($project2)->shouldNotHaveBeenCalled();
  }

  /**
   * Get Successful Tmgmt Job Mock .
   */
  private function getSuccessfulTmgmtJobMock(): ObjectProphecy {
    $jobItem = $this->prophesize(TmgmtJobItemInterface::class)->reveal();
    $jobMock = $this->prophesize(TmgmtJobInterface::class);
    $jobMock->jobId()->willReturn(1);
    $jobMock->items()->willReturn([$jobItem]);

    return $jobMock;
  }

  /**
   * Project State Handler .
   */
  private function projectStateHandler(): ProjectStateHandler {
    return new ProjectStateHandler(
          $this->projectRepository->reveal()
      );
  }

  /**
   * Create Project .
   */
  private function createProject(TmgmtJobInterface $job, ProjectStatus $projectStatus): Project {
    return (new ProjectBuilder())->withTmgmtJob($job)->withStatus($projectStatus)->build();
  }

}
