<?php
/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

namespace Espo\Services;

use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Exceptions\NotFound;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\BadRequest;

use Espo\ORM\Entity;

class Import extends \Espo\Services\Record
{
    const REVERT_PERMANENTLY_REMOVE_PERIOD_DAYS = 2;

    protected function init()
    {
        parent::init();

        $this->addDependency('serviceFactory');
        $this->addDependency('fileManager');
        $this->addDependency('selectManagerFactory');
        $this->addDependency('fileStorageManager');
    }

    protected $dateFormatsMap = [
        'YYYY-MM-DD' => 'Y-m-d',
        'DD-MM-YYYY' => 'd-m-Y',
        'MM-DD-YYYY' => 'm-d-Y',
        'MM/DD/YYYY' => 'm/d/Y',
        'DD/MM/YYYY' => 'd/m/Y',
        'DD.MM.YYYY' => 'd.m.Y',
        'MM.DD.YYYY' => 'm.d.Y',
        'YYYY.MM.DD' => 'Y.m.d',
    ];

    protected $timeFormatsMap = [
        'HH:mm' => 'H:i',
        'HH:mm:ss' => 'H:i:s',
        'hh:mm a' => 'h:i a',
        'hh:mma' => 'h:ia',
        'hh:mm A' => 'h:iA',
        'hh:mmA' => 'h:iA',
    ];

    protected $services = [];

    protected function getSelectManagerFactory()
    {
        return $this->injections['selectManagerFactory'];
    }

    protected function getFileStorageManager()
    {
        return $this->injections['fileStorageManager'];
    }

    protected function getFileManager()
    {
        return $this->injections['fileManager'];
    }

    protected function getAcl()
    {
        return $this->injections['acl'];
    }

    protected function getMetadata()
    {
        return $this->injections['metadata'];
    }

    protected function getServiceFactory()
    {
        return $this->injections['serviceFactory'];
    }

    public function loadAdditionalFields(Entity $entity)
    {
        parent::loadAdditionalFields($entity);

        $importedCount = $this->getRepository()->countRelated($entity, 'imported');
        $duplicateCount = $this->getRepository()->countRelated($entity, 'duplicates');
        $updatedCount = $this->getRepository()->countRelated($entity, 'updated');
        $entity->set([
            'importedCount' => $importedCount,
            'duplicateCount' => $duplicateCount,
            'updatedCount' => $updatedCount
        ]);
    }

    public function findLinked($id, $link, $params)
    {
        $entity = $this->getRepository()->get($id);
        $foreignEntityType = $entity->get('entityType');

        if (!$this->getAcl()->check($entity, 'read')) {
            throw new Forbidden();
        }
        if (!$this->getAcl()->check($foreignEntityType, 'read')) {
            throw new Forbidden();
        }

        $selectParams = $this->getSelectManager($foreignEntityType)->getSelectParams($params, true);

        if (array_key_exists($link, $this->linkSelectParams)) {
            $selectParams = array_merge($selectParams, $this->linkSelectParams[$link]);
        }

        $collection = $this->getRepository()->findRelated($entity, $link, $selectParams);

        $recordService = $this->getRecordService($foreignEntityType);

        foreach ($collection as $e) {
            $recordService->loadAdditionalFieldsForList($e);
            $recordService->prepareEntityForOutput($e);
        }

        $total = $this->getRepository()->countRelated($entity, $link, $selectParams);

        return [
            'total' => $total,
            'collection' => $collection
        ];
    }

    protected function readCsvString(&$string, $CSV_SEPARATOR = ';', $CSV_ENCLOSURE = '"', $CSV_LINEBREAK = "\n")
    {
        $o = [];

        $cnt = strlen($string);
        $esc = false;
        $escesc = false;

        $num = 0;
        $i = 0;
        while ($i < $cnt) {
            $s = $string[$i];
            if ($s == $CSV_LINEBREAK) {
                if ($esc) {
                    $o[$num].= $s;
                } else {
                    $i++;
                    break;
                }
            } else if ($s == $CSV_SEPARATOR) {
                if ($esc) {
                    $o[$num].= $s;
                } else {
                    $num++;
                    $esc = false;
                    $escesc = false;
                }
            } else if ($s == $CSV_ENCLOSURE) {
                if ($escesc) {
                    $o[$num].= $CSV_ENCLOSURE;
                    $escesc = false;
                }
                if ($esc) {
                    $esc = false;
                    $escesc = true;
                } else {
                    $esc = true;
                    $escesc = false;
                }
            } else {
                if (!array_key_exists($num, $o)) {
                    $o[$num] = '';
                }
                if ($escesc) {
                    $o[$num] .= $CSV_ENCLOSURE;
                    $escesc = false;
                }
                $o[$num] .= $s;
            }
            $i++;
        }
        $string = substr($string, $i);

        $keys = array_keys($o);
        $maxKey = end($keys);
        for ($i = 0; $i < $maxKey; $i++) {
            if (!array_key_exists($i, $o)) {
                $o[$i] = '';
            }
        }

        return $o;
    }

