<?php

namespace Drupal\acquia_contenthub_s3;

use Acquia\ContentHubClient\CDF\CDFObjectInterface;
use Drupal\acquia_contenthub\ContentHubCommonActions;
use Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\StreamWrapper\StreamWrapperManager;

/**
 * Responsible for locating the s3 source of the given file url.
 *
 * @package Drupal\acquia_contenthub_s3
 */
class S3FileOriginLocator {

  /**
   * The s3 file mapper.
   *
   * @var \Drupal\acquia_contenthub_s3\S3FileMap
   */
  protected $s3FileMap;

  /**
   * File entity storage.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface
   */
  protected $fileEntityStorage;

  /**
   * Common actions of content hub.
   *
   * @var \Drupal\acquia_contenthub\ContentHubCommonActions
   */
  protected $commonActions;

  /**
   * CH configurations.
   *
   * @var \Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface
   */
  protected ContentHubConfigurationInterface $achConfigurations;

  /**
   * S3FileOriginLocator constructor.
   *
   * @param \Drupal\acquia_contenthub_s3\S3FileMap $file_map
   *   The database connection.
   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
   *   The file entity storage.
   * @param \Drupal\acquia_contenthub\ContentHubCommonActions $common
   *   The ContentHub common actions service.
   * @param \Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface $ch_configurations
   *   CH configurations.
   */
  public function __construct(S3FileMap $file_map, EntityStorageInterface $storage, ContentHubCommonActions $common, ContentHubConfigurationInterface $ch_configurations) {
    $this->fileEntityStorage = $storage;
    $this->s3FileMap = $file_map;
    $this->commonActions = $common;
    $this->achConfigurations = $ch_configurations;
  }

  /**
   * Returns the source of the s3 file.
   *
   * @param string $uri
   *   The s3 originated file uri.
   *
   * @return array
   *   The s3 source with the bucket and root_folder.
   *
   * @throws \Exception
   */
  public function getS3FileSource(string $uri): array {
    $target_uri = StreamWrapperManager::getTarget($uri);
    if ($target_uri) {
      $uri = $target_uri;
    }

    $uri = $this->getOriginalFileUrl($uri);
    $tracked_s3_file = $this->s3FileMap->getFileByUri($uri);
    if (!$tracked_s3_file) {

      if (file_exists($uri)) {
        return $this->getLocalS3FileSource();
      }

      $tracked_s3_file = $this->getRemoteFile($uri);
      if (!$tracked_s3_file) {
        return $this->getLocalS3FileSource();
      }
    }

    if ($this->achConfigurations->getConnectionDetails()->getClientUuid() === $tracked_s3_file->origin_uuid) {
      return $this->getLocalS3FileSource();
    }

    return [
      'bucket' => $tracked_s3_file->bucket,
      'root_folder' => $tracked_s3_file->root_folder,
    ];
  }

  /**
   * Returns the remote file by the uri if there is one, NULL otherwise.
   *
   * @param string $uri
   *   The uri to identify the file.
   *
   * @return object|null
   *   An object with the following fields:
   *
   * @see \Drupal\acquia_contenthub_s3\S3FileMap::getFileByUuid()
   *
   * @throws \Exception
   */
  protected function getRemoteFile(string $uri): ?object {
    $files = $this->fileEntityStorage->loadByProperties(['uri' => $uri]);
    if (!$files) {
      return NULL;
    }

    // Use the tracker to avoid unnecessary requests to Content Hub.
    // @codingStandardsIgnoreLine
    $container = \Drupal::getContainer();
    $tracker = $container->has('acquia_contenthub_publisher.tracker') ?
      $container->get('acquia_contenthub_publisher.tracker') : NULL;
    // Due to the current implementation of s3fs it is possible that a site
    // stores multiple file entities with the same uri. We pick and store the
    // one that was actually exported and can be located in ContentHub.
    foreach ($files as $file) {
      if (!is_null($tracker)) {
        // Do not initiate entity retrieval since it is already in the export
        // table.
        $tracked_file = $tracker->get($file->uuid());
        if ($tracked_file) {
          continue;
        }
      }

      $remote_file = $this->commonActions->getRemoteEntity($file->uuid());
      if ($remote_file) {
        $this->s3FileMap->record(
          $remote_file->getUuid(),
          $this->getAttributeValue($remote_file, 'ach_s3_bucket'),
          $this->getAttributeValue($remote_file, 'ach_s3_source'),
          $remote_file->getOrigin()
        );

        return $this->s3FileMap->getFileByUuid($remote_file->getUuid());
      }
    }

    return NULL;
  }

  /**
   * Returns the original file url from the current request.
   *
   * Transforms the image style provided derivative url back to the original.
   *
   * @param string $uri
   *   The uri to transform.
   *
   * @return string
   *   The original url.
   */
  protected function getOriginalFileUrl(string $uri): string {
    $parts = explode('s3/', $uri);
    $transformed_uri = $parts[1] ?? $parts[0];
    return "s3://$transformed_uri";
  }

  /**
   * Returns the value of an arbitrary CDF attribute.
   *
   * @param \Acquia\ContentHubClient\CDF\CDFObjectInterface $object
   *   The object to get the attribute value from.
   * @param string $attribute
   *   The attribute to get.
   *
   * @return mixed
   *   The value of the attribute.
   */
  protected function getAttributeValue(CDFObjectInterface $object, string $attribute) {
    $attr = $object->getAttribute($attribute);
    if (!$attr) {
      return '';
    }

    return $attr->getValue()[LanguageInterface::LANGCODE_NOT_SPECIFIED];
  }

  /**
   * Returns the current site's s3fs original configuration.
   *
   * @return array
   *   The s3 source, bucket and root_folder.
   */
  protected function getLocalS3FileSource(): array {
    // We need the original unmodified s3fs config object.
    // phpcs:ignore
    $config = \Drupal::config('s3fs.settings');
    return [
      'bucket' => $config->get('bucket'),
      'root_folder' => $config->get('root_folder'),
    ];
  }

}
