<?php

namespace MauticPlugin\MauticCrmBundle\Integration;

use Mautic\CoreBundle\Form\Type\YesNoButtonGroupType;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\PluginBundle\Entity\IntegrationEntity;
use Mautic\PluginBundle\Entity\IntegrationEntityRepository;
use Mautic\PluginBundle\Exception\ApiErrorException;
use Mautic\PluginBundle\Integration\IntegrationObject;
use MauticPlugin\MauticCrmBundle\Form\Type\IntegrationCampaignsTaskType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilder;

/**
 * @method \MauticPlugin\MauticCrmBundle\Api\ConnectwiseApi getApiHelper()
 */
class ConnectwiseIntegration extends CrmAbstractIntegration
{
    public const PAGESIZE = 200;

    public function getName(): string
    {
        return 'Connectwise';
    }

    public function getSupportedFeatures(): array
    {
        return ['push_lead', 'get_leads'];
    }

    /**
     * @return array<string, string>
     */
    public function getRequiredKeyFields(): array
    {
        return [
            'username'  => 'mautic.connectwise.form.integrator',
            'password'  => 'mautic.connectwise.form.privatekey',
            'site'      => 'mautic.connectwise.form.site',
            'appcookie' => 'mautic.connectwise.form.cookie',
        ];
    }

    /**
     * Get the array key for application cookie.
     */
    public function getClientId(): string
    {
        return 'appcookie';
    }

    /**
     * Get the array key for companyid.
     */
    public function getCompanyIdKey(): string
    {
        return 'companyid';
    }

    /**
     * Get the array key for client id.
     */
    public function getIntegrator(): string
    {
        return 'username';
    }

    /**
     * Get the array key for client id.
     */
    public function getConnectwiseUrl(): string
    {
        return 'site';
    }

    /**
     * Get the array key for client secret.
     */
    public function getClientSecretKey(): string
    {
        return 'password';
    }

    public function getSecretKeys(): array
    {
        return [
            'password', 'appcookie',
        ];
    }

    public function getApiUrl(): string
    {
        return sprintf('%s/v4_6_release/apis/3.0', $this->keys['site']);
    }

    public function getAuthLoginUrl(): string
    {
        return $this->router->generate('mautic_integration_auth_callback', ['integration' => $this->getName()]);
    }

    /**
     * @return bool
     */
    public function authCallback($settings = [], $parameters = [])
    {
        $url   = $this->getApiUrl();
        $error = false;
        try {
            $response = $this->makeRequest($url.'/system/members/', $parameters, 'GET', $settings);

            foreach ($response as $key => $r) {
                $key = preg_replace('/[\r\n]+/', '', $key);
                switch ($key) {
                    case '<!DOCTYPE_html_PUBLIC_"-//W3C//DTD_XHTML_1_0_Strict//EN"_"http://www_w3_org/TR/xhtml1/DTD/xhtml1-strict_dtd"><html_xmlns':
                        $error = '404 not found error';
                        break;
                    case 'code':
                        $error = $response['message'].' '.$r;
                        break;
                }
            }
            if (!$error) {
                $data = ['username' => $this->keys['username'], 'password' => $this->keys['password']];
                $this->extractAuthKeys($data, 'username');
            }
        } catch (\Exception $e) {
            return $e->getMessage();
        }

        return $error;
    }

    /**
     * Append ClientID into header to enable authentication.
     *
     * @param string       $url
     * @param array<mixed> $parameters
     * @param string       $method
     * @param array<mixed> $settings
     * @param string       $authType
     *
     * @return array<mixed>
     */
    public function prepareRequest($url, $parameters, $method, $settings, $authType): array
    {
        [$parameters,$headers] = parent::prepareRequest($url, $parameters, $method, $settings, $authType);

        $headers['clientId'] = $this->keys['appcookie'];    // Even though it is called appcookie it is ClientID

        return [$parameters, $headers];
    }

    public function getAuthenticationType(): string
    {
        return 'basic';
    }