    public function revert($id)
    {
        $import = $this->getEntityManager()->getEntity('Import', $id);
        if (empty($import)) {
            throw new NotFound();
        }

        if (!$this->getAcl()->check($import, 'delete')) {
            throw new Forbidden();
        }

        $pdo = $this->getEntityManager()->getPDO();

        $sql = "SELECT * FROM import_entity WHERE import_id = ".$pdo->quote($import->id) . " AND is_imported = 1";

        $removeFromDb = false;
        $createdAt = $import->get('createdAt');
        if ($createdAt) {
            $dtNow = new \DateTime();
            $createdAtDt = new \DateTime($createdAt);
            $dayDiff = ($dtNow->getTimestamp() - $createdAtDt->getTimestamp()) / 60 / 60 / 24;
            if ($dayDiff < self::REVERT_PERMANENTLY_REMOVE_PERIOD_DAYS) {
                $removeFromDb = true;
            }
        }

        $sth = $pdo->prepare($sql);
        $sth->execute();
        while ($row = $sth->fetch(\PDO::FETCH_ASSOC)) {
            if (empty($row['entity_type']) || empty($row['entity_id'])) {
                continue;
            }
            $entityType = $row['entity_type'];
            $entityId = $row['entity_id'];

            $entity = $this->getEntityManager()->getEntity($entityType, $entityId);
            if ($entity) {
                $this->getEntityManager()->removeEntity($entity, [
                    'noStream' => true,
                    'noNotifications' => true,
                    'import' => true
                ]);
            }
            if ($removeFromDb) {
                $this->getEntityManager()->getRepository($entityType)->deleteFromDb($entityId);
            }
        }

        $this->getEntityManager()->removeEntity($import);

        $this->processActionHistoryRecord('delete', $import);

        return true;
    }

    public function removeDuplicates($id)
    {
        $import = $this->getEntityManager()->getEntity('Import', $id);
        if (empty($import)) {
            throw new NotFound();
        }

        if (!$this->getAcl()->check($import, 'delete')) {
            throw new Forbidden();
        }

        $pdo = $this->getEntityManager()->getPDO();


        $sql = "SELECT * FROM import_entity WHERE import_id = ".$pdo->quote($import->id) . " AND is_duplicate = 1";

        $sth = $pdo->prepare($sql);
        $sth->execute();
        while ($row = $sth->fetch(\PDO::FETCH_ASSOC)) {
            if (empty($row['entity_type']) || empty($row['entity_id'])) {
                continue;
            }
            $entityType = $row['entity_type'];
            $entityId = $row['entity_id'];

            $entity = $this->getEntityManager()->getEntity($entityType, $entityId);
            if ($entity) {
                $this->getEntityManager()->removeEntity($entity);
            }
            $this->getEntityManager()->getRepository($entity->getEntityType())->deleteFromDb($entityId);
        }

        return true;
    }

    public function jobRunIdleImport($data)
    {
        if (
            empty($data->userId) ||
            empty($data->userId) ||
            !isset($data->importAttributeList) ||
            !isset($data->params) ||
            !isset($data->entityType)
        ) {
            throw new Error("Import: Bad job data.");
        }

        $entityType = $data->entityType;
        $params = json_decode(json_encode($data->params), true);
        $attachmentId = $data->attachmentId;
        $importId = $data->importId;
        $importAttributeList = $data->importAttributeList;
        $userId = $data->userId;

        $user = $this->getEntityManager()->getEntity('User', $userId);

        if (!$user) {
            throw new Error("Import: User not found.");
        }
        if (!$user->get('isActive')) {
            throw new Error("Import: User is not active.");
        }

        $this->import($entityType, $importAttributeList, $attachmentId, $params, $importId, $user);
    }

