<?php declare(strict_types = 1);

namespace MailPoet\API\JSON\v1;

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


use MailPoet\API\JSON\Endpoint as APIEndpoint;
use MailPoet\API\JSON\Error;
use MailPoet\API\JSON\Response;
use MailPoet\API\JSON\ResponseBuilders\DynamicSegmentsResponseBuilder;
use MailPoet\Config\AccessControl;
use MailPoet\ConflictException;
use MailPoet\Doctrine\Validator\ValidationException;
use MailPoet\Entities\SegmentEntity;
use MailPoet\Listing\Handler;
use MailPoet\Newsletter\Segment\NewsletterSegmentRepository;
use MailPoet\Segments\DynamicSegments\DynamicSegmentsListingRepository;
use MailPoet\Segments\DynamicSegments\Exceptions\InvalidFilterException;
use MailPoet\Segments\DynamicSegments\FilterDataMapper;
use MailPoet\Segments\DynamicSegments\SegmentSaveController;
use MailPoet\Segments\SegmentsRepository;
use MailPoet\Segments\SegmentSubscribersRepository;
use MailPoet\UnexpectedValueException;
use Throwable;

class DynamicSegments extends APIEndpoint {

  public $permissions = [
    'global' => AccessControl::PERMISSION_MANAGE_SEGMENTS,
  ];

  /** @var Handler */
  private $listingHandler;

  /** @var DynamicSegmentsListingRepository */
  private $dynamicSegmentsListingRepository;

  /** @var SegmentsRepository */
  private $segmentsRepository;

  /** @var DynamicSegmentsResponseBuilder */
  private $segmentsResponseBuilder;

  /** @var SegmentSaveController */
  private $saveController;

  /** @var SegmentSubscribersRepository */
  private $segmentSubscribersRepository;

  /** @var FilterDataMapper */
  private $filterDataMapper;

  /** @var NewsletterSegmentRepository */
  private $newsletterSegmentRepository;

  public function __construct(
    Handler $handler,
    DynamicSegmentsListingRepository $dynamicSegmentsListingRepository,
    DynamicSegmentsResponseBuilder $segmentsResponseBuilder,
    SegmentsRepository $segmentsRepository,
    SegmentSubscribersRepository $segmentSubscribersRepository,
    FilterDataMapper $filterDataMapper,
    SegmentSaveController $saveController,
    NewsletterSegmentRepository $newsletterSegmentRepository
  ) {
    $this->listingHandler = $handler;
    $this->dynamicSegmentsListingRepository = $dynamicSegmentsListingRepository;
    $this->segmentsResponseBuilder = $segmentsResponseBuilder;
    $this->segmentsRepository = $segmentsRepository;
    $this->saveController = $saveController;
    $this->segmentSubscribersRepository = $segmentSubscribersRepository;
    $this->filterDataMapper = $filterDataMapper;
    $this->newsletterSegmentRepository = $newsletterSegmentRepository;
  }

  public function get($data = []) {
    if (isset($data['id'])) {
      $id = (int)$data['id'];
    } else {
      return $this->errorResponse([
        Error::BAD_REQUEST => __('Missing mandatory argument `id`.', 'mailpoet'),
      ]);
    }

    $segment = $this->segmentsRepository->findOneById($id);
    if (!$segment instanceof SegmentEntity) {
      return $this->errorResponse([
        Error::NOT_FOUND => __('This segment does not exist.', 'mailpoet'),
      ]);
    }

    return $this->successResponse($this->segmentsResponseBuilder->build($segment));
  }

  public function getCount($data = []) {
    try {
      $filterData = $this->filterDataMapper->map($data);
      $count = $this->segmentSubscribersRepository->getDynamicSubscribersCount($filterData);
      return $this->successResponse([
        'count' => $count,
      ]);
    } catch (InvalidFilterException $e) {
      return $this->errorResponse([
        Error::BAD_REQUEST => $this->getErrorString($e),
      ], [], Response::STATUS_BAD_REQUEST);
    }
  }

