<?php

namespace Mautic\LeadBundle\Model;

use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
use Doctrine\ORM\EntityManager;
use Mautic\CoreBundle\Cache\ResultCacheOptions;
use Mautic\CoreBundle\Form\RequestTrait;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\CoreBundle\Helper\DateTimeHelper;
use Mautic\CoreBundle\Helper\InputHelper;
use Mautic\CoreBundle\Helper\UserHelper;
use Mautic\CoreBundle\Model\AjaxLookupModelInterface;
use Mautic\CoreBundle\Model\FormModel as CommonFormModel;
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
use Mautic\CoreBundle\Translation\Translator;
use Mautic\EmailBundle\Helper\EmailValidator;
use Mautic\LeadBundle\Deduplicate\CompanyDeduper;
use Mautic\LeadBundle\Entity\Company;
use Mautic\LeadBundle\Entity\CompanyLead;
use Mautic\LeadBundle\Entity\CompanyRepository;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Entity\LeadField;
use Mautic\LeadBundle\Entity\LeadRepository;
use Mautic\LeadBundle\Event\CompanyEvent;
use Mautic\LeadBundle\Event\CompanyMergeEvent;
use Mautic\LeadBundle\Event\LeadChangeCompanyEvent;
use Mautic\LeadBundle\Exception\UniqueFieldNotFoundException;
use Mautic\LeadBundle\Field\FieldList;
use Mautic\LeadBundle\Form\Type\CompanyType;
use Mautic\LeadBundle\LeadEvents;
use Mautic\UserBundle\Entity\User;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\EventDispatcher\Event;

/**
 * @extends CommonFormModel<Company>
 *
 * @implements AjaxLookupModelInterface<Company>
 */
class CompanyModel extends CommonFormModel implements AjaxLookupModelInterface
{
    use DefaultValueTrait;
    use RequestTrait;

    /**
     * @var FieldModel
     */
    protected $leadFieldModel;

    /**
     * @var array
     */
    protected $companyFields;

    /**
     * @var array
     */
    private $fields = [];

    private bool $repoSetup = false;

    public function __construct(
        FieldModel $leadFieldModel,
        protected EmailValidator $emailValidator,
        protected CompanyDeduper $companyDeduper,
        EntityManager $em,
        CorePermissions $security,
        EventDispatcherInterface $dispatcher,
        UrlGeneratorInterface $router,
        Translator $translator,
        UserHelper $userHelper,
        LoggerInterface $mauticLogger,
        CoreParametersHelper $coreParametersHelper,
        private FieldList $fieldList,
    ) {
        $this->leadFieldModel = $leadFieldModel;

        parent::__construct($em, $security, $dispatcher, $router, $translator, $userHelper, $mauticLogger, $coreParametersHelper);
    }

    /**
     * @param Company $entity
     * @param bool    $unlock
     */
    public function saveEntity($entity, $unlock = true): void
    {
        // Update leads primary company name
        $this->setEntityDefaultValues($entity, 'company');
        $this->getCompanyLeadRepository()->updateLeadsPrimaryCompanyName($entity);

        parent::saveEntity($entity, $unlock);
    }

    /**
     * Save an array of entities.
     *
     * @param array $entities
     * @param bool  $unlock
     */
    public function saveEntities($entities, $unlock = true): void
    {
        // Update leads primary company name
        foreach ($entities as $entity) {
            $this->setEntityDefaultValues($entity, 'company');
            $this->getCompanyLeadRepository()->updateLeadsPrimaryCompanyName($entity);
        }
        parent::saveEntities($entities, $unlock);
    }

    public function getRepository(): CompanyRepository
    {
        $repo = $this->em->getRepository(Company::class);

        if (!$this->repoSetup) {
            $this->repoSetup = true;
            $repo->setDispatcher($this->dispatcher);
            // set the point trigger model in order to get the color code for the lead
            $fields = $this->fieldList->getFieldList(true, true, ['isPublished' => true, 'object' => 'company']);

            $searchFields = [];
            foreach ($fields as $groupFields) {
                $searchFields = array_merge($searchFields, array_keys($groupFields));
            }
            $repo->setAvailableSearchFields($searchFields);
        }

        return $repo;
    }

