<?php

namespace Drupal\pagedesigner\Plugin\rest\resource;

use Drupal\Component\Serialization\Json;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManager;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\node\Entity\Node;
use Drupal\pagedesigner\Entity\Element;
use Drupal\pagedesigner\Service\ElementHandler;
use Drupal\pagedesigner\Service\Locker;
use Drupal\rest\ModifiedResourceResponse;
use Drupal\rest\Plugin\ResourceBase;
use Drupal\rest\ResourceResponse;
use Drupal\ui_patterns\UiPatternsManager;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;

/**
 * Provides a resource to get view modes by entity and bundle.
 *
 * @RestResource(
 *   id = "pagedesigner_element",
 *   label = @Translation("Pagedesigner element resource"),
 *   uri_paths = {
 *     "canonical" = "/pagedesigner/element/{id}",
 *     "create" = "/pagedesigner/element",
 *   }
 * )
 */
class ElementResource extends ResourceBase {

  /**
   * Constructs a new ElementResource object.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param array $serializer_formats
   *   The available serialization formats.
   * @param \Psr\Log\LoggerInterface $logger
   *   A logger instance.
   * @param \Drupal\Core\Session\AccountProxyInterface $currentUser
   *   A current user instance.
   * @param \Drupal\pagedesigner\Service\ElementHandler $elementHandler
   *   The element handler.
   * @param \Drupal\ui_patterns\UiPatternsManager $patternManager
   *   The pattern manager.
   * @param \Drupal\Core\Language\LanguageManager $languageManager
   *   The language manager.
   * @param \Drupal\pagedesigner\Service\Locker $locker
   *   The locker service.
   */
  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    array $serializer_formats,
    LoggerInterface $logger,
    protected AccountProxyInterface $currentUser,
    protected ElementHandler $elementHandler,
    protected UiPatternsManager $patternManager,
    protected LanguageManager $languageManager,
    protected Locker $locker,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
          $configuration,
          $plugin_id,
          $plugin_definition,
          $container->getParameter('serializer.formats'),
          $container->get('logger.factory')->get('pagedesigner'),
          $container->get('current_user'),
          $container->get('pagedesigner.service.element_handler'),
          $container->get('plugin.manager.ui_patterns'),
          $container->get('language_manager'),
          $container->get('pagedesigner.locker')
      );
  }

  /**
   * Responds to GET requests.
   *
   * @param int $id
   *   ID of the entity.
   *
   * @return \Drupal\rest\ResourceResponse
   *   The HTTP response object.
   */
  public function get($id = NULL) {

    $build = [];
    if (!is_numeric($id)) {
      throw new BadRequestHttpException('The entity key must be numeric.');
    }
    $entity = Element::load($id);
    if ($entity == NULL) {
      throw new NotFoundHttpException('The entity does not exist.');
    }

    if ($entity->isTranslatable()) {
      $language = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId();
      if (!$entity->hasTranslation($language)) {
        throw new UnprocessableEntityHttpException('The entity does not exist in the given language.');
      }
      $entity = $entity->getTranslation($language);
    }

    if ($entity != NULL) {
      $build = $this->elementHandler->serialize($entity);
    }

    $response = new ResourceResponse($build, 200);
    $response->addCacheableDependency(['#cache' => ['max-age' => 0]]);
    return $response;

  }

  /**
   * Responds to POST requests.
   *
   *   The request.
   *
   * @return \Drupal\rest\ModifiedResourceResponse
   *   The HTTP response object.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
   *   Throws exception expected.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
   *   If the request is malformed.
   */
  public function post(Request $request) {
    $requestContent = Json::decode($request->getContent());

    if (empty($requestContent['pattern'])) {
      throw new BadRequestHttpException('The pattern key is mandatory for the post requests.');
    }
    if (empty($requestContent['container'])) {
      throw new BadRequestHttpException('The container key is mandatory for the post requests.');
    }
    if (empty($requestContent['entity'])) {
      throw new BadRequestHttpException('The entity key is mandatory for the post requests.');
    }
    if (empty($requestContent['parent']) && $requestContent['parent'] != NULL) {
      throw new BadRequestHttpException('The parent key is mandatory for the post requests.');
    }
    if (!$this->currentUser->hasPermission('edit pagedesigner element entities')) {
      throw new AccessDeniedHttpException('You are not allowed to create pagedesigner content');
    }

    $type = NULL;
    $patternDefinition = NULL;
    $patternDefinitions = $this->patternManager->getDefinitions();
    $this->elementHandler->collectPatterns($patternDefinitions);
    foreach ($patternDefinitions as $plugin_id => $definition) {
      if ($plugin_id == $requestContent['pattern']) {
        $patternDefinition = $definition;
      }
    }
    if ($patternDefinition === NULL) {
      throw new UnprocessableEntityHttpException('The given pattern is not registered.');
    }
    $type = $patternDefinition->getAdditional()['type'];
    if (empty($type)) {
      throw new UnprocessableEntityHttpException('No type given in pattern definition.');
    }
    $isDesignerPattern = FALSE;
    if (isset($patternDefinition->getAdditional()['designer'])) {
      $isDesignerPattern = !empty($patternDefinition->getAdditional()['designer']) || $patternDefinition->getAdditional()['designer'] == 1;
    }
    $designerPermission = $this->currentUser->hasPermission('access pagedesigner designer patterns');
    // Check if the pattern is for designer only.
    if ($isDesignerPattern && !$designerPermission) {
      throw new AccessDeniedHttpException('You are not allowed to create designer elements.');
    }
    $identifier = $requestContent['tabIdentifier'];
    $parentEntity = Node::load($requestContent['entity']);
    if (!$this->updateLock($parentEntity, $identifier)) {
      throw new AccessDeniedHttpException('The entity is now locked by another user.');
    }
    $entity = $this->elementHandler->generate($patternDefinition, $requestContent);
    $build = $this->elementHandler->describe($entity);
    return new ModifiedResourceResponse($build, 200);
  }

  /**
   * Responds to PATCH requests.
   *
   * @param int $id
   *   The entity key.
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request.
   *
   * @return \Drupal\rest\ModifiedResourceResponse
   *   The HTTP response object.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
   *   Throws exception expected.
   */
  public function patch($id, Request $request) {
    $requestContent = Json::decode($request->getContent());

    if (!is_numeric($id)) {
      throw new BadRequestHttpException('The entity key must be numeric.');
    }

    if (!$this->currentUser->hasPermission('edit pagedesigner element entities')) {
      throw new AccessDeniedHttpException('You are not allowed to edit pagedesigner content');
    }

    $entity = Element::load($id);
    if ($entity == NULL) {
      throw new NotFoundHttpException('The entity does not exist.');
    }
    $language = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId();
    if (!$entity->hasTranslation($language)) {
      throw new UnprocessableEntityHttpException('The entity does not exist in the given language.');
    }
    $entity = $entity->getTranslation($language);

    $identifier = $requestContent['tabIdentifier'];
    if (!$this->updateLock($entity->entity->entity, $identifier)) {
      throw new AccessDeniedHttpException('The entity is now locked by another user.');
    }
    $this->elementHandler->patch($entity, $requestContent);
    $build = $this->elementHandler->serialize($entity);
    return new ModifiedResourceResponse($build, 200);
  }

  /**
   * Responds to DELETE requests.
   *
   * @param int $id
   *   ID of the entity.
   *
   * @return \Drupal\rest\ModifiedResourceResponse
   *   The HTTP response object.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
   *   Throws exception expected.
   */
  public function delete($id) {
    if (!is_numeric($id)) {
      throw new BadRequestHttpException('The entity key must be numeric.');
    }
    if (!$this->currentUser->hasPermission('edit pagedesigner element entities')) {
      throw new AccessDeniedHttpException('You are not allowed to delete pagedesigner content');
    }

    $entity = Element::load($id);
    if ($entity == NULL) {
      throw new NotFoundHttpException('The entity does not exist.');
    }
    $language = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId();
    if (!$entity->hasTranslation($language)) {
      throw new UnprocessableEntityHttpException('The entity does not exist in the given language.');
    }
    $entity = $entity->getTranslation($language);
    if (!$this->updateLock($entity->entity->entity)) {
      throw new AccessDeniedHttpException('The entity is now locked by another user.');
    }
    $build = $this->elementHandler->delete($entity);
    return new ModifiedResourceResponse($build, 200);
  }

  /**
   * Update the lock on altering the element.
   *
   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
   *   The altered content entity.
   * @param string $identifier
   *   The identifier of the lock.
   */
  protected function updateLock(ContentEntityInterface $entity, $identifier = NULL) {
    $locker = $this->locker->setEntity($entity);
    if ($identifier == NULL) {
      $identifier = $this->locker->getIdentifier();
    }
    return $locker->acquire($identifier);
  }

}
