<?php

declare(strict_types=1);

namespace MauticPlugin\DealFlowBundle\EventSubscriber;

use Mautic\CampaignBundle\CampaignEvents;
use Mautic\CampaignBundle\Event\CampaignBuilderEvent;
use Mautic\CampaignBundle\Event\CampaignExecutionEvent;
use MauticPlugin\DealFlowBundle\DealFlowEvents;
use MauticPlugin\DealFlowBundle\Entity\DealRepository;
use MauticPlugin\DealFlowBundle\Entity\DealStage;
use MauticPlugin\DealFlowBundle\Entity\DealStageRepository;
use MauticPlugin\DealFlowBundle\Entity\PipelineRepository;
use MauticPlugin\DealFlowBundle\Form\Type\CampaignDealCreateActionType;
use MauticPlugin\DealFlowBundle\Form\Type\CampaignDealMatchConditionType;
use MauticPlugin\DealFlowBundle\Form\Type\CampaignDealStageActionType;
use MauticPlugin\DealFlowBundle\Service\DealManager;
use MauticPlugin\DealFlowBundle\Service\LicenseService;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

final class CampaignSubscriber implements EventSubscriberInterface
{
    public function __construct(
        private readonly DealRepository $dealRepository,
        private readonly DealStageRepository $stageRepository,
        private readonly DealManager $dealManager,
        private readonly PipelineRepository $pipelineRepository,
        private readonly LicenseService $licenseService,
    ) {
    }

    public static function getSubscribedEvents(): array
    {
        return [
            CampaignEvents::CAMPAIGN_ON_BUILD => ['onCampaignBuild', 0],
            DealFlowEvents::ON_CAMPAIGN_TRIGGER_ACTION => ['onCampaignTriggerAction', 0],
            DealFlowEvents::ON_CAMPAIGN_TRIGGER_ACTION_CREATE => ['onCampaignTriggerActionCreate', 0],
            DealFlowEvents::ON_CAMPAIGN_TRIGGER_CONDITION => ['onCampaignTriggerCondition', 0],
        ];
    }

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

        $event->addAction('dealflow.move_stage', [
            'label' => 'Move Deal Flow deal to stage',
            'description' => 'Move matching related deals to another Deal Flow stage.',
            'formType' => CampaignDealStageActionType::class,
            'eventName' => DealFlowEvents::ON_CAMPAIGN_TRIGGER_ACTION,
        ]);

        $event->addAction('dealflow.create_deal', [
            'label' => 'dealflow.campaign.action.create_deal',
            'description' => 'dealflow.campaign.action.create_deal.desc',
            'formType' => CampaignDealCreateActionType::class,
            'eventName' => DealFlowEvents::ON_CAMPAIGN_TRIGGER_ACTION_CREATE,
        ]);