  public function save($data) {
    try {
      $data['name'] = isset($data['name']) ? sanitize_text_field($data['name']) : '';
      $data['description'] = isset($data['description']) ? sanitize_textarea_field($data['description']) : '';
      $segment = $this->saveController->save($data);
      return $this->successResponse($this->segmentsResponseBuilder->build($segment));
    } catch (InvalidFilterException $e) {
      return $this->errorResponse([
        Error::BAD_REQUEST => $this->getErrorString($e),
      ], [], Response::STATUS_BAD_REQUEST);
    } catch (ConflictException $e) {
      return $this->badRequest([
        Error::BAD_REQUEST => __('Another record already exists. Please specify a different "name".', 'mailpoet'),
      ]);
    } catch (ValidationException $exception) {
      return $this->badRequest([
        Error::BAD_REQUEST => __('Please specify a name.', 'mailpoet'),
      ]);
    }
  }

  public function duplicate($data = []) {
    $segment = $this->getSegment($data);

    if ($segment instanceof SegmentEntity) {
      try {
        $duplicate = $this->saveController->duplicate($segment);
      } catch (Throwable $e) {
        return $this->errorResponse([
          // translators: %s is the error message
          Error::UNKNOWN => sprintf(__('Duplicating of segment failed: %s', 'mailpoet'), $e->getMessage()),
        ], [], Response::STATUS_UNKNOWN);
      }
      return $this->successResponse(
        $this->segmentsResponseBuilder->build($duplicate),
        ['count' => 1]
      );
    } else {
      return $this->errorResponse([
        Error::NOT_FOUND => __('This segment does not exist.', 'mailpoet'),
      ]);
    }
  }

  private function getErrorString(InvalidFilterException $e) {
    switch ($e->getCode()) {
      case InvalidFilterException::MISSING_TYPE:
        return __('The segment type is missing.', 'mailpoet');
      case InvalidFilterException::INVALID_TYPE:
        return __('The segment type is unknown.', 'mailpoet');
      case InvalidFilterException::MISSING_ROLE:
        return __('Please select a user role.', 'mailpoet');
      case InvalidFilterException::MISSING_ACTION:
      case InvalidFilterException::INVALID_EMAIL_ACTION:
        return __('Please select an email action.', 'mailpoet');
      case InvalidFilterException::MISSING_NEWSLETTER_ID:
        return __('Please select an email.', 'mailpoet');
      case InvalidFilterException::MISSING_PRODUCT_ID:
        return __('Please select a product.', 'mailpoet');
      case InvalidFilterException::MISSING_COUNTRY:
        return __('Please select a country.', 'mailpoet');
      case InvalidFilterException::MISSING_CATEGORY_ID:
        return __('Please select a category.', 'mailpoet');
      case InvalidFilterException::MISSING_VALUE:
        return __('Please fill all required values.', 'mailpoet');
      case InvalidFilterException::MISSING_NUMBER_OF_ORDERS_FIELDS:
        return __('Please select a type for the comparison, a number of orders and a number of days.', 'mailpoet');
      case InvalidFilterException::MISSING_TOTAL_SPENT_FIELDS:
      case InvalidFilterException::MISSING_SINGLE_ORDER_VALUE_FIELDS:
      case InvalidFilterException::MISSING_AVERAGE_SPENT_FIELDS:
        return __('Please select a type for the comparison, an amount and a number of days.', 'mailpoet');
      case InvalidFilterException::MISSING_FILTER:
        return __('Please add at least one condition for filtering.', 'mailpoet');
      case InvalidFilterException::MISSING_OPERATOR:
        return __('Please select a type for the comparison.', 'mailpoet');
      default:
        return __('An error occurred while saving data.', 'mailpoet');
    }
  }

