<?php

namespace FiloBlu\ActiveCampaign\Model\Entity\Export;

use Exception;
use FiloBlu\ActiveCampaign\Api\Data\JobsInterface;
use FiloBlu\ActiveCampaign\Api\Data\JobsInterfaceFactory;
use FiloBlu\ActiveCampaign\Api\JobsRepositoryInterface;
use FiloBlu\ActiveCampaign\Helper\ConsentsProfilingHelper;
use FiloBlu\ActiveCampaign\Helper\Data;
use FiloBlu\ActiveCampaign\Model\Config\Source\DirectionType;
use FiloBlu\ActiveCampaign\Model\Config\Source\EntityType;
use FiloBlu\ActiveCampaign\Model\Resource\ActiveCampaign;
use Magento\Customer\Api\AddressRepositoryInterface;
use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Newsletter\Model\SubscriberFactory as MagentoSubscriber;
use Magento\Store\Model\Service\StoreConfigManager;
use Magento\Store\Model\StoreManagerInterface;
use Zend_Db;

/**
 * Class Subscriber
 * @package FiloBlu\ActiveCampaign\Model\Entity\Export
 */
class Subscriber extends AbstractEntityExport
{
    /**
     * @var ResourceConnection
     */
    protected $resourceConnection;
    /**
     * @var Data
     */
    protected $helperData;
    /**
     * @var JobsRepositoryInterface
     */
    protected $jobsRepository;
    /**
     * @var string
     */
    protected $entity = EntityType::ENTITY_SUBSCRIBER;
    /**
     * @var string
     */
    protected $direction = DirectionType::DIRECTION_EXPORT;
    /**
     * @var CustomerRepositoryInterface
     */
    protected $customerRepository;
    /**
     * @var StoreManagerInterface
     */
    protected $storeManager;
    /**
     * @var MagentoSubscriber
     */
    protected $subscriber;
    /**
     * @var ActiveCampaign
     */
    protected $activeCampaign;
    /**
     * @var AddressRepositoryInterface
     */
    protected $addressRepository;
    /**
     * @var StoreConfigManager
     */
    protected $storeConfigManager;
    /**
     * @var array
     */
    protected $storeLocales = [];
    /**
     * @var array|mixed
     */
    private $genderMapping;
    /**
     * @var ConsentsProfilingHelper
     */
    private $consentsProfilingHelper;

    /**
     * Subscriber constructor.
     * @param ResourceConnection $resourceConnection
     * @param Data $helperData
     * @param JobsRepositoryInterface $jobsRepository
     * @param JobsInterfaceFactory $jobsFactory
     * @param ActiveCampaign $activeCampaign
     * @param MagentoSubscriber $subscriber
     * @param CustomerRepositoryInterface $customerRepository
     * @param StoreManagerInterface $storeManager
     * @param AddressRepositoryInterface $addressRepository
     * @param StoreConfigManager $storeConfigManager
     * @param ConsentsProfilingHelper $consentsProfilingHelper
     */
    public function __construct(
        ResourceConnection          $resourceConnection,
        Data                        $helperData,
        JobsRepositoryInterface     $jobsRepository,
        JobsInterfaceFactory        $jobsFactory,
        ActiveCampaign              $activeCampaign,
        MagentoSubscriber           $subscriber,
        CustomerRepositoryInterface $customerRepository,
        StoreManagerInterface       $storeManager,
        AddressRepositoryInterface  $addressRepository,
        StoreConfigManager          $storeConfigManager,
        ConsentsProfilingHelper     $consentsProfilingHelper
    )
    {
        parent::__construct($resourceConnection, $helperData, $jobsRepository, $jobsFactory, $activeCampaign);
        $this->subscriber = $subscriber;
        $this->customerRepository = $customerRepository;
        $this->storeManager = $storeManager;
        $this->activeCampaign = $activeCampaign;
        $this->addressRepository = $addressRepository;
        $this->storeConfigManager = $storeConfigManager;
        $this->consentsProfilingHelper = $consentsProfilingHelper;
    }


    /**
     * @return array|mixed
     */
    public function getAllIncrementalIds()
    {
        $connection = $this->resourceConnection->getConnection();
        $tableName = $this->resourceConnection->getTableName('newsletter_subscriber');
        $tableNameJobs = $this->resourceConnection->getTableName(JobsInterface::DB_TABLE);

        if (empty($sw = $this->helperData->getEnabledStoreViews())) {
            return [];
        }

        $sql = "SELECT
                  ce.subscriber_id as subscriber_id
                FROM
                  {$tableName} AS ce
                LEFT JOIN
                  {$tableNameJobs} AS j
                ON
                  j.entity_id = ce.subscriber_id
                AND
                  j.entity = ?
                AND
                  j.direction = ?
                WHERE
                  j.status IS NULL
                AND
                  FIND_IN_SET(ce.store_id, ?) > 0";

        return $connection->fetchCol($sql, [$this->entity, $this->direction, $sw]);

    }