    public function getDataPriority(): bool
    {
        return true;
    }

    /**
     * Get available company fields for choices in the config UI.
     *
     * @param array $settings
     *
     * @return array
     */
    public function getFormCompanyFields($settings = [])
    {
        return $this->getFormFieldsByObject('company', $settings);
    }

    /**
     * @param array $settings
     *
     * @return array|mixed
     */
    public function getFormLeadFields($settings = [])
    {
        return $this->getFormFieldsByObject('Contact', $settings);
    }

    /**
     * @return mixed[]
     */
    public function getAvailableLeadFields($settings = []): array
    {
        $cwFields = [];
        if (isset($settings['feature_settings']['objects'])) {
            $cwObjects = $settings['feature_settings']['objects'];
        } else {
            $cwObjects['Contact'] = 'Contact';
        }
        if (!$this->isAuthorized()) {
            return [];
        }
        switch ($cwObjects) {
            case isset($cwObjects['Contact']):
                $contactFields = $this->getContactFields();

                $cwFields['Contact'] = $this->setFields($contactFields);
                break;
            case isset($cwObjects['company']):
                $company             = $this->getCompanyFields();
                $cwFields['company'] = $this->setFields($company);
                break;
        }

        return $cwFields;
    }

    public function setFields($fields): array
    {
        $cwFields = [];

        foreach ($fields as $fieldName => $field) {
            if (in_array($field['type'], ['string', 'boolean', 'ref'])) {
                $cwFields[$fieldName] = [
                    'type'     => $field['type'],
                    'label'    => ucfirst($fieldName),
                    'required' => $field['required'],
                ];
            }
        }

        return $cwFields;
    }

    /**
     * @param \Mautic\PluginBundle\Integration\Form|FormBuilder $builder
     * @param array                                             $data
     * @param string                                            $formArea
     */
    public function appendToForm(&$builder, $data, $formArea): void
    {
        if ('features' == $formArea) {
            $builder->add(
                'updateBlanks',
                ChoiceType::class,
                [
                    'choices' => [
                        'mautic.integrations.blanks' => 'updateBlanks',
                    ],
                    'expanded'          => true,
                    'multiple'          => true,
                    'label'             => 'mautic.integrations.form.blanks',
                    'label_attr'        => ['class' => 'control-label'],
                    'placeholder'       => false,
                    'required'          => false,
                ]
            );
            $builder->add(
                'objects',
                ChoiceType::class,
                [
                    'choices' => [
                        'mautic.connectwise.object.contact' => 'Contact',
                        'mautic.connectwise.object.company' => 'company',
                    ],
                    'expanded'          => true,
                    'multiple'          => true,
                    'label'             => 'mautic.connectwise.form.objects_to_pull_from',
                    'label_attr'        => ['class' => ''],
                    'placeholder'       => false,
                    'required'          => false,
                ]
            );
        }

        if ('integration' == $formArea) {
            if ($this->isAuthorized()) {
                $builder->add(
                    'push_activities',
                    YesNoButtonGroupType::class,
                    [
                        'label'      => 'mautic.plugin.config.push.activities',
                        'label_attr' => ['class' => 'control-label'],
                        'attr'       => [
                            'class' => 'form-control',
                        ],
                        'data'     => (!isset($data['push_activities'])) ? true : $data['push_activities'],
                        'required' => false,
                    ]
                );

                $builder->add(
                    'campaign_task',
                    IntegrationCampaignsTaskType::class,
                    [
                        'label' => false,
                        'attr'  => [
                            'data-hide-on' => '{"campaignevent_properties_config_push_activities_0":"checked"}',
                        ],
                        'data' => $data['campaign_task'] ?? [],
                    ]);
            }
        }
    }

