<?php

declare(strict_types=1);

namespace Drupal\languagewire_translation_provider\Client;

use Drupal\languagewire_translation_provider\Adapter\TMGMT\TmgmtJobInterface;
use Drupal\languagewire_translation_provider\Adapter\TMGMT\TmgmtTranslatorInterface;
use Drupal\languagewire_translation_provider\Database\ProjectTemplateRepositoryInterface;
use Drupal\languagewire_translation_provider\Domain\Document;
use Drupal\languagewire_translation_provider\Domain\Project;
use Drupal\languagewire_translation_provider\Domain\ProjectTemplate;
use Drupal\languagewire_translation_provider\Platform\AvailableServices;

/**
 * Project Specification Factory.
 */
final class ProjectSpecificationFactory {

  /**
   * Project Specification Factory.
   *
   * @var \Drupal\languagewire_translation_provider\Database\ProjectTemplateRepositoryInterface
   */
  private ProjectTemplateRepositoryInterface $projectTemplateRepository;

  /**
   * Constructs a new ProjectSpecificationFactory object.
   *
   * @param \Drupal\languagewire_translation_provider\Database\ProjectTemplateRepositoryInterface $projectTemplateRepository
   *   Project Template Repository.
   */
  public function __construct(ProjectTemplateRepositoryInterface $projectTemplateRepository) {
    $this->projectTemplateRepository = $projectTemplateRepository;
  }

  /**
   * Create project specification.
   *
   * @param \Drupal\languagewire_translation_provider\Domain\Project $project
   *   Project.
   * @param \Drupal\languagewire_translation_provider\Platform\AvailableServices $availableServices
   *   Available services.
   *
   * @throws \Exception
   * @throws InvalidSourceLanguageException
   */
  public function createSpecification(
    Project $project,
    AvailableServices $availableServices
  ): ProjectSpecificationInterface {
    // @todo split too big project
    // @todo require all stuff to not be null
    $projectTemplatePlatformId = $project->getTmgmtJob()->getProjectTemplatePlatformId();

    if ($projectTemplatePlatformId != NULL) {
      $translator = $project->getTmgmtJob()->translator();
      return $this->projectSpecificationFromTemplate(
        $projectTemplatePlatformId,
        $translator,
        $project
      );
    }
    else {
      $serviceId = $project->getTmgmtJob()->getServiceId() ?? 0;
      return $this->projectSpecificationFromService(
          $availableServices,
          $serviceId,
          $project
      );
    }
  }

  /**
   * Get source document ids.
   */
  private function getSourceDocumentIds(array $documents): array {
    return array_filter(array_map(function (Document $document): ?int {
        return $document->getPlatformId();
    }, $documents));
  }

  /**
   * Create project specification from service.
   *
   * @param \Drupal\languagewire_translation_provider\Platform\AvailableServices $availableServices
   *   Available services.
   * @param int $serviceId
   *   Service ID.
   * @param \Drupal\languagewire_translation_provider\Domain\Project $project
   *   Project.
   *
   * @return LanguageWireProjectSpecification
   *   Project specification.
   *
   * @throws \Exception
   */
  public function projectSpecificationFromService(
    AvailableServices $availableServices,
    int $serviceId,
    Project $project
  ): LanguageWireProjectSpecification {
    $service = $availableServices->getById($serviceId) ?? throw new \Exception('No service available');
    $service->configure($project->getTmgmtJob()->settings());

    $tmgmtJob = $project->getTmgmtJob();
    return new LanguageWireProjectSpecification(
      $this->getSourceDocumentIds($project->getDocuments()),
      $project->getCorrelationId(),
      $tmgmtJob->title(),
      $service,
      $tmgmtJob->getBriefing()
    );
  }

  /**
   * Project specification from template.
   *
   * @param int $projectTemplatePlatformId
   *   Project template platform ID.
   * @param \Drupal\languagewire_translation_provider\Adapter\TMGMT\TmgmtTranslatorInterface $translator
   *   Translator.
   * @param \Drupal\languagewire_translation_provider\Domain\Project $project
   *   Project.
   *
   * @return LanguageWireProjectFromTemplateSpecification
   *   Project specification.
   *
   * @throws \Exception
   * @throws InvalidSourceLanguageException
   */
  public function projectSpecificationFromTemplate(
    int $projectTemplatePlatformId,
    TmgmtTranslatorInterface $translator,
    Project $project
  ): LanguageWireProjectFromTemplateSpecification {
    $projectTemplate = $this->projectTemplateRepository->getByPlatformId($projectTemplatePlatformId, $translator);

    $this->validateProjectTemplate($projectTemplate, $projectTemplatePlatformId, $project);

    return new LanguageWireProjectFromTemplateSpecification(
      $project,
      $projectTemplate
    );
  }

  /**
   * Is valid source language.
   */
  private function isValidSourceLanguage(?ProjectTemplate $projectTemplate, TmgmtJobInterface $tmgmtJob): bool {
    return $tmgmtJob->sourceLanguage()->remoteLanguage() == $projectTemplate->getSourceLanguage();
  }

  /**
   * Validate project template.
   *
   * @throws \Exception
   */
  private function validateProjectTemplate(?ProjectTemplate $projectTemplate, int $projectTemplatePlatformId, Project $project): void {
    if ($projectTemplate == NULL) {
      throw new \Exception(
            sprintf(
                "Could not find project template with Platform ID %d",
                $projectTemplatePlatformId
            )
        );
    }

    if (!$this->isValidSourceLanguage($projectTemplate, $project->getTmgmtJob())) {
      throw new InvalidSourceLanguageException(
            sprintf(
                "Project template with Platform ID %d has source language set to '%s', which does not match '%s' as set in this project",
                $projectTemplatePlatformId,
                $projectTemplate->getSourceLanguage(),
                $project->getTmgmtJob()->sourceLanguage()->remoteLanguage()
            )
        );
    }
  }

}