    /**
     * @param array $job
     * @return bool
     * @throws LocalizedException
     * @throws NoSuchEntityException
     */
    public function executeProcess(array $job): bool
    {
        $this->init();
        $subscriberData = $this->getSubscriberData($job);

        if (!array_key_exists('create_update_contact', $subscriberData)) {
            foreach ($subscriberData as $request) {
                $this->executeContactRestProcess($request, $job);
            }
            return true;
        }

        $this->executeContactRestProcess($subscriberData, $job);

        return true;
    }

    /**
     * Init
     */
    public function init()
    {
        if (count($this->storeLocales) == 0) {
            foreach ($this->storeConfigManager->getStoreConfigs() as $storeConfig) {
                $this->storeLocales[$storeConfig->getId()] = $storeConfig->getLocale();
            }
        }
    }

    /**
     * @param array $job
     * @return array
     * @throws LocalizedException
     * @throws NoSuchEntityException
     */
    public function getSubscriberData(array $job)
    {

        $this->init();

        $data = [];
        $subscriberMapping = $this->helperData->getSubscriberMapping();
        $customerMapping = $this->helperData->getCustomerMapping();
        $customerBillingMapping = $this->helperData->getCustomerBillingAddressMapping();
        $customerShippingMapping = $this->helperData->getCustomerShippingAddressMapping();

        $subscriber = null;
        $customer = null;
        $storeId = null;
        $customerId = $job['customer_id'] ?? null;
        $isSubscribed = '0';

        if (!empty($job['customer_id'])) {
            $customer = $this->customerRepository->getById($job['customer_id']);
        }

        if (!empty($job['store_id'])) {
            $storeId = $job['store_id'];
        }

        if (!empty($job['entity_id'])) {
            $subscriber = $this->subscriber->create()->load($job['entity_id']);
            $customerId = $subscriber->getCustomerId();
            $storeId = $subscriber->getStoreId();
            $isSubscribed = ($subscriber->isSubscribed() == 1) ? '1' : '0';

            if (!empty($subscriber->getCustomerId())) {
                $customer = $this->customerRepository->getById($subscriber->getCustomerId());
            }

        }

        if (empty($job['language'])) {
            $job['language'] = $this->storeLocales[$storeId];
        }

        $additionalNewsletterData = [];
        if ($this->helperData->isNewsletterExtensionEnabled() && !empty($job['entity_id'])) {
            $additionalNewsletterData = $this->normalizeFields($this->getNewsletterAdditionalInfo($job['entity_id']));
        } else {
            $additionalNewsletterData = $job;
        }


        $additionalData = [
            '_customer_id' => $customerId,
            '_store_code' => $this->storeManager->getStore($storeId)->getCode(),
            '_store_name' => $this->storeManager->getStore($storeId)->getName(),
            '_website_name' => $this->storeManager->getStore($storeId)->getWebsite()->getName(),
            '_website_code' => $this->storeManager->getStore($storeId)->getWebsite()->getCode(),
            '_type' => 'SUBSCRIBER',
            '_subscriber_status_normalized' => $isSubscribed,
            '_created_at_normalized' => '',
            '_updated_at_normalized' => '',
            '_language' => $this->storeLocales[$storeId],
            '_language_iso2' => strtolower(substr($this->storeLocales[$storeId], 0, 2)),
            '_source' => ''
        ];

        $result = [
            'email' => $subscriber ? $subscriber->getEmail() : $job['email'],
        ];

        if ($additionalNewsletterData !== false) {
            $additionalData = array_merge($additionalData, $additionalNewsletterData);
            if (isset($additionalNewsletterData['firstname']) && !empty($additionalNewsletterData['firstname'])) {
                $result['firstName'] = $additionalNewsletterData['firstname'];
            }
            if (isset($additionalNewsletterData['lastname']) && !empty($additionalNewsletterData['lastname'])) {
                $result['lastName'] = $additionalNewsletterData['lastname'];
            }
            if (isset($additionalNewsletterData['phone']) && !empty($additionalNewsletterData['phone'])) {
                $result['phone'] = $additionalNewsletterData['phone'];
            }
            if (isset($additionalNewsletterData['created_at']) && !empty($additionalNewsletterData['created_at'])) {
                $additionalData['_created_at_normalized'] = $this->helperData->getACDatetimeFormat($additionalNewsletterData['created_at']);
            }
            if (isset($additionalNewsletterData['updated_at']) && !empty($additionalNewsletterData['updated_at'])) {
                $additionalData['_updated_at_normalized'] = $this->helperData->getACDatetimeFormat($additionalNewsletterData['updated_at']);
            }
            if (isset($additionalNewsletterData['language']) && !empty($additionalNewsletterData['language'])) {
                $additionalData['_language'] = $additionalNewsletterData['language'];
            }
            if (isset($additionalNewsletterData['source']) && !empty($additionalNewsletterData['source'])) {
                $additionalData['_source'] = $additionalNewsletterData['source'];
            }
        }

        foreach ($subscriberMapping as $key => $val) {
            if ($this->helperData->isAdditional($key) || isset($additionalData[$key])) {
                if (!empty($additionalData[$key])) {
                    $data[$val] = [
                        'field' => $val,
                        'value' => $additionalData[$key]
                    ];
                }
                continue;
            }
            if ($subscriber) {
                if (!empty($subscriber->{'get' . $this->helperData->snakeToCamelCase($key)}())) {
                    $data[$val] = [
                        'field' => $val,
                        'value' => $subscriber->{'get' . $this->helperData->snakeToCamelCase($key)}()
                    ];
                }
            }
        }

        if ($subscriber) {
            return $this->generateSubscriberData($subscriber, $additionalData, $customer, $result, $data);
        }

        return $this->generateSubscriberWithProfilingOnly($job, $result, $additionalData);
    }

