<?php

namespace FiloBlu\Flow\Model\From;

use Exception;
use FiloBlu\Flow\Exception\BlockingException;
use FiloBlu\Flow\Exception\NonBlockingException;
use FiloBlu\Flow\Helper\CustomerHelper;
use FiloBlu\Flow\Helper\Data;
use FiloBlu\Flow\Model\Processor;
use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Customer\Model\CustomerFactory;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ProductMetadataInterface;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\Data\Collection\AbstractDb;
use Magento\Framework\Model\Context;
use Magento\Framework\Model\ResourceModel\AbstractResource;
use Magento\Framework\ObjectManagerInterface;
use Magento\Framework\Registry;
use RuntimeException;
use Throwable;
use Zend_Db;

use function count;

/**
 * Class Customer
 * @package FiloBlu\Flow\Model\From
 */
class Customer extends Eavs
{

    /**
     * @var CustomerRepositoryInterface
     */
    protected $customerRepositoryInterface;
    /**
     * @var CustomerFactory
     */
    protected $customerFactory;
    /**
     * @var Data
     */
    protected $helperFlow;
    /**
     * @var CustomerHelper
     */
    protected $customerHelper;
    /**
     * @var
     */
    protected $entityManagerClassName;
    /**
     * @var
     */
    protected $websiteId;
    /**
     * @var
     */
    protected $storeId;
    /**
     * @var \Magento\Framework\DB\Adapter\AdapterInterface
     */
    protected $connection;
    /**
     * @var
     */
    protected $channelConfig;

    /**
     * @param ScopeConfigInterface $scopeConfig
     * @param Context $context
     * @param Registry $registry
     * @param Data $helperFlow
     * @param ObjectManagerInterface $objectManager
     * @param ProductMetadataInterface $productMetadata
     * @param ResourceConnection $resourceConnection
     * @param CustomerRepositoryInterface $customerRepositoryInterface
     * @param CustomerFactory $customerFactory
     * @param CustomerHelper $customerHelper
     * @param AbstractResource|null $resource
     * @param AbstractDb|null $resourceCollection
     * @param array $data
     */
    public function __construct(
        ScopeConfigInterface $scopeConfig,
        Context $context,
        Registry $registry,
        Data $helperFlow,
        ObjectManagerInterface $objectManager,
        ProductMetadataInterface $productMetadata,
        ResourceConnection $resourceConnection,
        CustomerRepositoryInterface $customerRepositoryInterface,
        CustomerFactory $customerFactory,
        CustomerHelper $customerHelper,
        AbstractResource $resource = null,
        AbstractDb $resourceCollection = null,
        array $data = []
    ) {
        parent::__construct(
            $scopeConfig,
            $context,
            $registry,
            $helperFlow,
            $objectManager,
            $productMetadata,
            $resourceConnection,
            $resource,
            $resourceCollection,
            $data
        );
        $this->customerRepositoryInterface = $customerRepositoryInterface;
        $this->customerFactory = $customerFactory;
        $this->customerHelper = $customerHelper;
        $this->helperFlow = $helperFlow;
        $this->connection = $this->resourceConnection->getConnection();
    }


    /**
     * @return void
     * @noinspection MagicMethodsValidityInspection
     */
    public function _construct()
    {
        $this->_init(\FiloBlu\Flow\Model\ResourceModel\From\Customer::class);
    }

