<?php

namespace Drupal\Tests\acquia_contenthub\Kernel;

use Acquia\ContentHubClient\CDF\CDFObject;
use Acquia\ContentHubClient\CDFAttribute;
use Drupal\acquia_contenthub\AcquiaContentHubEvents;
use Drupal\acquia_contenthub\ContentHubCommonActions;
use Drupal\acquia_contenthub\Event\CreateCdfEntityEvent;
use Drupal\acquia_contenthub\Exception\ContentHubFileException;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\file\Entity\File;
use Drupal\file\FileInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\acquia_contenthub\Traits\CdfMockerTrait;
use PHPUnit\Framework\Assert;
use Prophecy\Argument;

/**
 * Tests the Private File Scheme Handler.
 *
 * @group acquia_contenthub
 * @coversDefaultClass \Drupal\acquia_contenthub\Plugin\FileSchemeHandler\PrivateFileSchemeHandler
 *
 * @requires module depcalc
 *
 * @package Drupal\Tests\acquia_contenthub\Kernel
 */
class PrivateFileSchemeHandlerTest extends KernelTestBase {

  use CdfMockerTrait;

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'acquia_contenthub',
    'depcalc',
    'file',
    'user',
    'filter',
  ];

  /**
   * {@inheritdoc}
   */
  protected function setup(): void {
    parent::setUp();

    $this->installEntitySchema('file');
    $this->installEntitySchema('user');

    /** @var \Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface $ach_configuration */
    $ach_configuration = $this->container->get('acquia_contenthub.configuration');
    $ch_connection = $ach_configuration->getConnectionDetails();
    $ch_connection->setClientUuid('aa1c7fd9-cffe-411e-baef-d0a2c67bddd4');
  }

  /**
   * @covers ::addAttributes
   */
  public function testAddAttributes() {
    $file = $this->createFileEntity('test.jpg', 'private');
    $event = new CreateCdfEntityEvent($file, []);
    $this->container->get('event_dispatcher')->dispatch($event, AcquiaContentHubEvents::CREATE_CDF_OBJECT);

    $cdf = $event->getCdf($file->uuid());
    $this->assertCdfAttribute($cdf, 'file_scheme', 'private');
    $this->assertCdfAttribute($cdf, 'file_uri', $file->getFileUri());
  }

  /**
   * Tests minimum requirements.
   *
   * @covers ::getFile
   */
  public function testGetFile() {
    /** @var \Drupal\acquia_contenthub\Plugin\FileSchemeHandler\PrivateFileSchemeHandler $private_handler */
    $private_handler = $this->container->get('acquia_contenthub.file_scheme_handler.manager')
      ->createInstance('private');
    // file_location attribute is missing.
    $cdf = $this->generateFileCdf();
    $this->expectException(ContentHubFileException::class);
    $this->expectExceptionMessage('file_location is missing from file CDF');
    $this->expectExceptionCode(ContentHubFileException::MISSING_ATTRIBUTE);
    $private_handler->getFile($cdf);

    // file_uri attribute is missing.
    $attributes = [new CDFAttribute('file_location', CDFAttribute::TYPE_STRING, 'location')];
    $cdf = $this->generateFileCdf($attributes);
    $this->expectException(ContentHubFileException::class);
    $this->expectExceptionMessage('file_uri is missing from file CDF');
    $this->expectExceptionCode(ContentHubFileException::MISSING_ATTRIBUTE);
    $private_handler->getFile($cdf);

    // Directory is not writable.
    $attributes[] = new CDFAttribute('file_uri', CDFAttribute::TYPE_STRING, 'location');
    $cdf = $this->generateFileCdf($attributes);
    $fs = $this->prophesize(FileSystemInterface::class);
    $fs->dirname(Argument::type('string'))->willReturn('somedir');
    $dir = Argument::type('string');
    $fs->prepareDirectory($dir, Argument::type('int'))->willReturn(FALSE);
    $private_handler->setFileSystem($fs->reveal());
    $this->expectException(ContentHubFileException::class);
    $this->expectExceptionMessage('target directory (somedir) does not exist or not writable');
    $this->expectExceptionCode(ContentHubFileException::DIRECTORY_NOT_WRITABLE);
    $private_handler->getFile($cdf);

    // Failed to request remote file.
    $fs->prepareDirectory($dir, Argument::type('int'))->willReturn('prepared/dir');
    $private_handler->setFileSystem($fs->reveal());
    $common_actions = $this->prophesize(ContentHubCommonActions::class);
    $common_actions->requestRemoteEntity(
      Argument::type('string'), Argument::type('string'),
      Argument::type('string'), Argument::type('string')
    )->willThrow(new \Exception('exception message'));
    $this->container->set('acquia_contenthub.common_actions', $common_actions->reveal());
    $this->expectException(ContentHubFileException::class);
    $this->expectExceptionMessage('unable to request file from remote source: exception message');
    $this->expectExceptionCode(ContentHubFileException::REMOTE_REQUEST);
    $private_handler->getFile($cdf);

    // Successful remote request.
    $common_actions->requestRemoteEntity(
      Argument::type('string'), Argument::type('string'),
      Argument::type('string'), Argument::type('string')
    )->willThrow('some file content');
    $this->container->set('acquia_contenthub.common_actions', $common_actions->reveal());
    $fs->saveData(
      Argument::type('string'), Argument::type('string'),
      Argument::type('int')
    )->shouldBeCalled();
    $private_handler->setFileSystem($fs->reveal());
    $private_handler->getFile($cdf);
  }

  /**
   * Saves and returns a file entity based on the file name and scheme.
   *
   * Optionally override every values by using the $values parameter.
   *
   * @param string $file_name
   *   The file name. Used to generate uri.
   * @param string $scheme
   *   The applicable scheme.
   * @param array $values
   *   Values to override file entity fields.
   *
   * @return \Drupal\file\FileInterface
   *   The newly created file entity.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function createFileEntity(string $file_name, string $scheme, array $values = []): FileInterface {
    $path = explode('/', $file_name);
    $filemime = \Drupal::service('acquia_contenthub.drupal_bridge')->getMimeTypeFromExtension($file_name);
    $data = [
      'uuid' => \Drupal::service('uuid')->generate(),
      'langcode' => 'en',
      'uid' => 1,
      'filename' => end($path),
      'uri' => sprintf("$scheme://%s", implode('/', $path)),
      'filemime' => $filemime,
      'filesize' => rand(1000, 5000),
      'status' => 1,
      'created' => time(),
    ];
    $data = array_merge($data, $values);
    $file = File::create($data);
    $file->save();

    return $file;
  }

  /**
   * Asserts CDF attribute values.
   *
   * @param \Acquia\ContentHubClient\CDF\CDFObject $cdf
   *   The cdf under test.
   * @param string $attribute
   *   The attribute to evaluate.
   * @param mixed $expected
   *   The expected value of the attribute.
   */
  protected function assertCdfAttribute(CDFObject $cdf, string $attribute, $expected): void {
    $attr = $cdf->getAttribute($attribute);
    if (is_null($attr)) {
      Assert::fail("Attribute ($attribute) doesn't exist!");
    }

    Assert::assertEquals(
      $cdf->getAttribute($attribute)->getValue()[LanguageInterface::LANGCODE_NOT_SPECIFIED],
      $expected,
      'CDF attribute value and expected value do not match.'
    );
  }

}