    /**
     * @param $fields
     * @return mixed
     */
    public function normalizeFields($fields)
    {
        if ($fields == false) {
            return false;
        }

        $fields['_dob_normalized'] = '';
        if (isset($fields['dob']) && !empty($fields['dob'])) {
            $date = explode('/', $fields['dob']);
            if (count($date) > 1) {
                $fields['_dob_normalized'] = $date[2] . '-' . $date[1] . '-' . $date[0];
            } else {
                $fields['_dob_normalized'] = $fields['dob'];
            }
        }
        $fields['_gender_normalized'] = '';
        if (isset($fields['gender']) && !empty($fields['gender'])) {
            $gender = $this->getGenderMapping();
            if (count($gender) > 0 && isset($gender[$fields['gender']])) {
                $fields['_gender_normalized'] = $gender[$fields['gender']];
            }
        }
        return $fields;
    }

    /**
     * @return array|mixed
     */
    public function getGenderMapping()
    {
        if ($this->genderMapping && count($this->genderMapping)) {
            return $this->genderMapping;
        }
        $this->genderMapping = $this->helperData->getGenderMapping();
        return $this->genderMapping;
    }

    /**
     * @param $subscriberId
     * @return array
     */
    public function getNewsletterAdditionalInfo($subscriberId)
    {
        $connection = $this->resourceConnection->getConnection();
        $tableName = $this->resourceConnection->getTableName('filoblu_newsletter_subscriber_info');
        $sql = "SELECT * FROM {$tableName} WHERE subscriber_id = ?";
        return $connection->fetchRow($sql, [$subscriberId], Zend_Db::FETCH_ASSOC);
    }

    /**
     * @param $additionalData
     * @param $customer
     * @return mixed
     */
    function completeAdditionalData($additionalData, $customerData)
    {
        $additionalData['_created_at_normalized'] = $this->helperData->getACDatetimeFormat($customerData['created_at']);
        $additionalData['_updated_at_normalized'] = $this->helperData->getACDatetimeFormat($customerData['updated_at']);
        $additionalData['_type'] = 'CUSTOMER';

        if (empty($additionalData['_store_code'])) {
            $additionalData['_store_code'] = $this->storeManager->getStore($customerData['store_id'])->getCode();
        }
        if (empty($additionalData['_store_name'])) {
            $additionalData['_store_name'] = $this->storeManager->getStore($customerData['store_id'])->getName();
        }
        if (empty($additionalData['_website_code'])) {
            $additionalData['_website_code'] = $this->storeManager->getStore($customerData['store_id'])->getWebsite()->getCode();
        }
        if (empty($additionalData['_website_name'])) {
            $additionalData['_website_name'] = $this->storeManager->getStore($customerData['store_id'])->getWebsite()->getName();
        }
        if (empty($additionalData['_language'])) {
            $additionalData['_language'] = $this->storeLocales[$customerData['store_id']];
        }
        if (empty($additionalData['_source'])) {
            $additionalData['_source'] = 'ACCOUNT';
        }
        if (empty($additionalData['_dob_normalized'])) {
            $additionalData['_dob_normalized'] = $customerData['dob'] ?? '';
        }

        return $additionalData;
    }