    /**
     * @param $file
     * @return \FiloBlu\Flow\Model\Inboundflow
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function processFileRows($file)
    {
        $mainTable = $this->connection->getTableName('flow_from_customer');
        $selectCustomer = $this->connection->select()->from($mainTable, ['e'])->where('meta_file = :meta_file')->where(
            'meta_processed = 0'
        )->distinct();
        $customerEmails = $this->connection->fetchAll(
            $selectCustomer,
            ['meta_file' => $file->getId()],
            Zend_Db::FETCH_ASSOC
        );

        $errorCount = 0;
        $lines = 0;
        $rowsData = [];
        foreach ($customerEmails as $customer) {
            $selectCustomerRows = $this->connection->select()
                ->from($mainTable)
                ->where('e = :customer_email')
                ->where("a not like '\_%' ")
                ->where('meta_file = :meta_file')
                ->where('meta_processed = 0');

            $customerRows = $this->connection->fetchAll(
                $selectCustomerRows,
                ['meta_file' => $file->getId(), 'customer_email' => $customer['e']],
                Zend_Db::FETCH_ASSOC
            );

            $rowsData['customer'] = $customerRows;

            $selectActionRows = $this->connection
                ->select()
                ->from($mainTable)
                ->where('e = :customer_email')
                ->where("a like '\_%' ")
                ->where('meta_file = :meta_file')
                ->where('meta_processed = 0');

            $actionRows = $this->connection
                ->fetchAll(
                    $selectActionRows,
                    [
                    'meta_file'      => $file->getId(),
                    'customer_email' => $customer['e']
                    ],
                    Zend_Db::FETCH_ASSOC
                );
            $rowsData['action'] = $actionRows;
            $customerLines = count($customerRows) + count($actionRows);


            if ($customerLines > 0) {
                $lines += $customerLines;

                try {
                    $status = $this->tryExecute($rowsData, 3);
                } catch (BlockingException $blockingException) {
                    $status = Processor::META_PROCESSED_ERROR;
                    throw new RuntimeException(
                        "processing item {$customer['e']} : {$blockingException->getMessage()}"
                    );
                } catch (NonBlockingException $nonBlockingException) {
                    $status = Processor::META_PROCESSED_ERROR;
                    $this->_logger->error(
                        "FLOW: processing item {$customer['e']} : {$nonBlockingException->getMessage()}"
                    );
                    $this->appendErrorMessage(
                        "processing item {$customer['e']} : {$nonBlockingException->getMessage()}"
                    );
                    $errorCount++;
                } catch (Throwable $e) {
                    $this->_logger->error("FLOW: processing item {$customer['e']} :{$e->getMessage()}");
                    $this->appendErrorMessage("processing item {$customer['e']} : {$e->getMessage()}");
                    $status = Processor::META_PROCESSED_ERROR;
                } finally {
                    $now = date('Y-m-d H:i:s');
                    $this->connection->query(
                        "UPDATE $mainTable SET meta_processed = ?, meta_process_time = ? WHERE meta_file = ? AND e = ?",
                        [$status, $now, $file->getId(), $customer['e']]
                    );
                }
            }
        }

        $now = date('Y-m-d H:i:s');
        $table = $file->getResource()->getMainTable();
        $this->connection->update($table, [
            'last_activity' => $now,
            'errors'        => $errorCount,
            'lines'         => $lines
        ], [
            'id = ?' => $file->getId()
        ]);

        $file->setErrorCount($errorCount);
        $this->postProcess($file);
        $this->sendErrorNotifications($file);

        if ($errorCount > 0) {
            throw new RuntimeException("Encountered $errorCount errors while processing flow");
        }

        return $file;
    }

    /**
     * @return $this|bool|Customer
     * @throws Exception
     */
    public function process()
    {
        $this->channelConfig = $this->getChannel()->getChannelConfig()->_mappedData['config'];
        $this->entityManagerClassName = $this->getEntityManagerClassName('customer');
        $entityIdentifier = null;
        $this->websiteId = null;
        $this->storeId = null;
        $customerData = [];
        foreach ($this->getData('customer') as $index => $data) {
            $entityIdentifier = $data['e'];
            $customerData['email'] = $data['e'];
            $customerData[$data['a']] = stripcslashes($data['v']);
        }

        if (count($customerData) > 0) {
            try {
                $customer = $this->processCustomer($customerData);
            } catch (Exception $e) {
                $this->_logger->info($e->getMessage());
                return $this->error(false, $e->getMessage());
            }
        }

        $actionData = [];
        foreach ($this->getData('action') as $index => $data) {
            $entityIdentifier = $data['e'];
            $actionData[$data['a']][] = [
                'value'  => stripcslashes($data['v']),
                'params' => stripcslashes($data['params'])
            ];
        }

        if (count($actionData) > 0) {
            try {
                $this->processActions($actionData, $entityIdentifier);
            } catch (Exception $e) {
                $this->_logger->info($e->getMessage());
                return $this->error(false, $e->getMessage());
            }
            return $this;
        }

        if ($this->channelConfig['create_company_with_customer']
            && $customer->getId()
            && !$this->customerHelper->companyExist($customer->getId())
        ) {
            $customerLoaded = $this->customerFactory->create()->load($customer->getId());
            try {
                $this->createCompany($customerLoaded);
            } catch (Exception $e) {
                $this->_logger->info($e->getMessage());
                return $this->error(false, $e->getMessage());
            }
        }

        return $this;
    }