    /**
     * @return array of company fields for connectwise
     */
    public function getCompanyFields(): array
    {
        return [
            'identifier'            => ['type' => 'string', 'required' => true],
            'name'                  => ['type' => 'string', 'required' => true],
            'addressLine1'          => ['type' => 'string', 'required' => false],
            'addressLine2'          => ['type' => 'string', 'required' => false],
            'city'                  => ['type' => 'string', 'required' => false],
            'state'                 => ['type' => 'string', 'required' => false],
            'zip'                   => ['type' => 'string', 'required' => false],
            'phoneNumber'           => ['type' => 'string', 'required' => false],
            'faxNumber'             => ['type' => 'string', 'required' => false],
            'website'               => ['type' => 'string', 'required' => false],
            'territoryId'           => ['type' => 'string', 'required' => false],
            'marketId'              => ['type' => 'string', 'required' => false],
            'accountNumber'         => ['type' => 'string', 'required' => false],
            'dateAcquired'          => ['type' => 'string', 'required' => false],
            'annualRevenue'         => ['type' => 'string', 'required' => false],
            'numberOfEmployees'     => ['type' => 'string', 'required' => false],
            'leadSource'            => ['type' => 'string', 'required' => false],
            'leadFlag'              => ['type' => 'boolean', 'required' => false],
            'unsubscribeFlag'       => ['type' => 'boolean', 'required' => false],
            'calendarId'            => ['type' => 'string', 'required' => false],
            'userDefinedField1'     => ['type' => 'string', 'required' => false],
            'userDefinedField2'     => ['type' => 'string', 'required' => false],
            'userDefinedField3'     => ['type' => 'string', 'required' => false],
            'userDefinedField4'     => ['type' => 'string', 'required' => false],
            'userDefinedField5'     => ['type' => 'string', 'required' => false],
            'userDefinedField6'     => ['type' => 'string', 'required' => false],
            'userDefinedField7'     => ['type' => 'string', 'required' => false],
            'userDefinedField8'     => ['type' => 'string', 'required' => false],
            'userDefinedField9'     => ['type' => 'string', 'required' => false],
            'userDefinedField10'    => ['type' => 'string', 'required' => false],
            'vendorIdentifier'      => ['type' => 'string', 'required' => false],
            'taxIdentifier'         => ['type' => 'string', 'required' => false],
            'invoiceToEmailAddress' => ['type' => 'string', 'required' => false],
            'invoiceCCEmailAddress' => ['type' => 'string', 'required' => false],
            'deletedFlag'           => ['type' => 'boolean', 'required' => false],
            'dateDeleted'           => ['type' => 'string', 'required' => false],
            'deletedBy'             => ['type' => 'string', 'required' => false],
            // todo 'customFields' => 'array',
        ];
    }