    /**
     * @return \Mautic\LeadBundle\Entity\CompanyLeadRepository
     */
    public function getCompanyLeadRepository()
    {
        return $this->em->getRepository(CompanyLead::class);
    }

    public function getPermissionBase(): string
    {
        // We are using lead:leads in the CompanyController so this should match to prevent a BC break
        return 'lead:leads';
    }

    public function getNameGetter(): string
    {
        return 'getPrimaryIdentifier';
    }

    /**
     * @throws MethodNotAllowedHttpException
     */
    public function createForm($entity, FormFactoryInterface $formFactory, $action = null, $options = []): \Symfony\Component\Form\FormInterface
    {
        if (!$entity instanceof Company) {
            throw new MethodNotAllowedHttpException(['Company']);
        }
        if (!empty($action)) {
            $options['action'] = $action;
        }

        return $formFactory->create(CompanyType::class, $entity, $options);
    }

    public function getEntity($id = null): ?Company
    {
        if (null === $id) {
            return new Company();
        }

        return parent::getEntity($id);
    }

    /**
     * @return mixed
     */
    public function getUserCompanies()
    {
        $user = (!$this->security->isGranted('lead:leads:viewother')) ?
            $this->userHelper->getUser() : false;

        return $this->em->getRepository(Company::class)->getCompanies($user);
    }

    /**
     * Reorganizes a field list to be keyed by field's group then alias.
     */
    public function organizeFieldsByGroup($fields): array
    {
        $array = [];

        foreach ($fields as $field) {
            if ($field instanceof LeadField) {
                $alias = $field->getAlias();
                if ('company' === $field->getObject()) {
                    $group                          = $field->getGroup();
                    $array[$group][$alias]['id']    = $field->getId();
                    $array[$group][$alias]['group'] = $group;
                    $array[$group][$alias]['label'] = $field->getLabel();
                    $array[$group][$alias]['alias'] = $alias;
                    $array[$group][$alias]['type']  = $field->getType();
                }
            } else {
                $alias   = $field['alias'];
                $field[] = $alias;
                if ('company' === $field['object']) {
                    $group                          = $field['group'];
                    $array[$group][$alias]['id']    = $field['id'];
                    $array[$group][$alias]['group'] = $group;
                    $array[$group][$alias]['label'] = $field['label'];
                    $array[$group][$alias]['alias'] = $alias;
                    $array[$group][$alias]['type']  = $field['type'];
                }
            }
        }

        // make sure each group key is present
        $groups = ['core', 'social', 'personal', 'professional'];
        foreach ($groups as $g) {
            if (!isset($array[$g])) {
                $array[$g] = [];
            }
        }

        return $array;
    }

    /**
     * Populates custom field values for updating the company.
     */
    public function setFieldValues(Company $company, array $data, bool $overwriteWithBlank = false): void
    {
        // save the field values
        $fieldValues = $company->getFields();

        if (empty($fieldValues)) {
            // Lead is new or they haven't been populated so let's build the fields now
            if (empty($this->fields)) {
                $this->fields = $this->leadFieldModel->getEntities(
                    [
                        'filter'         => ['object' => 'company'],
                        'hydration_mode' => 'HYDRATE_ARRAY',
                        'result_cache'   => new ResultCacheOptions(LeadField::CACHE_NAMESPACE),
                    ]
                );
                $this->fields = $this->organizeFieldsByGroup($this->fields);
            }
            $fieldValues = $this->fields;
        }

        // update existing values
        foreach ($fieldValues as &$groupFields) {
            foreach ($groupFields as $alias => &$field) {
                if (!isset($field['value'])) {
                    $field['value'] = null;
                }
                // Only update fields that are part of the passed $data array
                if (array_key_exists($alias, $data)) {
                    $curValue = $field['value'];
                    $newValue = $data[$alias];

                    if (is_array($newValue)) {
                        $newValue = implode('|', $newValue);
                    }

                    if ($curValue !== $newValue && (strlen((string) $newValue) > 0 || $overwriteWithBlank)) {
                        $field['value'] = $newValue;
                        $company->addUpdatedField($alias, $newValue, $curValue);
                    }
                }
            }
        }
        $company->setFields($fieldValues);
    }

