<?php declare(strict_types = 1);

namespace MailPoet\CustomFields\RestApi\Endpoints;

if (!defined('ABSPATH')) exit;


use InvalidArgumentException;
use MailPoet\API\REST\Request;
use MailPoet\API\REST\Response;
use MailPoet\CustomFields\ApiDataSanitizer;
use MailPoet\CustomFields\CustomFieldsRepository;
use MailPoet\CustomFields\RestApi\CustomFieldApiException;
use MailPoet\Entities\CustomFieldEntity;
use MailPoet\Validator\Builder;
use MailPoetVendor\Doctrine\DBAL\Exception\UniqueConstraintViolationException;

class CustomFieldsPutEndpoint extends CustomFieldsEndpoint {
  /** @var CustomFieldsRepository */
  private $customFieldsRepository;

  /** @var ApiDataSanitizer */
  private $apiDataSanitizer;

  public function __construct(
    CustomFieldsRepository $customFieldsRepository,
    ApiDataSanitizer $apiDataSanitizer
  ) {
    $this->customFieldsRepository = $customFieldsRepository;
    $this->apiDataSanitizer = $apiDataSanitizer;
  }

  public function handle(Request $request): Response {
    $id = $this->getId($request);
    $customField = $this->customFieldsRepository->findOneById($id);
    if (!$customField instanceof CustomFieldEntity || $customField->getDeletedAt() !== null) {
      throw new CustomFieldApiException(
        __('The custom field does not exist.', 'mailpoet'),
        404,
        'mailpoet_custom_fields_not_found'
      );
    }

    $requestData = $this->getRequestData($request);
    if (
      $requestData['type'] !== ''
      && $requestData['type'] !== $customField->getType()
      && $this->customFieldsRepository->hasSubscriberValues($id)
    ) {
      throw new CustomFieldApiException(
        __('The custom field type cannot be changed because subscribers have values stored for this field.', 'mailpoet'),
        409,
        'mailpoet_custom_fields_type_locked'
      );
    }

    try {
      $data = $this->apiDataSanitizer->sanitize($requestData);
    } catch (InvalidArgumentException $exception) {
      throw new CustomFieldApiException(
        $exception->getMessage(),
        400,
        'mailpoet_custom_fields_invalid_data',
        [],
        $exception
      );
    }

    $existing = $this->customFieldsRepository->findOneBy(['name' => $data['name']]);
    if ($existing instanceof CustomFieldEntity && $existing->getId() !== $id) {
      throw new CustomFieldApiException(
        __('A custom field with this name already exists.', 'mailpoet'),
        409,
        'mailpoet_custom_fields_duplicate'
      );
    }

    $data['id'] = $id;
    try {
      $customField = $this->customFieldsRepository->createOrUpdate($data);
    } catch (UniqueConstraintViolationException $exception) {
      // Concurrent request renamed another field to this name between the duplicate-name check and the update.
      throw new CustomFieldApiException(
        __('A custom field with this name already exists.', 'mailpoet'),
        409,
        'mailpoet_custom_fields_duplicate',
        [],
        $exception
      );
    }
    return new Response($this->buildItem($customField));
  }

  private function getId(Request $request): int {
    $rawId = $request->getParam('id');
    return (int)(is_scalar($rawId) ? $rawId : 0);
  }

  /**
   * @return array{name: string, type: string, params: array}
   */
  private function getRequestData(Request $request): array {
    $rawName = $request->getParam('name');
    $rawType = $request->getParam('type');
    $rawParams = $request->getParam('params');
    return [
      'name' => sanitize_text_field(is_scalar($rawName) ? (string)$rawName : ''),
      'type' => sanitize_key(is_scalar($rawType) ? (string)$rawType : ''),
      'params' => is_array($rawParams) ? $this->sanitizeParams($rawParams) : [],
    ];
  }

  private function sanitizeParams(array $params): array {
    $sanitized = [];
    foreach ($params as $key => $value) {
      if (is_array($value)) {
        $sanitized[$key] = $this->sanitizeParams($value);
      } elseif (is_scalar($value)) {
        $sanitized[$key] = sanitize_text_field((string)$value);
      }
    }
    return $sanitized;
  }

  public static function getRequestSchema(): array {
    return [
      'id' => Builder::integer()->required(),
      'name' => Builder::string()->required()->minLength(1),
      'type' => Builder::string()->required()->minLength(1),
      'params' => Builder::object(),
    ];
  }
}