    public function import($scope, array $importAttributeList, $attachmentId, array $params = [], $importId = null, $user = null)
    {
        $delimiter = ',';
        if (!empty($params['delimiter'])) {
            $delimiter = $params['delimiter'];
        }
        $enclosure = '"';
        if (!empty($params['textQualifier'])) {
            $enclosure = $params['textQualifier'];
        }

        $delimiter = str_replace('\t', "\t", $delimiter);

        if (!$user) {
            $user = $this->getUser();
        }

        if (!$user->isAdmin()) {
            $forbiddenAttrbuteList = $this->getAclManager()->getScopeForbiddenAttributeList($user, $scope, 'edit');
            foreach ($importAttributeList as $i => $attribute) {
                if (in_array($attribute, $forbiddenAttrbuteList)) {
                    unset($importAttributeList[$i]);
                }
            }

            if (!$this->getAclManager()->checkScope($user, $scope, 'create')) {
                throw new Error('Import: Create is forbidden.');
            }
        }

        $attachment = $this->getEntityManager()->getEntity('Attachment', $attachmentId);
        if (!$attachment) {
            throw new Error('Import error');
        }

        $contents = $this->getFileStorageManager()->getContents($attachment);
        if (empty($contents)) {
            throw new Error('Import error');
        }

        if ($importId) {
            $import = $this->getEntityManager()->getEntity('Import', $importId);
            if (!$import) {
                throw new Error('Import: Could not find import record.');
            }
        } else {
            $import = $this->getEntityManager()->getEntity('Import');
            $import->set([
                'entityType' => $scope,
                'fileId' => $attachmentId
            ]);
            $import->set('status', 'In Process');
        }

        $this->getEntityManager()->saveEntity($import);

        $this->processActionHistoryRecord('create', $import);

        if (!empty($params['idleMode'])) {
            $params['idleMode'] = false;

            $job = $this->getEntityManager()->getEntity('Job');
            $job->set([
                'serviceName' => 'Import',
                'methodName' => 'jobRunIdleImport',
                'data' => [
                    'entityType' => $scope,
                    'params' => $params,
                    'attachmentId' => $attachmentId,
                    'importAttributeList' => $importAttributeList,
                    'importId' => $import->id,
                    'userId' => $this->getUser()->id
                ]
            ]);
            $this->getEntityManager()->saveEntity($job);

            return [
                'id' => $import->id,
                'countCreated' => 0,
                'countUpdated' => 0
            ];
        }

        try {
            $pdo = $this->getEntityManager()->getPDO();

            $result = [
                'importedIds' => [],
                'updatedIds' => [],
                'duplicateIds' => []
            ];
            $i = -1;

            $contents = str_replace("\r\n", "\n", $contents);

            while ($arr = $this->readCsvString($contents, $delimiter, $enclosure)) {
                $i++;
                if ($i == 0 && !empty($params['headerRow'])) {
                    continue;
                }

                if (count($arr) == 1 && empty($arr[0]) && count($importAttributeList) > 1) {
                    continue;
                }
                $r = $this->importRow($scope, $importAttributeList, $arr, $params, $user);
                if (empty($r)) {
                    continue;
                }
                if (!empty($r['isImported'])) {
                    $result['importedIds'][] = $r['id'];
                }
                if (!empty($r['isUpdated'])) {
                    $result['updatedIds'][] = $r['id'];
                }
                if (!empty($r['isDuplicate'])) {
                    $result['duplicateIds'][] = $r['id'];
                }
                $sql = "
                    INSERT INTO import_entity
                    (entity_type, entity_id, import_id, is_imported, is_updated, is_duplicate)
                    VALUES
                    (:entityType, :entityId, :importId, :isImported, :isUpdated, :isDuplicate)
                ";
                $sth = $pdo->prepare($sql);
                $sth->bindValue(':entityType', $scope);
                $sth->bindValue(':entityId', $r['id']);
                $sth->bindValue(':importId', $import->id);
                $sth->bindValue(':isImported', !empty($r['isImported']), \PDO::PARAM_BOOL);
                $sth->bindValue(':isUpdated', !empty($r['isUpdated']), \PDO::PARAM_BOOL);
                $sth->bindValue(':isDuplicate', !empty($r['isDuplicate']), \PDO::PARAM_BOOL);

                $sth->execute();
            }
        } catch (\Exception $e) {
            $GLOBALS['log']->error('Import Error: '. $e->getMessage());
            $import->set('status', 'Failed');
        }

        $import->set('status', 'Complete');

        $this->getEntityManager()->saveEntity($import);

        return [
            'id' => $import->id,
            'countCreated' => count($result['importedIds']),
            'countUpdated' => count($result['updatedIds']),
        ];
    }

