<?php

declare(strict_types=1);

namespace Drupal\languagewire_translation_provider\Cron\Job;

use Drupal\languagewire_translation_provider\Adapter\SystemInterface;
use Drupal\languagewire_translation_provider\Adapter\TMGMT\TmgmtJobInterface;
use Drupal\languagewire_translation_provider\Client\ClientInterface;
use Drupal\languagewire_translation_provider\Client\LanguageWireClientFactory;
use Drupal\languagewire_translation_provider\Database\ProjectRepositoryInterface;
use Drupal\languagewire_translation_provider\Domain\Project;
use Psr\Log\LoggerInterface;

/**
 * Base class for all jobs.
 */
abstract class BaseJob {
  /**
   * System adapter.
   *
   * @var \Drupal\languagewire_translation_provider\Adapter\SystemInterface
   */
  protected SystemInterface $system;

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

  /**
   * Project repository.
   *
   * @var \Drupal\languagewire_translation_provider\Database\ProjectRepositoryInterface
   */
  protected ProjectRepositoryInterface $projectRepository;

  /**
   * LanguageWire client factory.
   *
   * @var \Drupal\languagewire_translation_provider\Client\LanguageWireClientFactory
   */
  private LanguageWireClientFactory $clientFactory;

  /**
   * Constructs a new BaseJob object.
   *
   * @param \Drupal\languagewire_translation_provider\Adapter\SystemInterface $system
   *   System.
   * @param \Drupal\languagewire_translation_provider\Database\ProjectRepositoryInterface $projectRepository
   *   Project repository.
   * @param \Drupal\languagewire_translation_provider\Client\LanguageWireClientFactory $clientFactory
   *   LanguageWire client factory.
   */
  public function __construct(
    SystemInterface $system,
    ProjectRepositoryInterface $projectRepository,
    LanguageWireClientFactory $clientFactory
  ) {
    $this->system = $system;
    $this->projectRepository = $projectRepository;
    $this->logger = $this->system->logger();
    $this->clientFactory = $clientFactory;
  }

  /**
   * Get job name.
   */
  abstract protected function jobName(): string;

  /**
   * Get projects.
   */
  abstract protected function getProjects(): array;

  /**
   * Process project.
   */
  abstract protected function processProject(Project $project): void;

  /**
   * Execute job.
   */
  public function execute(): void {
    // @todo use some kind of global lock
    $this->logger->info("Starting {$this->jobName()} job.");

    $projectsToProcess = $this->safeGetProjects();

    $this->processProjects($projectsToProcess);

    $this->logger->info("{$this->jobName()} job finished.");
  }

  /**
   * Get projects.
   */
  private function safeGetProjects(): array {
    try {
      return $this->getProjects();
    }
    catch (\Throwable $exception) {
      $this->logger->error("Error while fetching projects from the database during {$this->jobName()}. Failure reason: $exception");
      return [];
    }
  }

  /**
   * Process projects.
   */
  protected function processProjects(array $projects): void {
    foreach ($projects as $project) {
      $this->safeProcessProject($project);
    }
  }

  /**
   * Process project.
   */
  protected function safeProcessProject(Project $project): void {
    try {
      $this->processProject($project);
    }
    catch (\Throwable $exception) {
      /*
      There are 2 general classes of exceptions:

      - Recoverable
      - Non-recoverable

      If we stumble upon a recoverable exception,
      it means that's most likely a temporary issue,
      and we should just try again later.

      If we get non-recoverable error,
      it means that project can't proceed to the next step
      and should end up with an error state.
       */
      $job = $project->getTmgmtJob();
      $this->logExceptionForJob($exception, $job);
    }
  }

  /**
   * Get LanguageWire client.
   *
   * @throws \Drupal\languagewire_translation_provider\api\Core\Exception\Error
   */
  protected function getLanguageWireClient(TmgmtJobInterface $job): ClientInterface {
    return $this->clientFactory->createForTmgmtTranslator($job->translator());
  }

  /**
   * Log exception for job.
   */
  protected function logExceptionForJob(\Throwable $exception, TmgmtJobInterface $job): void {
    $this->logger->error(
        "Error occurred during {$this->jobName()} for job {$job->title()}
        (ID {$job->jobId()})).\n
        Failure reason: $exception"
    );
  }

}