    /**
     * @return array of contact fields for connectwise
     */
    public function getContactFields(): array
    {
        return [
            'firstName'              => ['type' => 'string', 'required' => true],
            'lastName'               => ['type' => 'string', 'required' => false],
            'type'                   => ['type' => 'string', 'required' => false],
            'company'                => ['type' => 'ref', 'required' => false, 'value' => 'name'],
            'addressLine1'           => ['type' => 'string', 'required' => false],
            'addressLine2'           => ['type' => 'string', 'required' => false],
            'city'                   => ['type' => 'string', 'required' => false],
            'state'                  => ['type' => 'string', 'required' => false],
            'zip'                    => ['type' => 'string', 'required' => false],
            'country'                => ['type' => 'string', 'required' => false],
            'inactiveFlag'           => ['type' => 'string', 'required' => false],
            'securityIdentifier'     => ['type' => 'string', 'required' => false],
            'managerContactId'       => ['type' => 'string', 'required' => false],
            'assistantContactId'     => ['type' => 'string', 'required' => false],
            'title'                  => ['type' => 'string', 'required' => false],
            'school'                 => ['type' => 'string', 'required' => false],
            'nickName'               => ['type' => 'string', 'required' => false],
            'marriedFlag'            => ['type' => 'boolean', 'required' => false],
            'childrenFlag'           => ['type' => 'boolean', 'required' => false],
            'significantOther'       => ['type' => 'string', 'required' => false],
            'portalPassword'         => ['type' => 'string', 'required' => false],
            'portalSecurityLevel'    => ['type' => 'string', 'required' => false],
            'disablePortalLoginFlag' => ['type' => 'boolean', 'required' => false],
            'unsubscribeFlag'        => ['type' => 'boolean', 'required' => false],
            'userDefinedField1'      => ['type' => 'string', 'required' => false],
            'userDefinedField2'      => ['type' => 'string', 'required' => false],
            'userDefinedField3'      => ['type' => 'string', 'required' => false],
            'userDefinedField4'      => ['type' => 'string', 'required' => false],
            'userDefinedField5'      => ['type' => 'string', 'required' => false],
            'userDefinedField6'      => ['type' => 'string', 'required' => false],
            'userDefinedField7'      => ['type' => 'string', 'required' => false],
            'userDefinedField8'      => ['type' => 'string', 'required' => false],
            'userDefinedField9'      => ['type' => 'string', 'required' => false],
            'userDefinedField10'     => ['type' => 'string', 'required' => false],
            'gender'                 => ['type' => 'string', 'required' => false],
            'birthDay'               => ['type' => 'string', 'required' => false],
            'anniversary'            => ['type' => 'string', 'required' => false],
            'presence'               => ['type' => 'string', 'required' => false],
            'mobileGuid'             => ['type' => 'string', 'required' => false],
            'facebookUrl'            => ['type' => 'string', 'required' => false],
            'twitterUrl'             => ['type' => 'string', 'required' => false],
            'linkedInUrl'            => ['type' => 'string', 'required' => false],
            'defaultBillingFlag'     => ['type' => 'boolean', 'required' => false],
            'communicationItems'     => [
                'type'     => 'array',
                'required' => false,
                'items'    => [
                    'name'  => ['type' => 'name'],
                    'value' => 'value',
                    'keys'  => ['Email', 'Direct', 'Fax', 'Cell'],
                ],
            ],
            'Direct'                 => ['type' => 'string', 'required' => false, 'configOnly' => true],
            'Cell'                   => ['type' => 'string', 'required' => false, 'configOnly' => true],
            'Email'                  => ['type' => 'string', 'required' => true, 'configOnly' => true],
            'Fax'                    => ['type' => 'string', 'required' => false, 'configOnly' => true],
        ];
    }

    /**
     * Get Contacts from connectwise.
     *
     * @param array $params
     */
    public function getLeads($params = [], $query = null, &$executed = null, $result = [], $object = 'Contact'): int
    {
        return $this->getRecords($params, $object);
    }

    /**
     * Get Companies from connectwise.
     */
    public function getCompanies(array $params = []): int
    {
        return $this->getRecords($params, 'company');
    }

    public function getRecords($params, $object): int
    {
        if (!$this->isAuthorized()) {
            return 0;
        }

        $page                = 1;
        $executed            = 0;
        $integrationEntities = [];
        try {
            while ($records = ('Contact' == $object)
                ? $this->getApiHelper()->getContacts($params, $page)
                : $this->getApiHelper()->getCompanies($params, $page)) {
                $mauticReferenceObject = ('Contact' == $object) ? 'lead' : 'company';
                foreach ($records as $record) {
                    if (is_array($record)) {
                        $id            = $record['id'];
                        $formattedData = $this->amendLeadDataBeforeMauticPopulate($record, $object);
                        $entity        = ('Contact' == $object)
                            ? $this->getMauticLead($formattedData)
                            : $this->getMauticCompany(
                                $formattedData,
                                'company'
                            );
                        if ($entity) {
                            $integrationEntities[] = $this->saveSyncedData($entity, $object, $mauticReferenceObject, $id);
                            $this->em->detach($entity);
                            unset($entity);
                            ++$executed;
                        }
                    }
                }

                if ($integrationEntities) {
                    $this->em->getRepository(IntegrationEntity::class)->saveEntities($integrationEntities);
                    $this->integrationEntityModel->getRepository()->detachEntities($integrationEntities);
                }

                // No use checking the next page if there are less records than the requested page size

                if (count($records) < self::PAGESIZE) {
                    break;
                }

                ++$page;
            }

            return $executed;
        } catch (\Exception $e) {
            if (404 !== $e->getCode()) {
                $this->logIntegrationError($e);
            }
        }

        return $executed;
    }