    public function importRow($scope, array $importAttributeList, array $row, array $params = [], $user)
    {
        $id = null;
        $action = 'create';
        if (!empty($params['action'])) {
            $action = $params['action'];
        }

        if (empty($importAttributeList)) {
            return;
        }

        if (in_array($action, ['createAndUpdate', 'update'])) {
            $updateByAttributeList = [];
            $whereClause = [];
            if (!empty($params['updateBy']) && is_array($params['updateBy'])) {
                foreach ($params['updateBy'] as $i) {
                    if (array_key_exists($i, $importAttributeList)) {
                        $updateByAttributeList[] = $importAttributeList[$i];
                        $whereClause[$importAttributeList[$i]] = $row[$i];
                    }
                }
            }
        }

        $recordService = $this->getRecordService($scope);

        if (in_array($action, ['createAndUpdate', 'update'])) {
            if (!count($updateByAttributeList)) {
                return;
            }
            $entity = $this->getEntityManager()->getRepository($scope)->where($whereClause)->findOne();

            if ($entity) {
                if (!$user->isAdmin()) {
                    if (!$this->getAclManager()->checkEntity($user, $entity, 'edit')) {
                        return;
                    }
                }
            }
            if (!$entity) {
                if ($action == 'createAndUpdate') {
                    $entity = $this->getEntityManager()->getEntity($scope);
                    if (array_key_exists('id', $whereClause)) {
                        $entity->set('id', $whereClause['id']);
                    }
                } else {
                    return;
                }
            }
        } else {
            $entity = $this->getEntityManager()->getEntity($scope);
        }

        $isNew = $entity->isNew();

        if (!empty($params['defaultValues'])) {
            if (is_object($params['defaultValues'])) {
                $v = get_object_vars($params['defaultValues']);
            } else {
                $v = $params['defaultValues'];
            }
            $entity->set($v);
        }

        $attributeDefs = $entity->getAttributes();
        $relDefs = $entity->getRelations();

        $phoneFieldList = [];
        if (
            $entity->hasAttribute('phoneNumber')
            &&
            $entity->getAttributeParam('phoneNumber', 'fieldType') === 'phone'
        ) {
            $typeList = $this->getMetadata()->get('entityDefs.' . $scope . '.fields.phoneNumber.typeList', []);
            foreach ($typeList as $type) {
                $attr = str_replace(' ', '_', ucfirst($type));
                $phoneFieldList[] = 'phoneNumber' . $attr;
            }
        }

        $valueMap = (object) [];
        foreach ($importAttributeList as $i => $attribute) {
            if (!empty($attribute)) {
                if (!array_key_exists($i, $row)) {
                    continue;
                }
                $value = $row[$i];
                $valueMap->$attribute = $value;
            }
        }

        foreach ($importAttributeList as $i => $attribute) {
            if (!empty($attribute)) {
                if (!array_key_exists($i, $row)) {
                    continue;
                }
                $value = $row[$i];
                if ($attribute == 'id') {
                    if ($params['action'] == 'create') {
                        $entity->id = $value;
                    }
                    continue;
                }
                if (array_key_exists($attribute, $attributeDefs)) {
                    $attributeType = $entity->getAttributeType($attribute);

                    if ($value !== '') {
                        $type = $this->getMetadata()->get(['entityDefs', $scope, 'fields', $attribute, 'type']);

                        if ($attribute === 'emailAddress' && $type === 'email') {
                            $emailAddressData = $entity->get('emailAddressData');
                            $emailAddressData = $emailAddressData ?? [];
                            $o = (object) [
                                'emailAddress' => $value,
                                'primary' => true,
                            ];
                            $emailAddressData[] = $o;
                            $entity->set('emailAddressData', $emailAddressData);
                            continue;
                        }

                        if ($attribute === 'phoneNumber' && $type === 'phone') {
                            $phoneNumberData = $entity->get('phoneNumberData');
                            $phoneNumberData = $phoneNumberData ?? [];
                            $o = (object) [
                                'phoneNumber' => $value,
                                'primary' => true,
                            ];
                            $phoneNumberData[] = $o;
                            $entity->set('phoneNumberData', $phoneNumberData);
                            continue;
                        }

                        if ($type == 'personName') {
                            $firstNameAttribute = 'first' . ucfirst($attribute);
                            $lastNameAttribute = 'last' . ucfirst($attribute);
                            $middleNameAttribute = 'middle' . ucfirst($attribute);

                            $personNameData = $this->parsePersonName($value, $params['personNameFormat']);

                            if (!$entity->get($firstNameAttribute) && isset($personNameData['firstName'])) {
                                $personNameData['firstName'] = $this->prepareAttributeValue(
                                    $entity, $firstNameAttribute, $personNameData['firstName']
                                );
                                $entity->set($firstNameAttribute, $personNameData['firstName']);
                            }
                            if (!$entity->get($lastNameAttribute)) {
                                $personNameData['lastName'] = $this->prepareAttributeValue(
                                    $entity, $lastNameAttribute, $personNameData['lastName']
                                );
                                $entity->set($lastNameAttribute, $personNameData['lastName']);
                            }
                            if (!$entity->get($middleNameAttribute) && isset($personNameData['middleName'])) {
                                $personNameData['middleName'] = $this->prepareAttributeValue(
                                    $entity, $middleNameAttribute, $personNameData['middleName']
                                );
                                $entity->set($middleNameAttribute, $personNameData['middleName']);
                            }
                            continue;
                        }
                    }

                    if (
                        $value === '' &&
                        !in_array($attributeType, [Entity::BOOL])
                    ) {
                        continue;
                    }

                    $entity->set($attribute, $this->parseValue($entity, $attribute, $value, $params));

                } else {
                    if (in_array($attribute, $phoneFieldList) && !empty($value)) {
                        $phoneNumberData = $entity->get('phoneNumberData');
                        $isPrimary = false;
                        if (empty($phoneNumberData)) {
                            $phoneNumberData = [];
                            if (empty($valueMap->phoneNumber)) $isPrimary = true;
                        }
                        $type = str_replace('phoneNumber', '', $attribute);
                        $type = str_replace('_', ' ', $type);
                        $o = (object) [
                            'phoneNumber' => $value,
                            'type' => $type,
                            'primary' => $isPrimary,
                        ];
                        $phoneNumberData[] = $o;
                        $entity->set('phoneNumberData', $phoneNumberData);
                    }

                    if (
                        strpos($attribute, 'emailAddress') === 0 && $attribute !== 'emailAddress'
                        &&
                        $entity->hasAttribute('emailAddress')
                        &&
                        $entity->hasAttribute('emailAddressData')
                        &&
                        is_numeric(substr($attribute, 12))
                        &&
                        intval(substr($attribute, 12)) >= 2
                        &&
                        intval(substr($attribute, 12)) <= 4
                        &&
                        !empty($value)
                    ) {
                        $emailAddressData = $entity->get('emailAddressData');
                        $isPrimary = false;
                        if (empty($emailAddressData)) {
                            $emailAddressData = [];
                            if (empty($valueMap->emailAddress)) $isPrimary = true;
                        }
                        $o = (object) [
                            'emailAddress' => $value,
                            'primary' => $isPrimary,
                        ];
                        $emailAddressData[] = $o;
                        $entity->set('emailAddressData', $emailAddressData);
                    }
                }
            }
        }

        $defaultCurrency = $this->getConfig('defaultCurrency');
        if (!empty($params['currency'])) {
            $defaultCurrency = $params['currency'];
        }

        $mFieldsDefs = $this->getMetadata()->get(['entityDefs', $entity->getEntityType(), 'fields'], []);

        foreach ($mFieldsDefs as $field => $defs) {
            if (!empty($defs['type']) && $defs['type'] === 'currency') {
                if ($entity->has($field) && !$entity->get($field . 'Currency')) {
                    $entity->set($field . 'Currency', $defaultCurrency);
                }
            }
        }

        foreach ($importAttributeList as $i => $attribute) {
            if (!array_key_exists($attribute, $attributeDefs)) continue;;
            $defs = $attributeDefs[$attribute];
            $type = $attributeDefs[$attribute]['type'];

            if (in_array($type, [Entity::FOREIGN, Entity::VARCHAR]) && !empty($defs['foreign'])) {
                $relatedEntityIsPerson = is_array($defs['foreign']) && in_array('firstName', $defs['foreign']) && in_array('lastName', $defs['foreign']);

                if ($defs['foreign'] === 'name' || $relatedEntityIsPerson) {
                    if ($entity->has($attribute)) {
                        $relation = $defs['relation'];
                        if ($attribute == $relation . 'Name' && !$entity->has($relation . 'Id') && array_key_exists($relation, $relDefs)) {
                            if ($relDefs[$relation]['type'] == Entity::BELONGS_TO) {
                                $value = $entity->get($attribute);
                                $scope = $relDefs[$relation]['entity'];

                                if ($relatedEntityIsPerson) {
                                    $where = $this->parsePersonName($value, $params['personNameFormat']);
                                } else {
                                    $where['name'] = $value;
                                }

                                $found = $this->getEntityManager()->getRepository($scope)->where($where)->findOne();

                                if ($found) {
                                    $entity->set($relation . 'Id', $found->id);
                                    $entity->set($relation . 'Name', $found->get('name'));
                                } else {
                                    if (!in_array($scope, ['User', 'Team'])) {
                                        // TODO create related record with name $name and relate
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        $result = [];

        try {
            if ($isNew) {
                $isDuplicate = false;
                if (empty($params['skipDuplicateChecking'])) {
                    $isDuplicate = $recordService->checkIsDuplicate($entity);
                }
            }
            if ($entity->id) {
                $sql = $this->getEntityManager()->getRepository($entity->getEntityType())->deleteFromDb($entity->id, true);
            }
            $saveResult = $this->getEntityManager()->saveEntity($entity, [
                'noStream' => true,
                'noNotifications' => true,
                'import' => true,
                'silent' => !empty($params['silentMode']),
            ]);
            if ($saveResult) {
                $result['id'] = $entity->id;
                if ($isNew) {
                    $result['isImported'] = true;
                    if ($isDuplicate) {
                        $result['isDuplicate'] = true;
                    }
                } else {
                    $result['isUpdated'] = true;
                }
            }
        } catch (\Exception $e) {
            $GLOBALS['log']->error('Import: [' . $e->getCode() . '] ' .$e->getMessage());
        }

        return $result;
    }

    protected function prepareAttributeValue($entity, $attribute, $value)
    {
        if ($entity->getAttributeType($attribute) === $entity::VARCHAR) {
            $maxLength = $entity->getAttributeParam($attribute, 'len');
            if ($maxLength) {
                if (mb_strlen($value) > $maxLength) {
                    $value = substr($value, 0, $maxLength);
                }
            }
        }
        return $value;
    }

    protected function parsePersonName($value, $format)
    {
        $firstName = null;
        $lastName = $value;

        $middleName = null;

        switch ($format) {
            case 'f l':
                $pos = strpos($value, ' ');
                if ($pos) {
                    $firstName = trim(substr($value, 0, $pos));
                    $lastName = trim(substr($value, $pos + 1));
                }
                break;
            case 'l f':
                $pos = strpos($value, ' ');
                if ($pos) {
                    $lastName = trim(substr($value, 0, $pos));
                    $firstName = trim(substr($value, $pos + 1));
                }
                break;
            case 'l, f':
                $pos = strpos($value, ',');
                if ($pos) {
                    $lastName = trim(substr($value, 0, $pos));
                    $firstName = trim(substr($value, $pos + 1));
                }
                break;

            case 'f m l':
                $pos = strpos($value, ' ');
                if ($pos) {
                    $firstName = trim(substr($value, 0, $pos));
                    $lastName = trim(substr($value, $pos + 1));

                    $value = $lastName;

                    $pos = strpos($value, ' ');
                    if ($pos) {
                        $middleName = trim(substr($value, 0, $pos));
                        $lastName = trim(substr($value, $pos + 1));

                        return [
                            'firstName' => $firstName,
                            'middleName' => $middleName,
                            'lastName' => $lastName,
                        ];
                    }
                }
                break;

            case 'l f m':
                $pos = strpos($value, ' ');
                if ($pos) {
                    $lastName = trim(substr($value, 0, $pos));
                    $firstName = trim(substr($value, $pos + 1));

                    $value = $firstName;

                    $pos = strpos($value, ' ');
                    if ($pos) {
                        $firstName = trim(substr($value, 0, $pos));
                        $middleName = trim(substr($value, $pos + 1));

                        return [
                            'firstName' => $firstName,
                            'middleName' => $middleName,
                            'lastName' => $lastName,
                        ];
                    }
                }
                break;
        }
        return [
            'firstName' => $firstName,
            'lastName' => $lastName,
        ];
    }

    protected function parseValue(Entity $entity, $attribute, $value, $params = [])
    {
        $decimalMark = '.';
        if (!empty($params['decimalMark'])) {
            $decimalMark = $params['decimalMark'];
        }

        $dateFormat = 'Y-m-d';
        if (!empty($params['dateFormat'])) {
            if (!empty($this->dateFormatsMap[$params['dateFormat']])) {
                $dateFormat = $this->dateFormatsMap[$params['dateFormat']];
            }
        }

        $timeFormat = 'H:i';
        if (!empty($params['timeFormat'])) {
            if (!empty($this->timeFormatsMap[$params['timeFormat']])) {
                $timeFormat = $this->timeFormatsMap[$params['timeFormat']];
            }
        }

        $type = $entity->getAttributeType($attribute);

        switch ($type) {
            case Entity::DATE:
                $dt = \DateTime::createFromFormat($dateFormat, $value);
                if ($dt) {
                    return $dt->format('Y-m-d');
                }
                return null;

            case Entity::DATETIME:
                $timezone = new \DateTimeZone(isset($params['timezone']) ? $params['timezone'] : 'UTC');
                $dt = \DateTime::createFromFormat($dateFormat . ' ' . $timeFormat, $value, $timezone);
                if ($dt) {
                    $dt->setTimezone(new \DateTimeZone('UTC'));
                    return $dt->format('Y-m-d H:i:s');
                }
                return null;

            case Entity::FLOAT:
                $a = explode($decimalMark, $value);
                $a[0] = preg_replace('/[^A-Za-z0-9\-]/', '', $a[0]);

                if (count($a) > 1) {
                    return floatval($a[0] . '.' . $a[1]);
                } else {
                    return floatval($a[0]);
                }
            case Entity::INT:
                return intval($value);

            case Entity::BOOL:
                if ($value && strtolower($value) !== 'false' && $value !== '0') {
                    return true;
                }
                return false;

            case Entity::JSON_OBJECT:
                $value = \Espo\Core\Utils\Json::decode($value);
                return $value;

            case Entity::JSON_ARRAY:
                if (!is_string($value)) return;
                if (!strlen($value)) return;
                if ($value[0] === '[') {
                    $value = \Espo\Core\Utils\Json::decode($value);
                    return $value;
                } else {
                    $value = explode(',', $value);
                    return $value;
                }
        }

        $value = $this->prepareAttributeValue($entity, $attribute, $value);

        return $value;
    }

    protected function getRecordService($scope)
    {
        if (empty($this->services[$scope])) {
            if ($this->getServiceFactory()->checkExists($scope)) {
                $service = $this->getServiceFactory()->create($scope);
            } else {
                $service = $this->getServiceFactory()->create('Record');
                $service->setEntityType($scope);
            }
            $this->services[$scope] = $service;
        }
        return $this->services[$scope];
    }

    public function unmarkAsDuplicate($id, $entityType, $entityId)
    {
        $pdo = $this->getEntityManager()->getPDO();

        $sql = "
            UPDATE import_entity
            SET is_duplicate = 0
            WHERE
                import_id = ".$pdo->quote($id)." AND
                entity_type = ".$pdo->quote($entityType)." AND
                entity_id = ".$pdo->quote($entityId)."
        ";

        if ($pdo->query($sql)) {
            return true;
        }
    }
}
