<?php

namespace Drupal\Tests\file\Functional\Rest;

use Drupal\file\Entity\File;
use Drupal\Tests\rest\Functional\BcTimestampNormalizerUnixTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
use Drupal\user\Entity\User;

abstract class FileResourceTestBase extends EntityResourceTestBase {

  use BcTimestampNormalizerUnixTestTrait;

  /**
   * {@inheritdoc}
   */
  public static $modules = ['file', 'user'];

  /**
   * {@inheritdoc}
   */
  protected static $entityTypeId = 'file';

  /**
   * @var \Drupal\file\FileInterface
   */
  protected $entity;

  /**
   * {@inheritdoc}
   */
  protected static $patchProtectedFieldNames = [
    'uri' => NULL,
    'filemime' => NULL,
    'filesize' => NULL,
    'status' => NULL,
    'changed' => NULL,
  ];

  /**
   * @var \Drupal\user\UserInterface
   */
  protected $author;

  /**
   * {@inheritdoc}
   */
  protected function setUpAuthorization($method) {
    switch ($method) {
      case 'GET':
        $this->grantPermissionsToTestedRole(['access content']);
        break;

      case 'PATCH':
      case 'DELETE':
        // \Drupal\file\FileAccessControlHandler::checkAccess() grants 'update'
        // and 'delete' access only to the user that owns the file. So there is
        // no permission to grant: instead, the file owner must be changed from
        // its default (user 1) to the current user.
        $this->makeCurrentUserFileOwner();
        break;
    }
  }

  /**
   * {@inheritdoc}
   */
  protected function grantPermissionsToTestedRole(array $permissions) {
    // testPatch() and testDelete() test the 'bc_entity_resource_permissions' BC
    // layer; also call makeCurrentUserFileOwner() then.
    if ($permissions === ['restful patch entity:file'] || $permissions === ['restful delete entity:file']) {
      $this->makeCurrentUserFileOwner();
    }
    parent::grantPermissionsToTestedRole($permissions);
  }

  /**
   * Makes the current user the file owner.
   */
  protected function makeCurrentUserFileOwner() {
    $account = static::$auth ? User::load(2) : User::load(0);
    $this->entity->setOwnerId($account->id());
    $this->entity->setOwner($account);
    $this->entity->save();
  }

  /**
   * {@inheritdoc}
   */
  protected function createEntity() {
    $this->author = User::load(1);

    $file = File::create();
    $file->setOwnerId($this->author->id());
    $file->setFilename('drupal.txt');
    $file->setMimeType('text/plain');
    $file->setFileUri('public://drupal.txt');
    $file->set('status', FILE_STATUS_PERMANENT);
    $file->save();

    file_put_contents($file->getFileUri(), 'Drupal');

    return $file;
  }

  /**
   * {@inheritdoc}
   */
  protected function getExpectedNormalizedEntity() {
    return [
      'changed' => [
        $this->formatExpectedTimestampItemValues($this->entity->getChangedTime()),
      ],
      'created' => [
        $this->formatExpectedTimestampItemValues((int) $this->entity->getCreatedTime()),
      ],
      'fid' => [
        [
          'value' => 1,
        ],
      ],
      'filemime' => [
        [
          'value' => 'text/plain',
        ],
      ],
      'filename' => [
        [
          'value' => 'drupal.txt',
        ],
      ],
      'filesize' => [
        [
          'value' => (int) $this->entity->getSize(),
        ],
      ],
      'langcode' => [
        [
          'value' => 'en',
        ],
      ],
      'status' => [
        [
          'value' => TRUE,
        ],
      ],
      'uid' => [
        [
          'target_id' => (int) $this->author->id(),
          'target_type' => 'user',
          'target_uuid' => $this->author->uuid(),
          'url' => base_path() . 'user/' . $this->author->id(),
        ],
      ],
      'uri' => [
        [
          'url' => base_path() . $this->siteDirectory . '/files/drupal.txt',
          'value' => 'public://drupal.txt',
        ],
      ],
      'uuid' => [
        [
          'value' => $this->entity->uuid(),
        ],
      ],
    ];
  }

  /**
   * {@inheritdoc}
   */
  protected function getNormalizedPostEntity() {
    return [
      'uid' => [
        [
          'target_id' => $this->author->id(),
        ],
      ],
      'filename' => [
        [
          'value' => 'drupal.txt',
        ],
      ],
    ];
  }

  /**
   * {@inheritdoc}
   */
  protected function getNormalizedPatchEntity() {
    return array_diff_key($this->getNormalizedPostEntity(), ['uid' => TRUE]);
  }

  /**
   * {@inheritdoc}
   */
  protected function getExpectedCacheContexts() {
    return [
      'user.permissions',
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function testPost() {
    // Drupal does not allow creating file entities independently. It allows you
    // to create file entities that are referenced from another entity (e.g. an
    // image for a node's image field).
    // For that purpose, there is the "file_upload" REST resource plugin.
    // @see \Drupal\file\FileAccessControlHandler::checkCreateAccess()
    // @see \Drupal\file\Plugin\rest\resource\FileUploadResource
    $this->markTestSkipped();
  }

  /**
   * {@inheritdoc}
   */
  protected function getExpectedUnauthorizedAccessMessage($method) {
    if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
      if ($method === 'DELETE') {
        return 'Only the file owner can update or delete the file entity.';
      }
      return parent::getExpectedUnauthorizedAccessMessage($method);
    }

    if ($method === 'GET') {
      return "The 'access content' permission is required.";
    }
    if ($method === 'PATCH' || $method === 'DELETE') {
      return 'Only the file owner can update or delete the file entity.';
    }
    return parent::getExpectedUnauthorizedAccessMessage($method);
  }

}