    /**
     * Ammend mapped lead data before creating to Mautic.
     *
     * @return mixed[]
     */
    public function amendLeadDataBeforeMauticPopulate($data, $object): array
    {
        $fieldsValues = [];

        if (empty($data)) {
            return $fieldsValues;
        }
        if ('Contact' == $object) {
            $fields = $this->getContactFields();
        } else {
            $fields = $this->getCompanyFields();
        }

        foreach ($data as $key => $field) {
            if (isset($fields[$key])) {
                $name = $key;
                if ('array' == $fields[$key]['type']) {
                    $items = $fields[$key]['items'];
                    foreach ($field as $item) {
                        if (is_array($item[key($items['name'])])) {
                            foreach ($item[key($items['name'])] as $nameKey => $nameField) {
                                if ($nameKey == $items['name'][key($items['name'])]) {
                                    $name = $nameField;
                                }
                            }
                        }
                        $fieldsValues[$name] = $item[$items['value']];
                    }
                } elseif ('ref' == $fields[$key]['type']) {
                    $fieldsValues[$name] = $field[$fields[$key]['value']];
                } else {
                    $fieldsValues[$name] = $field;
                }
            }
        }
        if (isset($data['id'])) {
            $fieldsValues['id'] = $data['id'];
        }

        return $fieldsValues;
    }

    public function saveSyncedData($entity, $object, $mauticObjectReference, $integrationEntityId): IntegrationEntity
    {
        /** @var IntegrationEntityRepository $integrationEntityRepo */
        $integrationEntityRepo = $this->em->getRepository(IntegrationEntity::class);
        $integrationEntities   = $integrationEntityRepo->getIntegrationEntities(
            $this->getName(),
            $object,
            $mauticObjectReference,
            [$entity->getId()]
        );

        if ($integrationEntities) {
            $integrationEntity = reset($integrationEntities);
            $integrationEntity->setLastSyncDate(new \DateTime());
        } else {
            $integrationEntity = new IntegrationEntity();
            $integrationEntity->setDateAdded(new \DateTime());
            $integrationEntity->setIntegration($this->getName());
            $integrationEntity->setIntegrationEntity($object);
            $integrationEntity->setIntegrationEntityId($integrationEntityId);
            $integrationEntity->setInternalEntity($mauticObjectReference);
            $integrationEntity->setInternalEntityId($entity->getId());
        }

        return $integrationEntity;
    }

