<?php

declare(strict_types=1);

namespace MauticPlugin\DealFlowBundle\EventSubscriber;

use Mautic\LeadBundle\Event\LeadListFilteringEvent;
use Mautic\LeadBundle\Event\LeadListFiltersChoicesEvent;
use Mautic\LeadBundle\LeadEvents;
use Mautic\LeadBundle\Model\ListModel;
use Mautic\LeadBundle\Segment\OperatorOptions;
use MauticPlugin\DealFlowBundle\Entity\Deal;
use MauticPlugin\DealFlowBundle\Entity\DealContactAssociation;
use MauticPlugin\DealFlowBundle\Service\AutomationOptionsProvider;
use MauticPlugin\DealFlowBundle\Service\LicenseService;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

final class LeadListSubscriber implements EventSubscriberInterface
{
    private const FIELD_PIPELINE = 'dealflow_pipeline_id';
    private const FIELD_STAGE = 'dealflow_stage_id';
    private const FIELD_STATUS = 'dealflow_status';
    private const FIELD_AMOUNT = 'dealflow_amount';
    private const FIELD_ROLE = 'dealflow_stakeholder_role';

    public function __construct(
        private readonly AutomationOptionsProvider $optionsProvider,
        private readonly ListModel $listModel,
        private readonly LicenseService $licenseService,
    ) {
    }

    public static function getSubscribedEvents(): array
    {
        return [
            LeadEvents::LIST_FILTERS_CHOICES_ON_GENERATE => ['onFilterChoiceFieldsGenerate', 0],
            LeadEvents::LIST_FILTERS_ON_FILTERING => ['onLeadListFiltering', 0],
        ];
    }

    public function onFilterChoiceFieldsGenerate(LeadListFiltersChoicesEvent $event): void
    {
        if (!$this->licenseService->isLicensed()) {
            return;
        }

        if (!$event->isForSegmentation()) {
            return;
        }

        $event->addChoice('lead', self::FIELD_PIPELINE, [
            'label' => 'Deal Flow pipeline',
            'properties' => [
                'type' => 'select',
                'list' => $this->optionsProvider->getPipelineChoices(),
            ],
            'operators' => $this->listModel->getOperatorsForFieldType([
                'include' => [OperatorOptions::EQUAL_TO, OperatorOptions::NOT_EQUAL_TO],
            ]),
            'object' => 'lead',
        ]);

        $event->addChoice('lead', self::FIELD_STAGE, [
            'label' => 'Deal Flow stage',
            'properties' => [
                'type' => 'select',
                'list' => $this->optionsProvider->getFlatStageChoices(),
            ],
            'operators' => $this->listModel->getOperatorsForFieldType([
                'include' => [OperatorOptions::EQUAL_TO, OperatorOptions::NOT_EQUAL_TO],
            ]),
            'object' => 'lead',
        ]);

        $event->addChoice('lead', self::FIELD_STATUS, [
            'label' => 'Deal Flow status',
            'properties' => [
                'type' => 'select',
                'list' => $this->optionsProvider->getStatusChoices(),
            ],
            'operators' => $this->listModel->getOperatorsForFieldType([
                'include' => [OperatorOptions::EQUAL_TO, OperatorOptions::NOT_EQUAL_TO],
            ]),
            'object' => 'lead',
        ]);

        $roleChoices = $this->optionsProvider->getStakeholderRoleChoices();
        if ([] !== $roleChoices) {
            $event->addChoice('lead', self::FIELD_ROLE, [
                'label' => 'Deal Flow stakeholder role',
                'properties' => [
                    'type' => 'select',
                    'list' => $roleChoices,
                ],
                'operators' => $this->listModel->getOperatorsForFieldType([
                    'include' => [OperatorOptions::EQUAL_TO, OperatorOptions::NOT_EQUAL_TO],
                ]),
                'object' => 'lead',
            ]);
        }

        $event->addChoice('lead', self::FIELD_AMOUNT, [
            'label' => 'Deal Flow amount',
            'properties' => [
                'type' => 'number',
            ],
            'operators' => $this->listModel->getOperatorsForFieldType([
                'include' => [
                    OperatorOptions::EQUAL_TO,
                    OperatorOptions::NOT_EQUAL_TO,
                    OperatorOptions::GREATER_THAN,
                    OperatorOptions::GREATER_THAN_OR_EQUAL,
                    OperatorOptions::LESS_THAN,
                    OperatorOptions::LESS_THAN_OR_EQUAL,
                ],
            ]),
            'object' => 'lead',
        ]);
    }