    /**
     * Add lead to company.
     *
     * @param array|Company $companies
     * @param array|Lead    $lead
     *
     * @throws \Doctrine\ORM\ORMException
     */
    public function addLeadToCompany($companies, $lead): bool
    {
        // Primary company name to be persisted to the lead's contact company field
        $companyName        = '';
        $companyLeadAdd     = [];
        $searchForCompanies = [];

        $dateManipulated = new \DateTime();

        if (!$lead instanceof Lead) {
            $leadId = (is_array($lead) && isset($lead['id'])) ? $lead['id'] : $lead;
            $lead   = $this->em->getReference(Lead::class, $leadId);
        }

        if ($companies instanceof Company) {
            $companyLeadAdd[$companies->getId()] = $companies;
            $companies                           = [$companies->getId()];
        } elseif (!is_array($companies)) {
            $companies = [$companies];
        }

        // make sure they are ints
        foreach ($companies as &$l) {
            $l = (int) $l;

            if (!isset($companyLeadAdd[$l])) {
                $searchForCompanies[] = $l;
            }
        }

        if (!empty($searchForCompanies)) {
            $companyEntities = $this->getEntities([
                'filter' => [
                    'force' => [
                        [
                            'column' => 'comp.id',
                            'expr'   => 'in',
                            'value'  => $searchForCompanies,
                        ],
                    ],
                ],
            ]);

            foreach ($companyEntities as $company) {
                $companyLeadAdd[$company->getId()] = $company;
            }
        }

        unset($companyEntities, $searchForCompanies);

        $persistCompany = [];
        $dispatchEvents = [];
        $contactAdded   = false;
        foreach ($companies as $companyId) {
            if (!isset($companyLeadAdd[$companyId])) {
                // List no longer exists in the DB so continue to the next
                continue;
            }

            $companyLead = $this->getCompanyLeadRepository()->findOneBy(
                [
                    'lead'    => $lead,
                    'company' => $companyLeadAdd[$companyId],
                ]
            );

            if (null != $companyLead) {
                // Detach from Doctrine
                $this->em->detach($companyLead);

                continue;
            } else {
                $companyLead = new CompanyLead();
                $companyLead->setCompany($companyLeadAdd[$companyId]);
                $companyLead->setLead($lead);
                $companyLead->setDateAdded($dateManipulated);
                $contactAdded     = true;
                $persistCompany[] = $companyLead;
                $dispatchEvents[] = $companyId;

                if (!$companyName) {
                    // CompanyLeadRepository::saveEntities will set the first company of the batch as primary so
                    // use the first company name to ensure they match
                    $companyName = $companyLeadAdd[$companyId]->getName();
                }
            }
        }

        if (!empty($persistCompany)) {
            $this->getCompanyLeadRepository()->saveEntities($persistCompany);
        }

        if (!empty($companyName)) {
            // Set the contact's primary company to the first company added in the batch
            // This must happen before LeadEvents::LEAD_COMPANY_CHANGE to ensure the Lead::getCompany has the correct value
            $currentCompanyName = $lead->getCompany();
            if ($currentCompanyName !== $companyName) {
                $lead->addUpdatedField('company', $companyName)
                    ->setDateModified(new \DateTime());

                /** @var LeadRepository $leadRepository */
                $leadRepository = $this->em->getRepository(Lead::class);
                $leadRepository->saveEntity($lead);
            }
        }

        if (!empty($dispatchEvents) && $this->dispatcher->hasListeners(LeadEvents::LEAD_COMPANY_CHANGE)) {
            foreach ($dispatchEvents as $companyId) {
                $event = new LeadChangeCompanyEvent($lead, $companyLeadAdd[$companyId]);
                $this->dispatcher->dispatch($event, LeadEvents::LEAD_COMPANY_CHANGE);

                unset($event);
            }
        }

        // Clear CompanyLead entities from Doctrine memory
        $this->getCompanyLeadRepository()->detachEntities($persistCompany);

        return $contactAdded;
    }