    /**
     * @param $customerData
     * @return bool|void
     * @throws Exception
     */
    public function processCustomer($customerData)
    {
        if (!isset($customerData['website'])) {
            $customerData['website'] = null;
        }
        $this->websiteId = $this->getCustomerWebsiteId($customerData['website']);
        unset($customerData['website']);

        if (!isset($customerData['store'])) {
            $customerData['store'] = null;
        }
        $this->storeId = $this->getCustomerStoreId($customerData['store']);
        unset($customerData['store']);

        if (isset($customerData['firstname'])) {
            if (!isset($customerData['lastname'])) {
                $customerData['lastname'] = $customerData['firstname'];
            }
            try {
                $customer = $this->objectManager->create(
                    $this->entityManagerClassName,
                    [
                        'data' => [
                            'entityIdentifier' => $customerData['email'],
                            'websiteId'        => $this->websiteId,
                            'storeId'          => $this->storeId,
                            'attributeCode'    => '',
                            'attributeValue'   => '',
                            'entityTypeCode'   => '',
                            'channelConfig'    => $this->channelConfig
                        ]
                    ]
                )->process();
            } catch (Exception $e) {
                $this->_logger->info($e->getMessage());
                return $this->error(false, $e->getMessage());
            }

            $customer->setWebsiteId($this->websiteId);
            $customer->setStoreId($this->storeId);
            foreach ($customerData as $customerAttribute => $value) {
                $customer->setData($customerAttribute, $value);
            }

            try {
                $savedCustomer = $customer->save();
            } catch (Exception $e) {
                $this->_logger->info($e->getMessage());
                return $this->error(false, $e->getMessage());
            }
            return $savedCustomer;
        }
    }

    /**
     * @param $website
     * @return float|int|mixed|string|null
     */
    public function getCustomerWebsiteId($website)
    {
        if ($website === null) {
            return $this->helperFlow->getDefaultWebsiteId();
        }

        if (is_numeric($website)) {
            return $website;
        }

        $websiteIds = $this->helperFlow->getStoreInWebsiteId();
        if (isset($websiteIds[$website])) {
            return $websiteIds[$website]['website_id'];
        }

        return null;
    }

    /**
     * @param $store
     * @return float|int|mixed|string|null
     */
    public function getCustomerStoreId($store)
    {
        if ($store === null) {
            $defaultWebsiteId = $this->helperFlow->getDefaultWebsiteId();
            $allStoresWebsite = $this->helperFlow->getStoreInWebsiteId();
            foreach ($allStoresWebsite as $stores) {
                if ($stores['website_id'] == $defaultWebsiteId) {
                    return $stores['store_id'];
                }
            }

            return null;
        }

        if (is_numeric($store)) {
            return $store;
        }

        $allStores = $this->helperFlow->getAllStoreIds();
        return $allStores[$store] ?? null;
    }

    /**
     * @param $actionData
     * @param $entityIdentifier
     * @return bool|void
     * @throws Exception
     */
    public function processActions($actionData, $entityIdentifier)
    {
        if (!$this->websiteId) {
            $this->websiteId = $this->getCustomerWebsiteId(null);
        }
        if (!$this->storeId) {
            $this->storeId = $this->getCustomerStoreId(null);
        }

        foreach ($actionData as $actionClass => $actionValues) {
            try {
                $this->objectManager->create(
                    $this->entityManagerClassName,
                    [
                        'data' => [
                            'entityIdentifier' => $entityIdentifier,
                            'websiteId'        => $this->websiteId,
                            'attributeCode'    => $actionClass,
                            'attributeValue'   => $actionValues,
                            'entityTypeCode'   => '',
                            'storeId'          => $this->storeId,
                            'channelConfig'    => $this->channelConfig
                        ]
                    ]
                )->process();
            } catch (Exception $e) {
                $this->_logger->info($e->getMessage());
                return $this->error(false, $e->getMessage());
            }
        }
    }

    /**
     * @param $customer
     * @return bool|void
     * @throws Exception
     */
    public function createCompany($customer)
    {
        $customerAddresses = $customer->getAddresses();
        if (!$customerAddresses) {
            return;
        }
        foreach ($customerAddresses as $customerAddress) {
            $address = $customerAddress->getData();
            $companyData = [
                'company_name'      => $customer->getData('firstname'),
                'company_email'     => $customer->getData('email'),
                'street'            => $address['street'],
                'city'              => $address['city'],
                'country_id'        => $address['country_id'],
                'region'            => $address['region'],
                'postcode'          => $address['postcode'],
                'telephone'         => $address['telephone'],
                'super_user_id'     => $customer->getId(),
                'customer_group_id' => $customer->getData('group_id')
            ];

            if (isset($address['region_id']) && (int)$address['region_id'] > 0) {
                $companyData['region_id'] = $address['region_id'];
            }

            try {
                $this->customerHelper->addCompanyToCustomer($companyData);
            } catch (Exception $e) {
                $this->_logger->info($e->getMessage());
                return $this->error(false, $e->getMessage());
            }
            break;
        }
    }
}