    /**
     * @param array|Lead $lead
     * @param array      $config
     *
     * @throws ApiErrorException
     */
    public function pushLead($lead, $config = []): bool
    {
        $config      = $this->mergeConfigToFeatureSettings($config);
        $personFound = false;
        $leadPushed  = false;
        $object      = 'Contact';

        if (empty($config['leadFields']) || !$lead->getEmail()) {
            return $leadPushed;
        }

        // findLead first
        $cwContactExists = $this->getApiHelper()->getContacts(['Email' => $lead->getEmail()]);

        if (!empty($cwContactExists)) {
            $personFound = true;
        }

        $personData = [];

        try {
            if ($personFound) {
                foreach ($cwContactExists as $cwContact) { // go through array of contacts found since Connectwise lets you duplicate records with same email address
                    $mappedData = $this->getMappedFields($object, $lead, $personFound, $config, $cwContact);

                    if (!empty($mappedData)) {
                        $personData = $this->getApiHelper()->updateContact($mappedData, $cwContact['id']);
                    } else {
                        $personData['id'] = $cwContact['id'];
                    }
                }
            } else {
                $mappedData = $this->getMappedFields($object, $lead, $personFound, $config);
                $personData = $this->getApiHelper()->createContact($mappedData);
            }

            if (!empty($personData['id'])) {
                $id                    = $personData['id'];
                $integrationEntities[] = $this->saveSyncedData($lead, $object, 'lead', $id);

                if (isset($config['push_activities']) and true == $config['push_activities']) {
                    $savedEntity = $this->createActivity($config['campaign_task'], $id, $lead->getId());
                    if ($savedEntity instanceof IntegrationEntity) {
                        $integrationEntities[] = $savedEntity;
                    }
                }

                $this->em->getRepository(IntegrationEntity::class)->saveEntities($integrationEntities);
                $this->integrationEntityModel->getRepository()->detachEntities($integrationEntities);

                $leadPushed = true;
            }
        } catch (\Exception $e) {
            if ($e instanceof ApiErrorException) {
                $e->setContact($lead);
            }
            $this->logIntegrationError($e);
        }

        return $leadPushed;
    }

    public function getMappedFields($object, $lead, $personFound, $config, $cwContactData = []): array
    {
        $fieldsToUpdateInCW = isset($config['update_mautic']) && $personFound ? array_keys($config['update_mautic'], 1) : [];
        $objectFields       = $this->prepareFieldsForPush($this->getContactFields());
        $leadFields         = $config['leadFields'];

        $cwContactExists = $this->amendLeadDataBeforeMauticPopulate($cwContactData, $object);

        $communicationItems = $cwContactData['communicationItems'] ?? [];

        $leadFields = array_diff_key($leadFields, array_flip($fieldsToUpdateInCW));
        $leadFields = $this->getBlankFieldsToUpdate($leadFields, $cwContactExists, $objectFields, $config);

        return $this->populateLeadData(
            $lead,
            [
                'leadFields'       => $leadFields,
                'object'           => 'Contact',
                'feature_settings' => [
                    'objects' => $config['objects'],
                ],
                'update'             => $personFound,
                'communicationItems' => $communicationItems,
            ]
        );
    }

    /**
     * Match lead data with integration fields.
     */
    public function populateLeadData($lead, $config = []): array
    {
        if ($lead instanceof Lead) {
            $fields = $lead->getFields(true);
        } else {
            $fields = $lead;
        }

        $leadFields = $config['leadFields'];
        if (empty($leadFields)) {
            return [];
        }
        $availableFields = $this->getContactFields();
        $unknown         = $this->translator->trans('mautic.integration.form.lead.unknown');
        $matched         = [];

        foreach ($availableFields as $key => $field) {
            $integrationKey = $matchIntegrationKey = $key;

            if (isset($field['configOnly'])) {
                continue;
            }

            if ('communicationItems' == $integrationKey) {
                $communicationItems = [];
                foreach ($field['items']['keys'] as $keyItem => $item) {
                    $defaultValue = [];
                    $keyExists    = false;
                    if (isset($leadFields[$item])) {
                        if ('Email' == $item) {
                            $defaultValue = ['defaultFlag' => true];
                        }
                        $mauticKey = $leadFields[$item];
                        if (isset($fields[$mauticKey]) && !empty($fields[$mauticKey]['value'])) {
                            foreach ($config['communicationItems'] as $key => $ci) {
                                if ($ci['type']['id'] == $keyItem + 1) {
                                    $config['communicationItems'][$key]['value'] = $fields[$mauticKey]['value'];
                                    $keyExists                                   = true;
                                }
                            }
                            if (!$keyExists) {
                                $type = [
                                    'type' => ['id' => $keyItem + 1, 'name' => $item], ];
                                $values = array_merge(['value' => $this->cleanPushData($fields[$mauticKey]['value'])], $defaultValue);

                                $communicationItems[] = array_merge($type, $values);
                            }
                        }
                    }
                }

                if ($config['update']) {
                    $communicationItems = array_merge($config['communicationItems'], $communicationItems);
                }
                if (!empty($communicationItems)) {
                    $matched[$integrationKey] = $communicationItems;
                }
            }

            if ('company' === $integrationKey && !empty($fields['company']['value'])) {
                try {
                    $foundCompanies = $this->getApiHelper()->getCompanies([
                        'conditions' => [
                            sprintf('Name = "%s"', $fields['company']['value']),
                        ],
                    ]);

                    $matched['company'] = ['identifier' => $foundCompanies[0]['identifier']];
                } catch (ApiErrorException) {
                    // No matching companies were found
                }

                continue;
            }

            if (isset($leadFields[$integrationKey])) {
                $mauticKey = $leadFields[$integrationKey];
                if (isset($fields[$mauticKey]) && !empty($fields[$mauticKey]['value'])) {
                    $matched[$matchIntegrationKey] = $this->cleanPushData($fields[$mauticKey]['value']);
                }
            }

            if (!empty($field['required']) && empty($matched[$matchIntegrationKey]) && !$config['update']) {
                $matched[$matchIntegrationKey] = $unknown;
            }
        }

        if ($config['update']) {
            $updateFields = [];
            foreach ($matched as $key => $field) {
                $updateFields[] = [
                    'op'    => 'replace',
                    'path'  => $key,
                    'value' => $field,
                ];
            }
            $matched = $updateFields;
        }

        return $matched;
    }