    /**
     * Remove a lead from company.
     *
     * @throws \Doctrine\ORM\ORMException
     */
    public function removeLeadFromCompany($companies, $lead): void
    {
        if (!$lead instanceof Lead) {
            $leadId = (is_array($lead) && isset($lead['id'])) ? $lead['id'] : $lead;
            $lead   = $this->em->getReference(Lead::class, $leadId);
        }

        $companyLeadRemove = [];
        if (!$companies instanceof Company) {
            // make sure they are ints
            $searchForCompanies = [];
            foreach ($companies as &$l) {
                $l                    = (int) $l;
                $searchForCompanies[] = $l;
            }
            if (!empty($searchForCompanies)) {
                $companyEntities = $this->getEntities(
                    [
                        'filter' => [
                            'force' => [
                                [
                                    'column' => 'comp.id',
                                    'expr'   => 'in',
                                    'value'  => $searchForCompanies,
                                ],
                            ],
                        ],
                    ]
                );

                foreach ($companyEntities as $company) {
                    $companyLeadRemove[$company->getId()] = $company;
                }
            }

            unset($companyEntities, $searchForCompanies);
        } else {
            $companyLeadRemove[$companies->getId()] = $companies;

            $companies = [$companies->getId()];
        }

        if (!is_array($companies)) {
            $companies = [$companies];
        }

        $deleteCompany     = [];
        $dispatchEvents    = [];
        $deleteCompanyLead = [];

        $primaryRemoved = false;
        foreach ($companies as $companyId) {
            if (!isset($companyLeadRemove[$companyId])) {
                continue;
            }

            /** @var CompanyLead $companyLead */
            $companyLead = $this->getCompanyLeadRepository()->findOneBy(
                [
                    'lead'    => $lead,
                    'company' => $companyLeadRemove[$companyId],
                ]
            );

            if (null == $companyLead) {
                // Lead is not part of this list
                continue;
            }

            // Lead was manually added and now manually removed or was not manually added and now being removed
            $deleteCompanyLead[] = $companyLead;
            $dispatchEvents[]    = $companyId;

            // Update the Lead's primary company name if removed from the primary company
            if (!$primaryRemoved) {
                $primaryRemoved = $companyLead->getPrimary();
            }

            unset($companyLead);
        }

        if (!empty($deleteCompanyLead)) {
            $this->getCompanyLeadRepository()->deleteEntities($deleteCompanyLead);
        }

        if ($primaryRemoved) {
            // Set the contact's primary company to a remaining company or empty it out if none are left
            // This must happen before LeadEvents::LEAD_COMPANY_CHANGE to ensure the Lead::getCompany has the correct value
            $this->updateContactAfterPrimaryCompanyWasRemoved($lead);
        }

        // Clear CompanyLead entities from Doctrine memory
        $this->getCompanyLeadRepository()->detachEntities($deleteCompanyLead);

        if (!empty($dispatchEvents) && $this->dispatcher->hasListeners(LeadEvents::LEAD_COMPANY_CHANGE)) {
            foreach ($dispatchEvents as $companyId) {
                $event = new LeadChangeCompanyEvent($lead, $companyLeadRemove[$companyId], false);
                $this->dispatcher->dispatch($event, LeadEvents::LEAD_COMPANY_CHANGE);

                unset($event);
            }
        }

        unset($lead, $deleteCompany, $companies);
    }

