<?php declare(strict_types = 1);

namespace MailPoet\Segments\DynamicSegments\Filters;

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


use MailPoet\CustomFields\CustomFieldsRepository;
use MailPoet\Entities\CustomFieldEntity;
use MailPoet\Entities\DynamicSegmentFilterData;
use MailPoet\Entities\DynamicSegmentFilterEntity;
use MailPoet\Entities\SubscriberCustomFieldEntity;
use MailPoet\Entities\SubscriberEntity;
use MailPoet\Segments\DynamicSegments\Exceptions\InvalidFilterException;
use MailPoet\Util\Helpers;
use MailPoet\Util\Security;
use MailPoetVendor\Doctrine\DBAL\Query\QueryBuilder;
use MailPoetVendor\Doctrine\ORM\EntityManager;

class MailPoetCustomFields implements Filter {
  const TYPE = 'mailpoetCustomField';

  /** @var EntityManager */
  private $entityManager;

  /** @var CustomFieldsRepository */
  private $customFieldsRepository;

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

  public function apply(QueryBuilder $queryBuilder, DynamicSegmentFilterEntity $filter): QueryBuilder {
    $filterData = $filter->getFilterData();
    $customFieldType = $filterData->getParam('custom_field_type');
    $customFieldId = $filterData->getParam('custom_field_id');
    $parameterSuffix = (string)($filter->getId() ?? Security::generateRandomString());
    $customFieldIdParam = 'customFieldId' . $parameterSuffix;

    $subscribersTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
    $subscribersCustomFieldTable = $this->entityManager->getClassMetadata(SubscriberCustomFieldEntity::class)->getTableName();

    $queryBuilder->leftJoin(
      $subscribersTable,
      $subscribersCustomFieldTable,
      'subscribers_custom_field',
      "$subscribersTable.id = subscribers_custom_field.subscriber_id AND subscribers_custom_field.custom_field_id = :$customFieldIdParam"
    );
    $queryBuilder->setParameter($customFieldIdParam, $customFieldId);

    $valueParam = 'value' . $parameterSuffix;
    if (
      ($customFieldType === CustomFieldEntity::TYPE_TEXT)
      || ($customFieldType === CustomFieldEntity::TYPE_TEXTAREA)
      || ($customFieldType === CustomFieldEntity::TYPE_RADIO)
      || ($customFieldType === CustomFieldEntity::TYPE_SELECT)
    ) {
      $queryBuilder = $this->applyEquality($queryBuilder, $filter, $valueParam);
    }
    if ($customFieldType === CustomFieldEntity::TYPE_CHECKBOX) {
      $queryBuilder = $this->applyForCheckbox($queryBuilder, $filter);
    }
    if ($customFieldType === CustomFieldEntity::TYPE_DATE) {
      $queryBuilder = $this->applyForDate($queryBuilder, $filter, $valueParam);
    }
    return $queryBuilder;
  }

  private function applyForDate(QueryBuilder $queryBuilder, DynamicSegmentFilterEntity $filter, string $valueParam): QueryBuilder {
    $filterData = $filter->getFilterData();
    $dateType = $filterData->getParam('date_type');
    $value = $filterData->getParam('value');
    $operator = $filterData->getParam('operator');
    $queryBuilder->setParameter($valueParam, $value);
    if ($operator === DynamicSegmentFilterData::IS_BLANK) {
      $queryBuilder->andWhere('subscribers_custom_field.value IS NULL');
      return $queryBuilder;
    } elseif ($operator === DynamicSegmentFilterData::IS_NOT_BLANK) {
      $queryBuilder->andWhere('subscribers_custom_field.value IS NOT NULL');
      return $queryBuilder;
    } elseif ($dateType === 'month') {
      return $this->applyForDateMonth($queryBuilder, $valueParam);
    } elseif ($dateType === 'year') {
      return $this->applyForDateYear($queryBuilder, $operator, $valueParam);
    }
    return $this->applyForDateEqual($queryBuilder, $operator, $valueParam);
  }

  private function applyForDateMonth(QueryBuilder $queryBuilder, string $valueParam): QueryBuilder {
    $queryBuilder->andWhere("month(subscribers_custom_field.value) = month(:$valueParam)");
    return $queryBuilder;
  }

  private function applyForDateYear(QueryBuilder $queryBuilder, ?string $operator, string $valueParam): QueryBuilder {
    if ($operator === 'before') {
      $queryBuilder->andWhere("year(subscribers_custom_field.value) < year(:$valueParam)");
    } elseif ($operator === 'after') {
      $queryBuilder->andWhere("year(subscribers_custom_field.value) > year(:$valueParam)");
    } else {
      $queryBuilder->andWhere("year(subscribers_custom_field.value) = year(:$valueParam)");
    }
    return $queryBuilder;
  }