  public function trash($data = []) {
    if (!isset($data['id'])) {
      return $this->errorResponse([
        Error::BAD_REQUEST => __('Missing mandatory argument `id`.', 'mailpoet'),
      ]);
    }

    $segment = $this->getSegment($data);
    if ($segment === null) {
      return $this->errorResponse([
        Error::NOT_FOUND => __('This segment does not exist.', 'mailpoet'),
      ]);
    }

    $activelyUsedNewslettersSubjects = $this->newsletterSegmentRepository->getSubjectsOfActivelyUsedEmailsForSegments([$segment->getId()]);
    if (isset($activelyUsedNewslettersSubjects[$segment->getId()])) {
      return $this->badRequest([
        Error::BAD_REQUEST => str_replace(
          '%1$s',
          "'" . join("', '", $activelyUsedNewslettersSubjects[$segment->getId()] ) . "'",
          // translators: %1$s is a comma-seperated list of emails for which the segment is used.
          _x('Segment cannot be deleted because it’s used for %1$s email', 'Alert shown when trying to delete segment, which is assigned to any automatic emails.', 'mailpoet')
        ),
      ]);
    }

    $this->segmentsRepository->bulkTrash([$segment->getId()], SegmentEntity::TYPE_DYNAMIC);
    return $this->successResponse(
      $this->segmentsResponseBuilder->build($segment),
      ['count' => 1]
    );
  }

  public function restore($data = []) {
    if (!isset($data['id'])) {
      return $this->errorResponse([
        Error::BAD_REQUEST => __('Missing mandatory argument `id`.', 'mailpoet'),
      ]);
    }

    $segment = $this->getSegment($data);
    if ($segment === null) {
      return $this->errorResponse([
        Error::NOT_FOUND => __('This segment does not exist.', 'mailpoet'),
      ]);
    }

    $this->segmentsRepository->bulkRestore([$segment->getId()], SegmentEntity::TYPE_DYNAMIC);
    return $this->successResponse(
      $this->segmentsResponseBuilder->build($segment),
      ['count' => 1]
    );
  }

  public function delete($data = []) {
    if (!isset($data['id'])) {
      return $this->errorResponse([
        Error::BAD_REQUEST => __('Missing mandatory argument `id`.', 'mailpoet'),
      ]);
    }

    $segment = $this->getSegment($data);
    if ($segment === null) {
      return $this->errorResponse([
        Error::NOT_FOUND => __('This segment does not exist.', 'mailpoet'),
      ]);
    }

    $this->segmentsRepository->bulkDelete([$segment->getId()], SegmentEntity::TYPE_DYNAMIC);
    return $this->successResponse(null, ['count' => 1]);
  }

  public function listing($data = []) {
    $data['params'] = $data['params'] ?? ['segments']; // Dummy param to apply constraints properly
    $definition = $this->listingHandler->getListingDefinition($data);
    $items = $this->dynamicSegmentsListingRepository->getData($definition);
    $count = $this->dynamicSegmentsListingRepository->getCount($definition);
    $filters = $this->dynamicSegmentsListingRepository->getFilters($definition);
    $groups = $this->dynamicSegmentsListingRepository->getGroups($definition);
    $segments = $this->segmentsResponseBuilder->buildForListing($items);

    return $this->successResponse($segments, [
      'count' => $count,
      'filters' => $filters,
      'groups' => $groups,
    ]);
  }

  public function bulkAction($data = []) {
    $definition = $this->listingHandler->getListingDefinition($data['listing']);
    $ids = $this->dynamicSegmentsListingRepository->getActionableIds($definition);
    if ($data['action'] === 'trash') {
      $count = $this->segmentsRepository->bulkTrash($ids, SegmentEntity::TYPE_DYNAMIC);
    } elseif ($data['action'] === 'restore') {
      $count = $this->segmentsRepository->bulkRestore($ids, SegmentEntity::TYPE_DYNAMIC);
    } elseif ($data['action'] === 'delete') {
      $count = $this->segmentsRepository->bulkDelete($ids, SegmentEntity::TYPE_DYNAMIC);
    } else {
      throw UnexpectedValueException::create()
        ->withErrors([Error::BAD_REQUEST => "Invalid bulk action '{$data['action']}' provided."]);
    }
    return $this->successResponse(null, ['count' => $count]);
  }

  private function getSegment(array $data): ?SegmentEntity {
    return isset($data['id'])
      ? $this->segmentsRepository->findOneById((int)$data['id'])
      : null;
  }
}