    /**
     * Get a list of companies with names only.
     *
     * @param mixed[]|string $filter
     *
     * @return string[]
     */
    public function getSimpleLookupResults(string $type, array|string $filter = '', int $limit = 10, int $start = 0, ?string $exclude = ''): array
    {
        $valueColumn = 'id';
        if (!in_array($type, ['companyfield', 'lead.company'])) {
            return [];
        }
        if ('lead.company' === $type) {
            $column    = 'companyname';
            $filterVal = $filter;
        } else {
            if (is_array($filter)) {
                $column    = $filter[0];
                $filterVal = $filter[1];
            } else {
                $column = $filter;
            }
        }

        $expr      = new ExpressionBuilder($this->em->getConnection());
        $composite = $expr->and($expr->like("comp.$column", ':filterVar'));

        // Exclude company if $exclude is provided
        if ('' !== $exclude) {
            $composite = $expr->and(
                $composite,
                $expr->neq('comp.id', $exclude)
            );
        }

        // Validate owner permissions
        if (!$this->security->isGranted('lead:leads:viewother')) {
            $composite->with(
                $expr->or(
                    $expr->and(
                        $expr->isNull('comp.owner_id'),
                        $expr->eq('comp.created_by', (int) $this->userHelper->getUser()->getId())
                    ),
                    $expr->eq('comp.owner_id', (int) $this->userHelper->getUser()->getId())
                )
            );
        }

        return $this->getRepository()->getAjaxSimpleList($composite, ['filterVar' => $filterVal.'%', 'onlyNames' => true], $column, $valueColumn);
    }

    /**
     * Get list of entities for autopopulate fields.
     *
     * @param string                   $type
     * @param string|array<int,string> $filter
     * @param int                      $limit
     * @param int                      $start
     * @param array<string, mixed>     $options
     *
     * @return array<mixed>
     */
    public function getLookupResults($type, $filter = '', $limit = 10, $start = 0, array $options = []): array
    {
        $results = [];
        switch ($type) {
            case 'companyfield':
            case 'lead.company':
                if ('lead.company' === $type) {
                    $column    = 'companyname';
                    $filterVal = $filter;
                } else {
                    if (is_array($filter)) {
                        $column    = $filter[0];
                        $filterVal = $filter[1];
                    } else {
                        $column = $filter;
                    }
                }

                $expr      = new ExpressionBuilder($this->em->getConnection());
                $composite = $expr->and($expr->like("comp.$column", ':filterVar'));

                // Validate owner permissions
                if (!$this->security->isGranted('lead:leads:viewother')) {
                    $composite->with(
                        $expr->or(
                            $expr->and(
                                $expr->isNull('comp.owner_id'),
                                $expr->eq('comp.created_by', (int) $this->userHelper->getUser()->getId())
                            ),
                            $expr->eq('comp.owner_id', (int) $this->userHelper->getUser()->getId())
                        )
                    );
                }

                $results = $this->getRepository()->getAjaxSimpleList(
                    $composite,
                    ['filterVar' => $filterVal.'%'],
                    $column,
                    'id',
                    $limit,
                    $start
                );

                break;
        }

        return $results;
    }

    /**
     * @throws MethodNotAllowedHttpException
     */
    protected function dispatchEvent($action, &$entity, $isNew = false, ?Event $event = null): ?Event
    {
        if (!$entity instanceof Company) {
            throw new MethodNotAllowedHttpException(['Email']);
        }

        switch ($action) {
            case 'pre_save':
                $name = LeadEvents::COMPANY_PRE_SAVE;
                break;
            case 'post_save':
                $name = LeadEvents::COMPANY_POST_SAVE;
                break;
            case 'pre_delete':
                $name = LeadEvents::COMPANY_PRE_DELETE;
                break;
            case 'post_delete':
                $name = LeadEvents::COMPANY_POST_DELETE;
                break;
            default:
                return null;
        }

        if ($this->dispatcher->hasListeners($name)) {
            if (empty($event)) {
                $event = new CompanyEvent($entity, $isNew);
                $event->setEntityManager($this->em);
            }

            $this->dispatcher->dispatch($event, $name);

            return $event;
        } else {
            return null;
        }
    }