  private function applyForDateEqual(QueryBuilder $queryBuilder, ?string $operator, string $valueParam): QueryBuilder {
    if ($operator === 'before') {
      $queryBuilder->andWhere("subscribers_custom_field.value < :$valueParam");
    } elseif ($operator === 'after') {
      $queryBuilder->andWhere("subscribers_custom_field.value > :$valueParam");
    } else {
      // we always save full date in the database: 2018-03-01 00:00:00
      // so this works even for year_month where we save the first day of the month
      $queryBuilder->andWhere("subscribers_custom_field.value = :$valueParam");
    }
    return $queryBuilder;
  }

  private function applyForCheckbox(QueryBuilder $queryBuilder, DynamicSegmentFilterEntity $filter): QueryBuilder {
    $filterData = $filter->getFilterData();
    $value = $filterData->getParam('value');
    $operator = $filterData->getParam('operator');

    if ($operator === DynamicSegmentFilterData::IS_BLANK) {
      $queryBuilder->andWhere('subscribers_custom_field.value IS NULL');
    } elseif ($operator === DynamicSegmentFilterData::IS_NOT_BLANK) {
      $queryBuilder->andWhere('subscribers_custom_field.value IS NOT NULL');
    } elseif ($value === '1') {
      $queryBuilder->andWhere('subscribers_custom_field.value = 1');
    } elseif ($value === '0') {
      $queryBuilder->andWhere('subscribers_custom_field.value <> 1');
    }
    return $queryBuilder;
  }

  private function applyEquality(QueryBuilder $queryBuilder, DynamicSegmentFilterEntity $filter, string $valueParam): QueryBuilder {
    $filterData = $filter->getFilterData();

    $operator = $filterData->getParam('operator');
    $value = $filterData->getParam('value');

    $requiresValue = !in_array($operator, [DynamicSegmentFilterData::IS_BLANK, DynamicSegmentFilterData::IS_NOT_BLANK]);

    if ($requiresValue && !is_string($value)) {
      throw new InvalidFilterException('Missing required value', InvalidFilterException::MISSING_VALUE);
    }

    /** @var string $value - for PhpStan */

    if ($operator === 'equals') {
      $queryBuilder->andWhere("subscribers_custom_field.value = :$valueParam");
      $queryBuilder->setParameter($valueParam, $value);
    } elseif ($operator === 'not_equals') {
      $queryBuilder->andWhere("subscribers_custom_field.value != :$valueParam");
      $queryBuilder->orWhere('subscribers_custom_field.value IS NULL');
      $queryBuilder->setParameter($valueParam, $value);
    } elseif ($operator === 'more_than') {
      $queryBuilder->andWhere("subscribers_custom_field.value > :$valueParam");
      $queryBuilder->setParameter($valueParam, $value);
    } elseif ($operator === 'less_than') {
      $queryBuilder->andWhere("subscribers_custom_field.value < :$valueParam");
      $queryBuilder->setParameter($valueParam, $value);
    } elseif ($operator === DynamicSegmentFilterData::IS_BLANK) {
      $queryBuilder->andWhere("subscribers_custom_field.value IS NULL OR subscribers_custom_field.value = ''");
    } elseif ($operator === DynamicSegmentFilterData::IS_NOT_BLANK) {
      $queryBuilder->andWhere("subscribers_custom_field.value IS NOT NULL AND subscribers_custom_field.value != ''");
    } elseif ($operator === 'not_contains') {
      $queryBuilder->andWhere("subscribers_custom_field.value NOT LIKE :$valueParam");
      $queryBuilder->setParameter($valueParam, '%' . Helpers::escapeSearch($value) . '%');
    } else {
      $queryBuilder->andWhere("subscribers_custom_field.value LIKE :$valueParam");
      $queryBuilder->setParameter($valueParam, '%' . Helpers::escapeSearch($value) . '%');
    }

    return $queryBuilder;
  }

  public function getLookupData(DynamicSegmentFilterData $filterData): array {
    $lookupData = [
      'customFields' => [],
    ];
    $customFieldId = $filterData->getIntParam('custom_field_id');
    $customField = $this->customFieldsRepository->findOneById($customFieldId);
    if ($customField instanceof CustomFieldEntity) {
      $lookupData['customFields'][$customFieldId] = $customField->getName();
    }
    return $lookupData;
  }
}