    public function generateSubscriberData($subscriber, $additionalData, $customer, $result, $data)
    {


        if (!empty($subscriber->getCustomerId())) {

            $result['firstName'] = $customer->getFirstname();
            $result['lastName'] = $customer->getLastname();

            $customerData = [
                'dob' => $customer->getDob(),
                'created_at' => $customer->getCreatedAt(),
                'updated_at' => $customer->getUpdatedAt(),
                'store_id' => $customer->getStoreId(),
            ];

            $additionalData = $this->completeAdditionalData($additionalData, $customerData);

            if (!isset($result['phone']) || empty($result['phone'])) {
                //try to get phone
            }

            $this->mapCustomerData( $additionalData, $result, $data, $customer);

            $this->mapCustomerShippingData($customer, $additionalData, $result, $data);

            $this->mapCustomerBillingData($customer, $additionalData, $result, $data);
        }

        if (count($data)) {
            $d = [];
            foreach ($data as $dat) {
                $d[] = $dat;
            }
            $result['fieldValues'] = $d;
        }

        $finalRequest['contact'] = $result;

        $request = [
            'create_update_contact' => [
                'request' => $finalRequest
            ]
        ];

        if (in_array($subscriber->getStatus(), [1, 3])) {
            $request['list_subscription'] = [
                'request' => [
                    'contactList' => [
                        'list' => $this->helperData->getSubscriberList(),
                        'status' => ($subscriber->isSubscribed() == 1) ? '1' : '2'
                    ]
                ]
            ];

        }

        $customerSubscription = $this->consentsProfilingHelper->getConsentsSubscriptionData($result['email']);

        if ($customerSubscription) {

            foreach ($this->helperData->getProfileList() as $item) {

                try {

                    $subscriberConsentsList = $this->helperData->getSubscriberConsentsList($result['email'],  $subscriber->getStoreId(), $item['consent']);
                } catch (\Exception $e) {
                    continue;
                }

                if (!$subscriberConsentsList->getId()) {
                    continue;
                }

                $newRequest = $request;

                $newRequest['list_subscription']['request'] = [
                    'contactList' => [
                        'list' => $item['campaign'],
                        'status' => '1'
                    ]
                ];

                $newRequest = $this->consentsProfilingHelper->addConsentDataToRequest($newRequest, $result['email']);

                $requests[] = $newRequest;

            }
        }

        $request = $this->consentsProfilingHelper->addConsentDataToRequest($request, $result['email']);

        $requests[] = $request;

        return $requests;
    }

    public function generateSubscriberWithProfilingOnly($job, $result, $additionalData)
    {
        if (!empty($job['profile_type_id'])) {

            $data = [];

            $customerSubscription = $this->consentsProfilingHelper->getConsentsSubscriptionData($job['email']);

            $additionalData = $this->completeAdditionalData($additionalData, $job);

            $data = $this->mapCustomerData( $additionalData, $result, $data);

            $data = $this->mapCustomerShippingData(null, $additionalData, $result, $data);

            $data = $this->mapCustomerBillingData(null, $additionalData, $result, $data);

            if (count($data)) {
                $d = [];
                foreach ($data as $dat) {
                    $d[] = $dat;
                }
                $result['fieldValues'] = $d;
            }

            $finalRequest['contact'] = $result;

            $request = [
                'create_update_contact' => [
                    'request' => $finalRequest
                ]
            ];

            foreach ($this->helperData->getSubscriberMapping() as $key => $mapping) {

                if ($key == 'country') {
                    $request['create_update_contact']['request']['contact']['fieldValues'][] = [
                        "field" => $mapping,
                        "value" => $this->helperData->getDefaultCountryByWebsite($additionalData['website_id'])
                    ];

                    continue;
                }

                if ($key == '_source') {
                    $request['create_update_contact']['request']['contact']['fieldValues'][] = [
                        "field" => $mapping,
                        "value" => $additionalData['source']
                    ];

                    continue;
                }

                if (!empty($additionalData[$key])) {
                    $request['create_update_contact']['request']['contact']['fieldValues'][] = [
                        "field" => $mapping,
                        "value" => $additionalData[$key]
                    ];
                }
            }

            $requests = [];

            foreach ($this->helperData->getProfileList() as $item) {

                try {

                    $subscriberConsentsList = $this->helperData->getSubscriberConsentsList($job['email'],  $job['store_id'], $item['consent']);
                } catch (\Exception $e) {
                    continue;
                }

                if (!$subscriberConsentsList->getId()) {
                    continue;
                }

                $newRequest = $request;

                $newRequest['list_subscription']['request'] = [
                    'contactList' => [
                        'list' => $item['campaign'],
                        'status' => '1'
                    ]
                ];

                $requests[] = $newRequest;
            }
        }

        $this->consentsProfilingHelper->addConsentDataToRequest($request, $result['email']);

        return $requests;
    }