    /**
     * Company Merge function, will merge $mainCompany with $secCompany -  empty records from main company will be
     * filled with secondary then secondary will be deleted.
     *
     * @return mixed
     */
    public function companyMerge($mainCompany, $secCompany)
    {
        $this->logger->debug('COMPANY: Merging companies');

        $mainCompanyId = $mainCompany->getId();
        $secCompanyId  = $secCompany->getId();

        // if they are the same lead, then just return one
        if ($mainCompanyId === $secCompanyId) {
            return $mainCompany;
        }
        // merge fields
        $mergeSecFields    = $secCompany->getFields();
        $mainCompanyFields = $mainCompany->getFields();
        foreach ($mergeSecFields as $group => $groupFields) {
            foreach ($groupFields as $alias => $details) {
                // fill in empty main company fields with secondary company fields
                if (empty($mainCompanyFields[$group][$alias]['value']) && !empty($details['value'])) {
                    $mainCompany->addUpdatedField($alias, $details['value']);
                    $this->logger->debug('Company: Updated '.$alias.' = '.$details['value']);
                }
            }
        }

        // merge owner
        $mainCompanyOwner = $mainCompany->getOwner();
        $secCompanyOwner  = $secCompany->getOwner();

        if (null === $mainCompanyOwner && null !== $secCompanyOwner) {
            $mainCompany->setOwner($secCompanyOwner);
        }

        // move all leads from secondary company to main company
        $companyLeadRepo = $this->getCompanyLeadRepository();
        $secCompanyLeads = $companyLeadRepo->getCompanyLeads($secCompanyId);

        foreach ($secCompanyLeads as $lead) {
            $this->addLeadToCompany($mainCompany->getId(), $lead['lead_id']);
        }

        $event = new CompanyMergeEvent($mainCompany, $secCompany);
        $this->dispatcher->dispatch($event, LeadEvents::COMPANY_PRE_MERGE);

        // save the updated company
        $this->saveEntity($mainCompany, false);

        $this->dispatcher->dispatch($event, LeadEvents::COMPANY_POST_MERGE);

        // delete the old company
        $this->deleteEntity($secCompany);

        // return the merged company
        return $mainCompany;
    }

    /**
     * @return array
     */
    public function fetchCompanyFields()
    {
        if (empty($this->companyFields)) {
            $this->companyFields = $this->leadFieldModel->getEntities(
                [
                    'filter' => [
                        'force' => [
                            [
                                'column' => 'f.isPublished',
                                'expr'   => 'eq',
                                'value'  => true,
                            ],
                            [
                                'column' => 'f.object',
                                'expr'   => 'eq',
                                'value'  => 'company',
                            ],
                        ],
                    ],
                    'hydration_mode' => 'HYDRATE_ARRAY',
                    'result_cache'   => new ResultCacheOptions(LeadField::CACHE_NAMESPACE),
                ]
            );
        }

        return $this->companyFields;
    }

    public function extractCompanyDataFromImport(array &$mappedFields, array &$data): array
    {
        $companyData    = [];
        $companyFields  = [];
        $internalFields = $this->fetchCompanyFields();

        if (!isset($mappedFields['companyname']) && isset($mappedFields['company'])) {
            $mappedFields['companyname'] = $mappedFields['company'];

            unset($mappedFields['company']);
        }

        foreach ($mappedFields as $mauticField => $importField) {
            foreach ($internalFields as $entityField) {
                if ($entityField['alias'] === $mauticField) {
                    $companyData[$importField]   = $data[$importField];
                    $companyFields[$mauticField] = $importField;
                    unset($data[$importField]);
                    unset($mappedFields[$mauticField]);
                    break;
                }
            }
        }

        return [$companyFields, $companyData];
    }