    public function onLeadListFiltering(LeadListFilteringEvent $event): void
    {
        if (!$this->licenseService->isLicensed()) {
            return;
        }

        $details = $event->getDetails();
        $field = (string) ($details['field'] ?? '');

        if (!in_array($field, [self::FIELD_PIPELINE, self::FIELD_STAGE, self::FIELD_STATUS, self::FIELD_AMOUNT, self::FIELD_ROLE], true)) {
            return;
        }

        $comparison = $this->buildComparison($event, $field, $details);
        if (null === $comparison) {
            return;
        }

        $leadAlias = $event->getLeadsTableAlias();
        $subQuery = sprintf(
            '%s (SELECT 1 FROM %s dealflow_assoc INNER JOIN %s dealflow_deal ON dealflow_deal.id = dealflow_assoc.deal_id WHERE dealflow_assoc.contact_id = %s.id AND %s)',
            $comparison['existsClause'],
            MAUTIC_TABLE_PREFIX.DealContactAssociation::TABLE_NAME,
            MAUTIC_TABLE_PREFIX.Deal::TABLE_NAME,
            $leadAlias,
            $comparison['expression']
        );

        $event->setSubQuery($subQuery);
    }

    /**
     * @param array<string, mixed> $details
     *
     * @return array{existsClause: string, expression: string}|null
     */
    private function buildComparison(LeadListFilteringEvent $event, string $field, array $details): ?array
    {
        $operator = (string) ($details['operator'] ?? OperatorOptions::EQUAL_TO);
        $filter = $details['properties']['filter'] ?? $details['filter'] ?? null;
        $column = match ($field) {
            self::FIELD_PIPELINE => 'dealflow_deal.pipeline_id',
            self::FIELD_STAGE => 'dealflow_deal.stage_id',
            self::FIELD_STATUS => 'dealflow_deal.status',
            self::FIELD_AMOUNT => 'dealflow_deal.amount',
            self::FIELD_ROLE => 'dealflow_assoc.label',
            default => null,
        };

        if (null === $column) {
            return null;
        }

        $parameterName = sprintf('dealflow_%s_%s', $event->getAlias(), preg_replace('/[^a-z0-9_]+/i', '_', $field) ?? 'filter');
        $queryBuilder = $event->getQueryBuilder();

        return match ($operator) {
            OperatorOptions::EQUAL_TO => $this->parameterizedComparison($queryBuilder, $parameterName, $column, '=', $this->normalizeFilterValue($field, $filter)),
            OperatorOptions::NOT_EQUAL_TO => $this->parameterizedComparison($queryBuilder, $parameterName, $column, '=', $this->normalizeFilterValue($field, $filter), true),
            OperatorOptions::GREATER_THAN => $this->parameterizedComparison($queryBuilder, $parameterName, $column, '>', (float) $filter),
            OperatorOptions::GREATER_THAN_OR_EQUAL => $this->parameterizedComparison($queryBuilder, $parameterName, $column, '>=', (float) $filter),
            OperatorOptions::LESS_THAN => $this->parameterizedComparison($queryBuilder, $parameterName, $column, '<', (float) $filter),
            OperatorOptions::LESS_THAN_OR_EQUAL => $this->parameterizedComparison($queryBuilder, $parameterName, $column, '<=', (float) $filter),
            default => null,
        };
    }

    /**
     * @return array{existsClause: string, expression: string}
     */
    private function parameterizedComparison(
        \Mautic\LeadBundle\Segment\Query\QueryBuilder $queryBuilder,
        string $parameterName,
        string $column,
        string $operator,
        mixed $value,
        bool $negateExists = false,
    ): array {
        $queryBuilder->setParameter($parameterName, $value);

        return [
            'existsClause' => $negateExists ? 'NOT EXISTS' : 'EXISTS',
            'expression' => sprintf('%s %s :%s', $column, $operator, $parameterName),
        ];
    }

    private function normalizeFilterValue(string $field, mixed $value): int|string
    {
        return match ($field) {
            self::FIELD_PIPELINE, self::FIELD_STAGE => (int) $value,
            default => trim((string) $value),
        };
    }
}