    public function mapData($mapping, $additionalData, &$data, $customer = null)
    {
        try {
            foreach ($mapping as $key => $val) {
                if ($this->helperData->isAdditional($key)) {
                    if (!empty($additionalData[$key])) {
                        $data[$val] = [
                            'field' => $val,
                            'value' => $additionalData[$key]
                        ];
                    }
                    continue;
                }
                if ($customer && !empty($customer->{'get' . $this->helperData->snakeToCamelCase($key)}())) {
                    $data[$val] = [
                        'field' => $val,
                        'value' => $customer->{'get' . $this->helperData->snakeToCamelCase($key)}()
                    ];
                }
            }
        } catch (Exception $exception) {
            // No address
            $x = null;
        }
    }

    public function mapCustomerData($additionalData, $result, &$data, $customer = null)
    {
        $customerMapping = $this->helperData->getCustomerMapping();

        if (!count($customerMapping)) {
            return $data;
        }

        $this->mapData($customerMapping, $additionalData, $data, $customer);

        return $data;
    }

    public function mapCustomerShippingData($customer, $additionalData, $result, &$data)
    {
        $customerShippingMapping = $this->helperData->getCustomerShippingAddressMapping();

        if (!count($customerShippingMapping)) {
            return $data;
        }

        try {
            //If there is no address
            if (!empty($customer)) {
                $shippingAddress = $this->addressRepository->getById($customer->getDefaultShipping());
                if (!empty($shippingAddress->getTelephone())) {
                    $result['phone'] = $shippingAddress->getTelephone();
                }
            }

            $this->mapData($customerShippingMapping, $additionalData, $data);

        } catch (Exception $exception) {
            // No address
        }

        return $data;
    }

    public function mapCustomerBillingData($customer, $additionalData, $result, &$data)
    {
        $customerBillingMapping = $this->helperData->getCustomerBillingAddressMapping();

        if (!count($customerBillingMapping)) {
            return $data;
        }

        try {
            //If there is no address
            if (!empty($customer)) {
                $billingAddress = $this->addressRepository->getById($customer->getDefaultBilling());
                if (!empty($billingAddress->getTelephone())) {
                    $result['phone'] = $billingAddress->getTelephone();
                }
            }

            $this->mapData($customerBillingMapping, $additionalData, $data);

        } catch (Exception $exception) {
            // No Address
        }

        return $data;

    }

    /**
     * @param $entityData
     * @param $job
     * @throws Exception
     */
    public function executeContactRestProcess($entityData, $job)
    {
        //convert in json
        $jsonData = $this->helperData->serializer->serialize($entityData);

        if (!empty($job['id'])) {
            $this->jobsRepository->updateJobs([JobsInterface::REQUEST => $jsonData], 'id = "' . $job['id'] . '"');
        }

        $job[JobsInterface::REQUEST] = $jsonData;
        $contactResponse = $this->activeCampaign->createUpdateContact($job);
        if (!in_array($contactResponse->getStatusCode(), [200, 201])) {
            throw new Exception($contactResponse->getReasonPhrase());
        }

        $contactResponseUnserialized = $this->helperData->serializer->unserialize($contactResponse->getBody());
        $contactId = $contactResponseUnserialized['contact']['id'];
        $entityData['list_subscription']['request']['contactList']['contact'] = $contactId;
        $jsonData = $this->helperData->serializer->serialize($entityData);

        if (!empty($job['id'])) {
            $this->jobsRepository->updateJobs(
                [
                    JobsInterface::REQUEST => $jsonData,
                    JobsInterface::RESPONSE => $contactResponse->getBody(),
                    JobsInterface::LOG => ''
                ],
                'id = "' . $job['id'] . '"'
            );
        }
        $job[JobsInterface::REQUEST] = $jsonData;
        $job[JobsInterface::RESPONSE] = $contactResponse->getBody();
        $listResponse = $this->activeCampaign->subscribeToList($job);

        if (!in_array($listResponse->getStatusCode(), [200, 201])) {
            throw new Exception($listResponse->getReasonPhrase());
        }

        if (!empty($job['id'])) {
            $this->jobsRepository->updateJobs(
                [JobsInterface::RESPONSE => $listResponse->getBody(), JobsInterface::LOG => ''],
                'id = "' . $job['id'] . '"'
            );
        }
    }
}