        $event->addCondition('dealflow.matching_deal', [
            'label' => 'Contact has matching Deal Flow deal',
            'description' => 'Check pipeline, stage, amount, status, and stakeholder role against related deals.',
            'formType' => CampaignDealMatchConditionType::class,
            'eventName' => DealFlowEvents::ON_CAMPAIGN_TRIGGER_CONDITION,
        ]);
    }

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

        if (!$event->checkContext('dealflow.matching_deal')) {
            return;
        }

        $lead = $event->getLead();
        $leadId = method_exists($lead, 'getId') ? $lead->getId() : null;
        if (null === $leadId) {
            $event->setResult(false);

            return;
        }

        $event->setResult(
            $this->dealRepository->hasMatchingDealForContact($leadId, $this->buildMatchCriteria($event->getConfig()))
        );
    }

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

        if (!$event->checkContext('dealflow.move_stage')) {
            return;
        }

        $lead = $event->getLead();
        $leadId = method_exists($lead, 'getId') ? $lead->getId() : null;
        if (null === $leadId) {
            $event->setResult(false);

            return;
        }

        $config = $event->getConfig();
        $targetStageId = (int) ($config['target_stage_id'] ?? 0);
        $targetStage = $targetStageId > 0 ? $this->stageRepository->find($targetStageId) : null;

        if (!$targetStage instanceof DealStage || !$targetStage->isPublished()) {
            $event->setFailed('The selected Deal Flow stage is no longer available.');

            return;
        }

        $criteria = $this->buildMatchCriteria($config, 'source_stage_id');
        $criteria['pipelineId'] ??= $targetStage->getPipeline()?->getId();
        $criteria['status'] = '' !== (string) ($criteria['status'] ?? '') ? $criteria['status'] : 'open';

        $changed = false;
        foreach ($this->dealRepository->findMatchingDealsForContact($leadId, $criteria) as $deal) {
            if ($deal->getStage()?->getId() === $targetStage->getId()) {
                continue;
            }

            $this->dealManager->moveToStage($deal, $targetStage, null, 'campaign');
            $changed = true;
        }

        $event->setResult($changed);
    }

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

        if (!$event->checkContext('dealflow.create_deal')) {
            return;
        }

        $lead = $event->getLead();
        $leadId = method_exists($lead, 'getId') ? $lead->getId() : null;
        if (null === $leadId) {
            $event->setResult(false);

            return;
        }

        $config = $event->getConfig();
        $title = trim((string) ($config['title'] ?? ''));
        if ('' === $title) {
            $event->setFailed('Deal title is required.');

            return;
        }

        $data = [
            'title' => $title,
        ];

        $pipelineId = (int) ($config['pipeline_id'] ?? 0);
        if ($pipelineId > 0) {
            $pipeline = $this->pipelineRepository->find($pipelineId);
            if (null === $pipeline || !$pipeline->isPublished()) {
                $event->setFailed('The selected pipeline is no longer available.');

                return;
            }

            $data['pipeline_id'] = $pipelineId;
        }

        $stageId = (int) ($config['stage_id'] ?? 0);
        if ($stageId > 0) {
            $stage = $this->stageRepository->find($stageId);
            if (!$stage instanceof DealStage || !$stage->isPublished()) {
                $event->setFailed('The selected Deal Flow stage is no longer available.');

                return;
            }

            $data['stage_id'] = $stageId;
        }

        if (null !== ($config['amount'] ?? null) && '' !== (string) $config['amount']) {
            $data['amount'] = (string) $config['amount'];
        }

        $deal = $this->dealManager->createDealFromArray($data, null, 'campaign');
        $this->dealManager->attachContact($deal, (int) $leadId, null, null, 'campaign');

        $event->setResult(true);
    }

    /**
     * @param array<string, mixed> $config
     *
     * @return array<string, int|string|float>
     */
    private function buildMatchCriteria(array $config, string $stageField = 'stage_id'): array
    {
        $criteria = [];

        $pipelineId = (int) ($config['pipeline_id'] ?? 0);
        if ($pipelineId > 0) {
            $criteria['pipelineId'] = $pipelineId;
        }

        $stageId = (int) ($config[$stageField] ?? 0);
        if ($stageId > 0) {
            $criteria['stageId'] = $stageId;
        }

        $status = trim((string) ($config['status'] ?? ''));
        if ('' !== $status) {
            $criteria['status'] = $status;
        }

        $stakeholderRole = trim((string) ($config['stakeholder_role'] ?? ''));
        if ('' !== $stakeholderRole) {
            $criteria['stakeholderRole'] = $stakeholderRole;
        }

        if (array_key_exists('min_amount', $config) && '' !== (string) $config['min_amount']) {
            $criteria['minAmount'] = (float) $config['min_amount'];
        }

        if (array_key_exists('max_amount', $config) && '' !== (string) $config['max_amount']) {
            $criteria['maxAmount'] = (float) $config['max_amount'];
        }

        return $criteria;
    }
}