    /**
     * @param array<string>   $fields
     * @param array<mixed>    $data
     * @param int|string|null $owner
     */
    public function import(array $fields, array $data, $owner = null, bool $skipIfExists = false): bool
    {
        $company = $this->importCompany($fields, $data, $owner, false, $skipIfExists);

        if (null === $company) {
            throw new \Exception($this->translator->trans('mautic.lead.import.unique_field_not_exist', [], 'flashes'));
        }

        $merged = !$company->isNew();

        $this->saveEntity($company);

        return $merged;
    }

    /**
     * @param array $fields
     * @param array $data
     *
     * @return Company|null
     *
     * @throws \Exception
     */
    public function importCompany($fields, $data, $owner = null, $persist = true, $skipIfExists = false)
    {
        try {
            $duplicateCompanies = $this->companyDeduper->checkForDuplicateCompanies($this->getFieldData($fields, $data));
        } catch (UniqueFieldNotFoundException) {
            return null;
        }

        $company = !empty($duplicateCompanies) ? $duplicateCompanies[0] : new Company();
        \assert($company instanceof Company);

        if (!$company->isNew() && !$this->existDataForUpdate($fields, $data)) {
            return $company;
        }

        if ($company->isNew()) {
            $granted = $this->security->isGranted('lead:leads:create');
        } else {
            $granted = $this->security->hasEntityAccess(
                'lead:leads:editown',
                'lead:leads:editother',
                $company->getPermissionUser()
            );
        }

        if (!$granted) {
            throw new \Exception($this->translator->trans('mautic.lead.import.error.unauthorized', ['%username%' => $this->userHelper->getUser()->getUsername()]));
        }

        if (!empty($fields['dateAdded']) && !empty($data[$fields['dateAdded']])) {
            $dateAdded = new DateTimeHelper($data[$fields['dateAdded']]);
            $company->setDateAdded($dateAdded->getUtcDateTime());
        }
        unset($fields['dateAdded']);

        if (!empty($fields['dateModified']) && !empty($data[$fields['dateModified']])) {
            $dateModified = new DateTimeHelper($data[$fields['dateModified']]);
            $company->setDateModified($dateModified->getUtcDateTime());
        }
        unset($fields['dateModified']);

        if (!empty($fields['createdByUser']) && !empty($data[$fields['createdByUser']])) {
            $userRepo      = $this->em->getRepository(User::class);
            $createdByUser = $userRepo->findByIdentifier($data[$fields['createdByUser']]);
            if (null !== $createdByUser) {
                $company->setCreatedBy($createdByUser);
            }
        }
        unset($fields['createdByUser']);

        if (!empty($fields['modifiedByUser']) && !empty($data[$fields['modifiedByUser']])) {
            $userRepo       = $this->em->getRepository(User::class);
            $modifiedByUser = $userRepo->findByIdentifier($data[$fields['modifiedByUser']]);
            if (null !== $modifiedByUser) {
                $company->setModifiedBy($modifiedByUser);
            }
        }
        unset($fields['modifiedByUser']);

        if (null !== $owner) {
            $company->setOwner($this->em->getReference(User::class, $owner));
        }

        $fieldData = $this->getFieldData($fields, $data);

        $fieldErrors = [];

        foreach ($this->fetchCompanyFields() as $entityField) {
            // Skip If value already exists
            if ($skipIfExists && !$company->isNew() && !empty($company->getProfileFields()[$entityField['alias']])) {
                unset($fieldData[$entityField['alias']]);
                continue;
            }

            if (isset($fieldData[$entityField['alias']])) {
                $fieldData = $this->getCleanedFieldData($entityField, $fieldData);

                if ('NULL' === $fieldData[$entityField['alias']]) {
                    $fieldData[$entityField['alias']] = null;

                    continue;
                }

                try {
                    $this->cleanFields($fieldData, $entityField);
                } catch (\Exception $exception) {
                    $fieldErrors[] = $entityField['alias'].': '.$exception->getMessage();
                }

                // Skip if the value is in the CSV row
                continue;
            } elseif ($company->isNew() && $entityField['defaultValue']) {
                // Fill in the default value if any
                $fieldData[$entityField['alias']] = ('multiselect' === $entityField['type']) ? [$entityField['defaultValue']] : $entityField['defaultValue'];
            }
        }

        if ($fieldErrors) {
            $fieldErrors = implode("\n", $fieldErrors);

            throw new \Exception($fieldErrors);
        }

        // All clear
        foreach ($fieldData as $field => $value) {
            $company->addUpdatedField($field, $value);
        }

        if ($persist) {
            $this->saveEntity($company);
        }

        return $company;
    }