    /**
     * @param array $objects
     *
     * @return array
     */
    protected function cleanPriorityFields($fieldsToUpdate, $objects = null)
    {
        if (null === $objects) {
            $objects = ['Leads', 'Contacts'];
        }
        if (isset($fieldsToUpdate['leadFields']) && is_array($objects)) {
            // Pass in the whole config
            $fields = $fieldsToUpdate['leadFields'];
        } else {
            $fields = array_flip($fieldsToUpdate);
        }

        return $this->prepareFieldsForSync($fields, $fieldsToUpdate, $objects);
    }

    /**
     * @param string $priorityObject
     *
     * @return mixed
     */
    protected function getPriorityFieldsForMautic($config, $object = null, $priorityObject = 'mautic')
    {
        if ('company' == $object) {
            $priority = parent::getPriorityFieldsForMautic($config, $object, 'mautic_company');
            $fields   = array_intersect_key($config['companyFields'], $priority);
        } else {
            $fields = parent::getPriorityFieldsForMautic($config, $object, $priorityObject);
        }

        return ($object && isset($fields[$object])) ? $fields[$object] : $fields;
    }

    /**
     * @return array
     *
     * @throws \Exception
     */
    public function getCampaigns()
    {
        $campaigns = [];
        try {
            $campaigns = $this->getApiHelper()->getCampaigns();
        } catch (\Exception $e) {
            $this->logIntegrationError($e);
        }

        return $campaigns;
    }

    /**
     * @throws \Exception
     */
    public function getCampaignChoices(): array
    {
        $choices   = [];
        $campaigns = $this->getCampaigns();

        foreach ($campaigns as $campaign) {
            if (isset($campaign['id'])) {
                $choices[] = [
                    'value' => $campaign['id'],
                    'label' => $campaign['name'],
                ];
            }
        }

        return $choices;
    }

