<?php

namespace MauticPlugin\MauticCrmBundle\Integration;

use Mautic\LeadBundle\Entity\Company;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Helper\IdentifyCompanyHelper;
use Mautic\PluginBundle\Entity\IntegrationEntity;
use Mautic\PluginBundle\Entity\IntegrationEntityRepository;
use Mautic\PluginBundle\Exception\ApiErrorException;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilder;

class DynamicsIntegration extends CrmAbstractIntegration
{
    public function getName(): string
    {
        return 'Dynamics';
    }

    public function getDisplayName(): string
    {
        return 'Dynamics CRM';
    }

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

    /**
     * Return's authentication method such as oauth2, oauth1a, key, etc.
     */
    public function getAuthenticationType(): string
    {
        return 'oauth2';
    }

    /**
     * Return array of key => label elements that will be converted to inputs to
     * obtain from the user.
     *
     * @return array<string, string>
     */
    public function getRequiredKeyFields(): array
    {
        return [
            'resource'      => 'mautic.integration.dynamics.resource',
            'client_id'     => 'mautic.integration.dynamics.client_id',
            'client_secret' => 'mautic.integration.dynamics.client_secret',
        ];
    }

    /**
     * @param FormBuilder $builder
     * @param array       $data
     * @param string      $formArea
     */
    public function appendToForm(&$builder, $data, $formArea): void
    {
        $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,
            ]
        );
        if ('features' === $formArea) {
            $builder->add(
                'objects',
                ChoiceType::class,
                [
                    'choices' => [
                        'mautic.dynamics.object.contact'  => 'contacts',
                        'mautic.dynamics.object.company'  => 'company',
                    ],
                    'expanded'          => true,
                    'multiple'          => true,
                    'label'             => 'mautic.dynamics.form.objects_to_pull_from',
                    'label_attr'        => ['class' => ''],
                    'placeholder'       => false,
                    'required'          => false,
                ]
            );
        }
    }

    public function sortFieldsAlphabetically(): bool
    {
        return false;
    }

    /**
     * Get the array key for the auth token.
     */
    public function getAuthTokenKey(): string
    {
        return 'access_token';
    }

    /**
     * Get the keys for the refresh token and expiry.
     */
    public function getRefreshTokenKeys(): array
    {
        return ['refresh_token', 'expires_on'];
    }

    /**
     * @return string
     */
    public function getApiUrl()
    {
        return $this->keys['resource'];
    }

    public function getAccessTokenUrl(): string
    {
        return 'https://login.microsoftonline.com/common/oauth2/token';
    }

    public function getAuthenticationUrl(): string
    {
        return 'https://login.microsoftonline.com/common/oauth2/authorize';
    }

    public function getAuthLoginUrl(): string
    {
        $url = parent::getAuthLoginUrl();

        return $url.('&resource='.urlencode($this->keys['resource']));
    }

    /**
     * @param bool $inAuthorization
     */
    public function getBearerToken($inAuthorization = false)
    {
        if (!$inAuthorization && isset($this->keys[$this->getAuthTokenKey()])) {
            return $this->keys[$this->getAuthTokenKey()];
        }

        return false;
    }

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

    /**
     * @return string|array
     */
    public function getFormNotes($section)
    {
        if ('custom' === $section) {
            return [
                'template'   => '@MauticCrm/Integration/dynamics.html.twig',
                'parameters' => [
                ],
            ];
        }

        return parent::getFormNotes($section);
    }

    /**
     * @return array
     */
    public function populateLeadData($lead, $config = [], $object = 'Contacts')
    {
        if ('company' === $object) {
            $object = 'accounts';
        }
        $config['object'] = $object;

        return parent::populateLeadData($lead, $config);
    }

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

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

    /**
     * @param array $settings
     *
     * @throws ApiErrorException
     */
    public function getAvailableLeadFields($settings = []): array
    {
        $dynamicsFields    = [];
        $silenceExceptions = $settings['silence_exceptions'] ?? true;
        if (isset($settings['feature_settings']['objects'])) {
            $dynamicsObjects = $settings['feature_settings']['objects'];
        } else {
            $settings        = $this->settings->getFeatureSettings();
            $dynamicsObjects = $settings['objects'] ?? ['contacts'];
        }
        try {
            if ($this->isAuthorized()) {
                if (!empty($dynamicsObjects) && is_array($dynamicsObjects)) {
                    foreach ($dynamicsObjects as $dynamicsObject) {
                        // Check the cache first
                        $settings['cache_suffix'] = $cacheSuffix = '.'.$dynamicsObject;
                        if ($fields = parent::getAvailableLeadFields($settings)) {
                            $dynamicsFields[$dynamicsObject] = $fields;
                            continue;
                        }
                        $leadObject = $this->getApiHelper()->getLeadFields($dynamicsObject);
                        if (null === $leadObject || !array_key_exists('value', $leadObject)) {
                            return [];
                        }
                        $fields = $leadObject['value'];
                        foreach ($fields as $field) {
                            $type      = 'string';
                            $fieldType = $field['AttributeTypeName']['Value'];
                            if (in_array($fieldType, [
                                'LookupType',
                                'OwnerType',
                                'PicklistType',
                                'StateType',
                                'StatusType',
                                'UniqueidentifierType',
                            ], true)) {
                                continue;
                            } elseif (in_array($fieldType, [
                                'DoubleType',
                                'IntegerType',
                                'MoneyType',
                            ], true)) {
                                $type = 'int';
                            } elseif ('Boolean' === $fieldType) {
                                $type = 'boolean';
                            } elseif ('DateTimeType' === $fieldType) {
                                $type = 'datetime';
                            }
                            $dynamicsFields[$dynamicsObject][$field['LogicalName']] = [
                                'type'     => $type,
                                'label'    => $field['DisplayName']['UserLocalizedLabel']['Label'],
                                'dv'       => $field['LogicalName'],
                                'required' => 'ApplicationRequired' === $field['RequiredLevel']['Value'],
                            ];
                        }
                        $this->cache->set('leadFields'.$cacheSuffix, $dynamicsFields[$dynamicsObject]);
                    }
                }
            }
        } catch (ApiErrorException $exception) {
            $this->logIntegrationError($exception);
            if (!$silenceExceptions) {
                throw $exception;
            }

            return [];
        }

        return $dynamicsFields;
    }

    /**
     * @param Lead  $lead
     * @param array $config
     *
     * @return array|bool
     */
    public function pushLead($lead, $config = [])
    {
        $config = $this->mergeConfigToFeatureSettings($config);

        if (empty($config['leadFields'])) {
            return [];
        }

        $mappedData = $this->populateLeadData($lead, $config, 'contacts');

        $this->amendLeadDataBeforePush($mappedData);

        if (empty($mappedData)) {
            return false;
        }

        try {
            if ($this->isAuthorized()) {
                $object = 'contacts';
                /** @var IntegrationEntityRepository $integrationEntityRepo */
                $integrationEntityRepo = $this->em->getRepository(IntegrationEntity::class);
                $integrationId         = $integrationEntityRepo->getIntegrationsEntityId('Dynamics', $object, 'lead', $lead->getId());
                if (!empty($integrationId)) {
                    $integrationEntityId = $integrationId[0]['integration_entity_id'];
                    $this->getApiHelper()->updateLead($mappedData, $integrationEntityId);

                    return $integrationEntityId;
                }
                /** @var ResponseInterface $response */
                $response = $this->getApiHelper()->createLead($mappedData, $lead);
                // OData-EntityId: https://clientname.crm.dynamics.com/api/data/v8.2/contacts(9844333b-c955-e711-80f1-c4346bad526c)
                $header = $response->getHeader('OData-EntityId');
                if (preg_match('/contacts\((.+)\)/', $header, $out)) {
                    $id = $out[1];
                    if (empty($integrationId)) {
                        $integrationEntity = new IntegrationEntity();
                        $integrationEntity->setDateAdded(new \DateTime());
                        $integrationEntity->setIntegration('Dynamics');
                        $integrationEntity->setIntegrationEntity($object);
                        $integrationEntity->setIntegrationEntityId($id);
                        $integrationEntity->setInternalEntity('lead');
                        $integrationEntity->setInternalEntityId($lead->getId());
                    } else {
                        $integrationEntity = $integrationEntityRepo->getEntity($integrationId[0]['id']);
                    }
                    $integrationEntity->setLastSyncDate(new \DateTime());
                    $this->em->persist($integrationEntity);
                    $this->em->flush($integrationEntity);

                    return $id;
                }

                return true;
            }
        } catch (\Exception $e) {
            $this->logIntegrationError($e);
        }

        return false;
    }

    /**
     * @param array      $params
     * @param array|null $query
     */
    public function getLeads($params = [], $query = null, &$executed = null, $result = [], $object = 'contacts'): int
    {
        if ('Contacts' === $object) {
            $object = 'contacts';
        }
        $executed    = 0;
        $MAX_RECORDS = 200; // Default max records is 5000
        try {
            if ($this->isAuthorized()) {
                $config           = $this->mergeConfigToFeatureSettings();
                $fields           = $config['leadFields'];
                $config['object'] = $object;
                $aFields          = $this->getAvailableLeadFields($config);
                $mappedData       = [];
                foreach (array_keys($fields) as $k) {
                    if (isset($aFields[$object][$k])) {
                        $mappedData[] = $aFields[$object][$k]['dv'];
                    }
                }
                $oparams['request_settings']['headers']['Prefer'] = 'odata.maxpagesize='.$MAX_RECORDS;
                $oparams['$select']                               = implode(',', $mappedData);
                if (isset($params['fetchAll'], $params['start']) && !$params['fetchAll']) {
                    $oparams['$filter'] = sprintf('modifiedon ge %sZ', substr($params['start'], 0, -6));
                }

                if (isset($params['output']) && $params['output']->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
                    $progress = new ProgressBar($params['output']);
                    $progress->start();
                }

                while (true) {
                    $data = $this->getApiHelper()->getLeads($oparams);

                    if (!isset($data['value'])) {
                        break; // no more data, exit loop
                    }

                    $result = $this->amendLeadDataBeforeMauticPopulate($data, $object);
                    $executed += array_key_exists('value', $data) ? count($data['value']) : count($result);

                    if (isset($params['output'])) {
                        if ($params['output']->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
                            $params['output']->writeln($result);
                        } else {
                            $progress->advance(count($result));
                        }
                    }

                    if (!isset($data['@odata.nextLink'])) {
                        break; // default exit
                    }

                    // prepare next loop
                    $nextLink              = $data['@odata.nextLink'];
                    $oparams['$skiptoken'] = urldecode(substr($nextLink, strpos($nextLink, '$skiptoken=') + 11));
                }

                if (isset($params['output']) && $params['output']->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
                    $progress->finish();
                }
            }
        } catch (\Exception $e) {
            $this->logIntegrationError($e);
        }

        return $executed;
    }

    /**
     * @param array $params
     */
    public function getCompanies($params = []): int
    {
        $executed    = 0;
        $MAX_RECORDS = 200; // Default max records is 5000
        $object      = 'company';
        try {
            if ($this->isAuthorized()) {
                $config           = $this->mergeConfigToFeatureSettings();
                $fields           = $config['companyFields'];
                $config['object'] = $object;
                $aFields          = $this->getAvailableLeadFields($config);
                $mappedData       = [];
                if (isset($aFields['company'])) {
                    $aFields = $aFields['company'];
                }
                foreach (array_keys($fields) as $k) {
                    $mappedData[] = $aFields[$k]['dv'];
                }
                $oparams['request_settings']['headers']['Prefer'] = 'odata.maxpagesize='.$MAX_RECORDS;
                $oparams['$select']                               = implode(',', $mappedData);
                if (isset($params['fetchAll'], $params['start']) && !$params['fetchAll']) {
                    $oparams['$filter'] = sprintf('modifiedon ge %sZ', substr($params['start'], 0, -6));
                }

                if (isset($params['output']) && $params['output']->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
                    $progress = new ProgressBar($params['output']);
                    $progress->start();
                }

                while (true) {
                    $data = $this->getApiHelper()->getCompanies($oparams);
                    if (!isset($data['value'])) {
                        break; // no more data, exit loop
                    }

                    $result = $this->amendLeadDataBeforeMauticPopulate($data, $object);
                    $executed += count($result);

                    if (isset($params['output'])) {
                        if ($params['output']->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
                            $params['output']->writeln($result);
                        } else {
                            $progress->advance(count($result));
                        }
                    }

                    if (!isset($data['@odata.nextLink'])) {
                        break; // default exit
                    }

                    // prepare next loop
                    $nextLink              = $data['@odata.nextLink'];
                    $oparams['$skiptoken'] = urldecode(substr($nextLink, strpos($nextLink, '$skiptoken=') + 11));
                }

                if (isset($params['output']) && $params['output']->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
                    $progress->finish();
                }
            }
        } catch (\Exception $e) {
            $this->logIntegrationError($e);
        }

        return $executed;
    }

    /**
     * Amend mapped lead data before creating to Mautic.
     *
     * @param array  $data
     * @param string $object
     */
    public function amendLeadDataBeforeMauticPopulate($data, $object = null): array
    {
        if ('company' === $object) {
            $object = 'accounts';
        } elseif ('Lead' === $object || 'Contact' === $object) {
            $object = 'contacts';
        }

        $config = $this->mergeConfigToFeatureSettings([]);

        $result = [];
        if (isset($data['value'])) {
            $this->em->getConnection()->getConfiguration()->setMiddlewares([]);
            $entity = null;
            /** @var IntegrationEntityRepository $integrationEntityRepo */
            $integrationEntityRepo = $this->em->getRepository(IntegrationEntity::class);
            $objects               = $data['value'];
            $integrationEntities   = [];
            /** @var array $objects */
            foreach ($objects as $entityData) {
                $isModified = false;
                if ('accounts' === $object) {
                    $recordId = $entityData['accountid'];
                    // first try to find integration entity
                    $integrationId = $integrationEntityRepo->getIntegrationsEntityId('Dynamics', $object, 'company',
                        null, null, null, false, 0, 0, "'".$recordId."'");
                    if (count($integrationId)) { // company exists, then update local fields
                        /** @var Company $entity */
                        $entity        = $this->companyModel->getEntity($integrationId[0]['internal_entity_id']);
                        $matchedFields = $this->populateMauticLeadData($entityData, $config, 'company');

                        // Match that data with mapped lead fields
                        $fieldsToUpdateInMautic = $this->getPriorityFieldsForMautic($config, $object, 'mautic_company');
                        if (!empty($fieldsToUpdateInMautic)) {
                            $fieldsToUpdateInMautic = array_intersect_key($config['companyFields'], array_flip($fieldsToUpdateInMautic));
                            $newMatchedFields       = array_intersect_key($matchedFields, array_flip($fieldsToUpdateInMautic));
                        } else {
                            $newMatchedFields = $matchedFields;
                        }
                        if (!isset($newMatchedFields['companyname'])) {
                            if (isset($newMatchedFields['companywebsite'])) {
                                $newMatchedFields['companyname'] = $newMatchedFields['companywebsite'];
                            }
                        }

                        // update values if already empty
                        foreach ($matchedFields as $field => $value) {
                            if (empty($entity->getFieldValue($field))) {
                                $newMatchedFields[$field] = $value;
                            }
                        }

                        // remove unchanged fields
                        foreach ($newMatchedFields as $k => $v) {
                            if ($entity->getFieldValue($k) === $v) {
                                unset($newMatchedFields[$k]);
                            }
                        }

                        if (count($newMatchedFields)) {
                            $this->companyModel->setFieldValues($entity, $newMatchedFields, false);
                            $this->companyModel->saveEntity($entity, false);
                            $isModified = true;
                        }
                    } else {
                        $entity = $this->getMauticCompany($entityData, 'company');
                    }
                    if ($entity) {
                        $result[] = $entity->getName();
                    }
                    $mauticObjectReference = 'company';
                } elseif ('contacts' === $object) {
                    $recordId = $entityData['contactid'];
                    // first try to find integration entity
                    $integrationId = $integrationEntityRepo->getIntegrationsEntityId('Dynamics', $object, 'lead',
                        null, null, null, false, 0, 0, "'".$recordId."'");
                    if (count($integrationId)) { // lead exists, then update
                        /** @var Lead $entity */
                        $entity        = $this->leadModel->getEntity($integrationId[0]['internal_entity_id']);
                        $matchedFields = $this->populateMauticLeadData($entityData, $config);

                        // Match that data with mapped lead fields
                        $fieldsToUpdateInMautic = $this->getPriorityFieldsForMautic($config, $object, 'mautic');
                        if (!empty($fieldsToUpdateInMautic)) {
                            $fieldsToUpdateInMautic = array_intersect_key($config['leadFields'] ?? [], array_flip($fieldsToUpdateInMautic));
                            $newMatchedFields       = array_intersect_key($matchedFields, array_flip($fieldsToUpdateInMautic));
                        } else {
                            $newMatchedFields = $matchedFields;
                        }

                        // update values if already empty
                        foreach ($matchedFields as $field => $value) {
                            if (empty($entity->getFieldValue($field))) {
                                $newMatchedFields[$field] = $value;
                            }
                        }

                        // remove unchanged fields
                        foreach ($newMatchedFields as $k => $v) {
                            if ($entity->getFieldValue($k) === $v) {
                                unset($newMatchedFields[$k]);
                            }
                        }

                        if (count($newMatchedFields)) {
                            $this->leadModel->setFieldValues($entity, $newMatchedFields, false, false);
                            $this->leadModel->saveEntity($entity, false);
                            $isModified = true;
                        }
                    } else {
                        /** @var Lead $entity */
                        $entity = $this->getMauticLead($entityData);
                    }

                    if ($entity) {
                        $result[] = $entity->getEmail();
                    }

                    // Associate lead company
                    if (!empty($entityData['parentcustomerid']) // company
                        && $entityData['parentcustomerid'] !== $this->translator->trans(
                            'mautic.integration.form.lead.unknown'
                        )
                    ) {
                        $company = IdentifyCompanyHelper::identifyLeadsCompany(
                            ['company' => $entityData['parentcustomerid']],
                            null,
                            $this->companyModel
                        );

                        if (!empty($company[2])) {
                            $syncLead = $this->companyModel->addLeadToCompany($company[2], $entity);
                            $this->em->detach($company[2]);
                        }
                    }

                    $mauticObjectReference = 'lead';
                } else {
                    $this->logIntegrationError(
                        new \Exception(
                            sprintf('Received an unexpected object "%s"', $object)
                        )
                    );
                    continue;
                }

                if ($entity) {
                    $integrationId = $integrationEntityRepo->getIntegrationsEntityId(
                        'Dynamics',
                        $object,
                        $mauticObjectReference,
                        $entity->getId()
                    );

                    if (0 === count($integrationId)) {
                        $integrationEntity = new IntegrationEntity();
                        $integrationEntity->setDateAdded(new \DateTime());
                        $integrationEntity->setIntegration('Dynamics');
                        $integrationEntity->setIntegrationEntity($object);
                        $integrationEntity->setIntegrationEntityId($recordId);
                        $integrationEntity->setInternalEntity($mauticObjectReference);
                        $integrationEntity->setInternalEntityId($entity->getId());
                        $integrationEntities[] = $integrationEntity;
                    } else {
                        $integrationEntity = $integrationEntityRepo->getEntity($integrationId[0]['id']);
                        if ($isModified) {
                            $integrationEntity->setLastSyncDate(new \DateTime());
                            $integrationEntities[] = $integrationEntity;
                        }
                    }
                    $this->em->detach($entity);
                    unset($entity);
                }
            }

            $integrationEntityRepo->saveEntities($integrationEntities);
            $this->em->clear();

            unset($integrationEntityRepo, $integrationEntities);
        }

        return $result;
    }

    /**
     * @param array $params
     *
     * @return mixed[]
     */
    public function pushLeads($params = []): array
    {
        $MAX_RECORDS = (isset($params['limit']) && $params['limit'] < 100) ? $params['limit'] : 100;
        if (isset($params['fetchAll']) && $params['fetchAll']) {
            $params['start'] = null;
            $params['end']   = null;
        }
        $object                = 'contacts';
        $config                = $this->mergeConfigToFeatureSettings();
        $integrationEntityRepo = $this->em->getRepository(IntegrationEntity::class);
        $fieldsToUpdateInCrm   = isset($config['update_mautic']) ? array_keys($config['update_mautic'], 0) : [];
        $leadFields            = array_unique(array_values($config['leadFields'] ?? []));
        $totalUpdated          = $totalCreated          = $totalErrors          = 0;

        if ($key = array_search('mauticContactTimelineLink', $leadFields)) {
            unset($leadFields[$key]);
        }
        if ($key = array_search('mauticContactIsContactableByEmail', $leadFields)) {
            unset($leadFields[$key]);
        }

        if (empty($leadFields)) {
            return [0, 0, 0];
        }

        $fields = implode(', l.', $leadFields);
        $fields = 'l.'.$fields;

        $availableFields         = $this->getAvailableLeadFields(['feature_settings' => ['objects' => [$object]]]);
        $fieldsToUpdate[$object] = array_values(array_intersect(array_keys($availableFields[$object]), $fieldsToUpdateInCrm));
        $fieldsToUpdate[$object] = array_intersect_key($config['leadFields'] ?? [], array_flip($fieldsToUpdate[$object]));

        $progress      = false;
        $totalToUpdate = array_sum($integrationEntityRepo->findLeadsToUpdate('Dynamics', 'lead', $fields, 0, $params['start'], $params['end'], [$object]));
        $totalToCreate = $integrationEntityRepo->findLeadsToCreate('Dynamics', $fields, 0, $params['start'], $params['end']);
        $totalToCreate = is_array($totalToCreate) ? count($totalToCreate) : (int) $totalToCreate;
        $totalCount    = $totalToCreate + $totalToUpdate;

        if (defined('IN_MAUTIC_CONSOLE')) {
            // start with update
            if ($totalToUpdate + $totalToCreate) {
                $output = new ConsoleOutput();
                $output->writeln("About $totalToUpdate to update and about $totalToCreate to create/update");
                $output->writeln('<info>This could take some time. Please wait until the process is completed</info>');
                $progress = new ProgressBar($output, $totalCount);
            }
        }

        // Start with contacts so we know who is a contact when we go to process converted leads
        $leadsToCreateInD    = [];
        $leadsToUpdateInD    = [];
        $integrationEntities = [];

        $toUpdate = $integrationEntityRepo->findLeadsToUpdate('Dynamics', 'lead', $fields, $totalToUpdate, $params['start'], $params['end'], $object, [])[$object];

        if (is_array($toUpdate)) {
            $totalUpdated += count($toUpdate);
            foreach ($toUpdate as $lead) {
                if (isset($lead['email']) && !empty($lead['email'])) {
                    $key                        = mb_strtolower($this->cleanPushData($lead['email']));
                    $lead                       = $this->getCompoundMauticFields($lead);
                    $lead['integration_entity'] = $object;
                    $leadsToUpdateInD[$key]     = $lead;
                    $integrationEntity          = $this->em->getReference(IntegrationEntity::class, $lead['id']);
                    $integrationEntities[]      = $integrationEntity->setLastSyncDate(new \DateTime());
                }
            }
        }
        unset($toUpdate);

        // create lead records, including deleted on D side (last_sync = null)
        /** @var array $leadsToCreate */
        $leadsToCreate = $integrationEntityRepo->findLeadsToCreate('Dynamics', $fields, $totalToCreate, $params['start'], $params['end']);
        if (is_array($leadsToCreate)) {
            $totalCreated += count($leadsToCreate);
            foreach ($leadsToCreate as $lead) {
                if (isset($lead['email']) && !empty($lead['email'])) {
                    $key                        = mb_strtolower($this->cleanPushData($lead['email']));
                    $lead                       = $this->getCompoundMauticFields($lead);
                    $lead['integration_entity'] = $object;
                    $leadsToCreateInD[$key]     = $lead;
                }
            }
        }
        unset($leadsToCreate);

        if (count($integrationEntities)) {
            // Persist updated entities if applicable
            $integrationEntityRepo->saveEntities($integrationEntities);
            $this->integrationEntityModel->getRepository()->detachEntities($integrationEntities);
        }

        // update contacts
        $leadData = [];
        $rowNum   = 0;
        foreach ($leadsToUpdateInD as $lead) {
            $mappedData = [];
            if (defined('IN_MAUTIC_CONSOLE') && $progress) {
                $progress->advance();
            }
            $existingPerson = $this->getExistingRecord('emailaddress1', $lead['email'], $object);

            $objectFields            = $this->prepareFieldsForPush($availableFields[$object]);
            $fieldsToUpdate[$object] = $this->getBlankFieldsToUpdate($fieldsToUpdate[$object], $existingPerson, $objectFields, $config);

            // Match that data with mapped lead fields
            foreach ($fieldsToUpdate[$object] as $k => $v) {
                foreach ($lead as $dk => $dv) {
                    if ($v === $dk) {
                        if ($dv) {
                            if (isset($availableFields[$object][$k])) {
                                $mappedData[$availableFields[$object][$k]['dv']] = $dv;
                            }
                        }
                    }
                }
            }
            $leadData[$lead['integration_entity_id']] = $mappedData;

            ++$rowNum;
            // SEND 100 RECORDS AT A TIME
            if ($MAX_RECORDS === $rowNum) {
                $this->getApiHelper()->updateLeads($leadData, $object);
                $leadData = [];
                $rowNum   = 0;
            }
        }
        $this->getApiHelper()->updateLeads($leadData, $object);

        // create  contacts
        $leadData = [];
        $rowNum   = 0;
        foreach ($leadsToCreateInD as $lead) {
            $mappedData = [];
            if (defined('IN_MAUTIC_CONSOLE') && $progress) {
                $progress->advance();
            }
            if (!isset($config['leadFields']) || !is_iterable($config['leadFields'])) {
                continue;
            }
            // Match that data with mapped lead fields
            foreach ($config['leadFields'] as $k => $v) {
                foreach ($lead as $dk => $dv) {
                    if ($v === $dk) {
                        if ($dv) {
                            if (isset($availableFields[$object][$k])) {
                                $mappedData[$availableFields[$object][$k]['dv']] = $dv;
                            }
                        }
                    }
                }
            }
            $leadData[$lead['internal_entity_id']] = $mappedData;

            ++$rowNum;
            // SEND 100 RECORDS AT A TIME
            if ($MAX_RECORDS === $rowNum) {
                $ids = $this->getApiHelper()->createLeads($leadData, $object);
                $this->createIntegrationEntities($ids, $object, $integrationEntityRepo);
                $leadData = [];
                $rowNum   = 0;
            }
        }
        $ids = $this->getApiHelper()->createLeads($leadData, $object);
        $this->createIntegrationEntities($ids, $object, $integrationEntityRepo);

        if ($progress) {
            $progress->finish();
            $output->writeln('');
        }

        return [$totalUpdated, $totalCreated, $totalErrors];
    }

    /**
     * @param array                       $ids
     * @param IntegrationEntityRepository $integrationEntityRepo
     */
    private function createIntegrationEntities($ids, $object, $integrationEntityRepo): void
    {
        foreach ($ids as $oid => $leadId) {
            $this->logger->debug('CREATE INTEGRATION ENTITY: '.$oid);
            $integrationId = $integrationEntityRepo->getIntegrationsEntityId('Dynamics', $object,
                'lead', null, null, null, false, 0, 0,
                "'".$oid."'"
            );

            if (0 === count($integrationId)) {
                $this->createIntegrationEntity($object, $oid, 'lead', $leadId);
            }
        }
    }

    private function getExistingRecord($seachColumn, $searchValue, $object = 'contacts')
    {
        $availableFields    = $this->getAvailableLeadFields();
        $oparams['$select'] = implode(',', array_keys($availableFields[$object]));
        $oparams['$filter'] = $seachColumn.' eq \''.$searchValue.'\'';
        $data               = $this->getApiHelper()->getLeads($oparams);

        return (isset($data['value'][0]) && !empty($data['value'][0])) ? $data['value'][0] : [];
    }
}