    /**
     * @return mixed[]
     */
    public function checkForDuplicateCompanies(array $queryFields): array
    {
        return $this->companyDeduper->checkForDuplicateCompanies($queryFields);
    }

    /**
     * @param array $fields
     * @param array $data
     */
    protected function getFieldData($fields, $data): array
    {
        // Set profile data using the form so that values are validated
        $fieldData = [];
        foreach ($fields as $importField => $entityField) {
            // Prevent overwriting existing data with empty data
            if (array_key_exists($importField, $data) && !is_null($data[$importField]) && '' != $data[$importField]) {
                $fieldData[$entityField] = $data[$importField];
            }
        }

        return $fieldData;
    }

    private function updateContactAfterPrimaryCompanyWasRemoved(Lead $lead): void
    {
        $primaryCompanyName = '';
        $companyLead        = null;
        $newPrimaryCompany  = null;

        // Find another company to make primary if applicable
        $leadCompanies = $this->getCompanyLeadRepository()->getCompaniesByLeadId($lead->getId());
        if (count($leadCompanies)) {
            $newPrimaryArray   = reset($leadCompanies);
            $newPrimaryCompany = $this->em->getReference(Company::class, $newPrimaryArray['company_id']);

            /** @var CompanyLead $companyLead */
            $companyLead = $this->getCompanyLeadRepository()->findOneBy(
                [
                    'lead'    => $lead,
                    'company' => $newPrimaryCompany,
                ]
            );

            $companyLead->setPrimary(true);
            $this->getCompanyLeadRepository()->saveEntity($companyLead);

            $primaryCompanyName = $newPrimaryArray['companyname'];
        }

        $lead->addUpdatedField('company', $primaryCompanyName)
            ->setDateModified(new \DateTime());
        $this->em->getRepository(Lead::class)->saveEntity($lead);

        if (null !== $newPrimaryCompany) {
            $this->getCompanyLeadRepository()->detachEntity($newPrimaryCompany);
        }

        if (null !== $companyLead) {
            $this->getCompanyLeadRepository()->detachEntity($companyLead);
        }
    }

    /**
     * @param mixed[] $fields
     * @param mixed[] $data
     */
    private function existDataForUpdate(array $fields, array $data): bool
    {
        $updateData = $this->getFieldData($fields, $data);
        $uniqueData = $this->companyDeduper->getUniqueData($updateData);

        return (bool) array_diff_assoc($updateData, $uniqueData);
    }

    /**
     * @param array<mixed> $fieldData
     *
     * @return array<mixed>
     */
    private function getCleanedFieldData(mixed $entityField, array $fieldData): array
    {
        $fieldData[$entityField['alias']] = InputHelper::_(
            $fieldData[$entityField['alias']],
            'html' === $entityField['type'] ? 'html' : 'string'
        );

        return $fieldData;
    }
}