    public function getCampaignMembers($campaignId): bool
    {
        if (!$this->isAuthorized()) {
            return false;
        }

        try {
            $page = 1;
            while ($campaignsMembersResults = $this->getApiHelper()->getCampaignMembers($campaignId, $page)) {
                $campaignMemberObject = new IntegrationObject('CampaignMember', 'lead');
                $recordList           = $this->getRecordList($campaignsMembersResults, 'id');
                $contacts             = (array) $this->integrationEntityModel->getSyncedRecords(new IntegrationObject('Contact', 'lead'), $this->getName(), $recordList);

                $existingContactsIds = array_column(array_filter(
                    $contacts,
                    fn ($contact): bool => 'lead' === $contact['internal_entity']
                ), 'integration_entity_id');

                $contactsToFetch = array_diff_key($recordList, array_flip($existingContactsIds));

                if (!empty($contactsToFetch)) {
                    $listOfContactsToFetch = implode(',', array_keys($contactsToFetch));
                    $params['Ids']         = $listOfContactsToFetch;

                    $this->getLeads($params);
                }

                $saveCampaignMembers = array_merge($existingContactsIds, array_keys($contactsToFetch));

                $this->saveCampaignMembers($saveCampaignMembers, $campaignMemberObject, $campaignId);

                if (count($campaignsMembersResults) < self::PAGESIZE) {
                    // No use continuing as we have less results than page size
                    break;
                }

                ++$page;
            }

            return true;
        } catch (\Exception $e) {
            if (404 !== $e->getCode()) {
                $this->logIntegrationError($e);
            }
        }

        return false;
    }

    public function saveCampaignMembers($allCampaignMembers, $campaignMemberObject, $campaignId): void
    {
        if (empty($allCampaignMembers)) {
            return;
        }
        $persistEntities = [];
        $recordList      = $this->getRecordList($allCampaignMembers);
        $mauticObject    = new IntegrationObject('Contact', 'lead');

        $contacts = $this->integrationEntityModel->getSyncedRecords($mauticObject, $this->getName(), $recordList);
        // first find existing campaign members.
        foreach ($contacts as $contact) {
            $existingCampaignMember = $this->integrationEntityModel->getSyncedRecords($campaignMemberObject, $this->getName(), $campaignId, $contact['internal_entity_id']);
            if (empty($existingCampaignMember)) {
                $persistEntities[] = $this->createIntegrationEntity(
                    $campaignMemberObject->getType(),
                    $campaignId,
                    $campaignMemberObject->getInternalType(),
                    $contact['internal_entity_id'],
                    [],
                    false
                );
            }
        }

        if ($persistEntities) {
            $this->em->getRepository(IntegrationEntity::class)->saveEntities($persistEntities);
            $this->integrationEntityModel->getRepository()->detachEntities($persistEntities);
            unset($persistEntities);
        }
    }

    public function getRecordList($records, $index = null): array
    {
        $recordList = [];

        foreach ($records as $record) {
            if ($index && isset($record[$index])) {
                $record = $record[$index];
            }

            $recordList[$record] = [
                'id' => $record,
            ];
        }

        return $recordList;
    }

    /**
     * @throws ApiErrorException
     */
    public function getActivityTypes(): array
    {
        $activities   = [];
        $cwActivities = $this->getApiHelper()->getActivityTypes();

        foreach ($cwActivities as $cwActivity) {
            if (isset($cwActivity['id'])) {
                $activities[$cwActivity['id']] = $cwActivity['name'];
            }
        }

        return $activities;
    }

    /**
     * @throws ApiErrorException
     */
    public function getMembers(): array
    {
        $members   = [];
        $cwMembers = $this->getApiHelper()->getMembers();
        foreach ($cwMembers as $cwMember) {
            if (isset($cwMember['id'])) {
                $members[$cwMember['id']] = $cwMember['identifier'];
            }
        }

        return $members;
    }

    /**
     * @throws ApiErrorException
     */
    public function createActivity($config, $cwContactId, $leadId): ?IntegrationEntity
    {
        if ($cwContactId and !empty($config['activity_name'])) {
            $activity = [
                'name'     => $config['activity_name'],
                'type'     => ['id' => $config['campaign_activity_type']],
                'assignTo' => ['id' => $config['campaign_members']],
                'contact'  => ['id' => $cwContactId],
            ];
            $activities = $this->getApiHelper()->postActivity($activity);

            if (isset($activities['id'])) {
                return $this->createIntegrationEntity('Activities', $activities['id'], 'lead', $leadId, null, false);
            }
        }

        return null;
    }
}
