<?php

declare(strict_types=1);

namespace Drupal\languagewire_translation_provider\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\Project;
use Drupal\languagewire_translation_provider\Domain\ProjectStatus;
use Drupal\languagewire_translation_provider\Platform\Document\TranslatedDocument;
use Psr\Log\LoggerInterface;

/**
 * Project importer.
 * */
class ProjectImporter {

  /**
   * Sanitizer.
   *
   * @var \Drupal\languagewire_translation_provider\Content\SanitizerInterface
   */
  private SanitizerInterface $sanitizer;

  /**
   * Logger.
   *
   * @var \Psr\Log\LoggerInterface
   */
  private LoggerInterface $logger;

  /**
   * Translation import fallback.
   *
   * @var \Drupal\languagewire_translation_provider\Content\Translation\Import\TranslationImportFallbackInterface
   */
  private TranslationImportFallbackInterface $translationImportFallback;

  /**
   * Constructs a new ProjectImporter object.
   *
   * @param \Drupal\languagewire_translation_provider\Content\SanitizerInterface $sanitizer
   *   Sanitizer.
   * @param \Psr\Log\LoggerInterface $logger
   *   Logger.
   * @param \Drupal\languagewire_translation_provider\Content\Translation\Import\TranslationImportFallbackInterface $translationImportFallback
   *   Translation import fallback.
   */
  public function __construct(SanitizerInterface $sanitizer, LoggerInterface $logger, TranslationImportFallbackInterface $translationImportFallback) {
    $this->sanitizer = $sanitizer;
    $this->logger = $logger;
    $this->translationImportFallback = $translationImportFallback;
  }

  /**
   * Import project.
   */
  public function importProject(Project $project, ClientInterface $client): void {
    $job = $project->getTmgmtJob();
    $translatedDocuments = $this->fetchTranslations($project, $job, $client);

    if ($translatedDocuments && count($translatedDocuments) !== 0) {
      $this->desanitize($translatedDocuments);
      $project->applyTranslations($translatedDocuments, $this->translationImportFallback);
    }

    $noError = $project->getStatus() !== ProjectStatus::error();

    if (!$noError) {
      return;
    }

    if ($project->isAllCancelled()) {
      $project->setStatus(ProjectStatus::cancelled());
      $job->addMessage("Translations of project {$project->getCorrelationId()} has been cancelled. <strong>Any doubt please contact LanguageWire.</strong>");
    }
    elseif ($project->isAllTranslated()) {
      $project->setStatus(ProjectStatus::finished());
      $job->addMessage("Translations of project {$project->getCorrelationId()} have been imported and applied. <strong>Job is ready to be reviewed.</strong>");
    }
    elseif ($project->isAllError()) {
      $project->setStatus(ProjectStatus::error());
      $job->addMessage("Translations of project {$project->getCorrelationId()} have failed. <strong>Any doubt please contact LanguageWire.</strong>");
    }
  }

  /**
   * Fetch translations.
   *
   * @param \Drupal\languagewire_translation_provider\Domain\Project $project
   *   Project.
   * @param \Drupal\languagewire_translation_provider\Adapter\TMGMT\TmgmtJobInterface $job
   *   Job.
   * @param \Drupal\languagewire_translation_provider\Client\ClientInterface $client
   *   Client.
   *
   * @return \Drupal\languagewire_translation_provider\Platform\Document\TranslatedDocument[]|null
   *   Translated documents.
   */
  private function fetchTranslations(Project $project, TmgmtJobInterface $job, ClientInterface $client): ?array {
    try {
      return $client->fetchTranslations($project);
    }
    catch (RecoverableException $exception) {
      $this->logger->warning("$exception");
    }
    catch (\Exception $exception) {
      $this->logException($exception, $job);

      $exceptionMessage = "Project {$project->getCorrelationId()} import has failed. Failure reason: {$exception->getMessage()}.";
      if ($job->countMessage($exceptionMessage) > 10) {
        $project->setStatus(ProjectStatus::error());
      }

      $job->addMessage($exceptionMessage);
    }

    return NULL;
  }

  /**
   * Desanitize.
   *
   * @param \Drupal\languagewire_translation_provider\Platform\Document\TranslatedDocument[] $translatedDocuments
   *   Desanitize translated documents.
   */
  private function desanitize(array &$translatedDocuments): void {
    array_walk($translatedDocuments, function (TranslatedDocument $document): void {
      $document->desanitizeContent($this->sanitizer);
    });
  }

  /**
   * Log exception.
   */
  private function logException(\Exception $exception, TmgmtJobInterface $job): void {
    $this->logger->error("Error occurred when trying to import a translation project for job {$job->title()} " . "(ID {$job->jobId()}) from the LanguageWire platform. \n" . "Error: $exception");
  }

}
