<?php

namespace FiloBlu\Refilo\Model;

use DateTime;
use Exception;
use FiloBlu\Core\Api\StoreCalculatorResolverInterface;
use FiloBlu\Newsletter\Model\SubscriberInfo;
use FiloBlu\Refilo\Api\NewsletterManagementInterface;
use Magento\Customer\Api\AccountManagementInterface as CustomerAccountManagement;
use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\DataObject;
use Magento\Framework\Event\ManagerInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Module\Manager;
use Magento\Framework\Phrase;
use Magento\Framework\Validator\EmailAddress;
use Magento\Newsletter\Model\Subscriber;
use Magento\Newsletter\Model\SubscriberFactory;
use Magento\Store\Api\StoreRepositoryInterface;
use Magento\Store\Model\ScopeInterface;
use Magento\Store\Model\StoreManagerInterface;
use Throwable;

/**
 *
 */
class NewsletterManagement implements NewsletterManagementInterface
{
    /**
     * @var string
     */
    const MAILCHIMP_DOB_FORMAT = 'd/m/Y';

    /**
     * @var string
     */
    const MAILCHIMP_BIRTHDAY_FORMAT = 'm/d';

    /**
     * @var StoreManagerInterface
     */
    protected $storeManager;

    /**
     * @var CustomerAccountManagement
     */
    protected $customerAccountManagement;

    /**
     * @var SubscriberFactory
     */
    protected $subscriberFactory;

    /**
     * @var Manager
     */
    protected $moduleManager;
    /**
     * @var StoreRepositoryInterface
     */
    protected $storeRepository;
    /**
     * @var CustomerRepositoryInterface
     */
    protected $customerRepositoryInterface;
    /**
     * @var StoreCalculatorResolverInterface
     */
    protected $storeCalculatorResolverInterface;
    /**
     * @var \Magento\Framework\App\Config\ScopeConfigInterface
     */
    protected $scopeConfig;
    /**
     * @var EmailAddress
     */
    private $emailValidator;

    /**
     * @var ManagerInterface
     */
    private $eventManager;

    /**
     * NewsletterManagement constructor.
     * @param StoreManagerInterface $storeManager
     * @param CustomerAccountManagement $customerAccountManagement
     * @param SubscriberFactory $subscriberFactory
     * @param Manager $moduleManager
     * @param StoreRepositoryInterface $storeRepository
     * @param CustomerRepositoryInterface $customerRepositoryInterface
     * @param StoreCalculatorResolverInterface $storeCalculatorResolverInterface
     * @param ScopeConfigInterface $scopeConfig
     * @param EmailAddress $emailValidator
     * @param ManagerInterface $eventManager
     */
    public function __construct(
        StoreManagerInterface $storeManager,
        CustomerAccountManagement $customerAccountManagement,
        SubscriberFactory $subscriberFactory,
        Manager $moduleManager,
        StoreRepositoryInterface $storeRepository,
        CustomerRepositoryInterface $customerRepositoryInterface,
        StoreCalculatorResolverInterface $storeCalculatorResolverInterface,
        ScopeConfigInterface $scopeConfig,
        EmailAddress $emailValidator,
        ManagerInterface $eventManager
    ) {
        $this->storeManager = $storeManager;
        $this->customerAccountManagement = $customerAccountManagement;
        $this->subscriberFactory = $subscriberFactory;
        $this->moduleManager = $moduleManager;
        $this->storeRepository = $storeRepository;
        $this->customerRepositoryInterface = $customerRepositoryInterface;
        $this->storeCalculatorResolverInterface = $storeCalculatorResolverInterface;
        $this->scopeConfig = $scopeConfig;
        $this->emailValidator = $emailValidator;
        $this->eventManager = $eventManager;
    }

    /**
     * @param $payload \Magento\Framework\DataObject
     * @return \Magento\Framework\DataObject
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     * @throws \Magento\Store\Model\StoreIsInactiveException
     */
    public function subscribeFromPayload($payload){
        return $this->executeSubscribe($payload);
    }

    /**
     * @param $email
     * @param $source
     * @param $firstname
     * @param $lastname
     * @param $dob
     * @param $gender
     * @param $country
     * @param $collections
     * @param $phone
     * @param $language
     * @param $website_name
     * @param $store
     * @param $birthday
     * @param $type
     * @return \Magento\Framework\DataObject
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     * @throws \Magento\Store\Model\StoreIsInactiveException
     * @description Edited this function to keep it as legacy for backward compatibility
     */
    public function subscribe(
        $email,
        $source,
        $firstname = null,
        $lastname = null,
        $dob = null,
        $gender = null,
        $country = null,
        $collections = null,
        $phone = null,
        $language = null,
        $website_name = null,
        $store = null,
        $birthday = null,
        $type = null
    ) {
        $rebuildData = [
            "email" => $email,
            "source" => $source,
            "firstname" => $firstname,
            "lastname" => $lastname,
            "dob" => $dob,
            "gender" => $gender,
            "country" => $country,
            "collections" => $collections,
            "phone" => $phone,
            "language" => $language,
            "website_name" => $website_name,
            "store" => $store,
            "birthday" => $birthday,
            "type" => $type
        ];
        // Keep everything compatible with old versions of mod2-refilo
        $reBuildPayload = new DataObject();
        $reBuildPayload->setData('fields', $rebuildData);
        // This is the new function
        return $this->subscribeFromPayload($reBuildPayload);
    }

    /**
     * @param $payload
     * @return DataObject
     * @throws NoSuchEntityException
     * @throws \Magento\Store\Model\StoreIsInactiveException
     */
    public function executeSubscribe($payload){

        // Maintain legacy version variables and logic
        $email = $payload->getDataByPath('fields/email');
        $source = $payload->getDataByPath('fields/source');
        $firstname = $payload->getDataByPath('fields/firstname');
        $lastname = $payload->getDataByPath('fields/lastname');
        $dob = $payload->getDataByPath('fields/dob');
        $gender = $payload->getDataByPath('fields/gender');
        $country = $payload->getDataByPath('fields/country');
        $collections = $payload->getDataByPath('fields/collections');
        $phone = $payload->getDataByPath('fields/phone');
        $language = $payload->getDataByPath('fields/language');
        $website_name = $payload->getDataByPath('fields/website_name');
        $store = $payload->getDataByPath('fields/store');
        $birthday = $payload->getDataByPath('fields/birthday');
        $type = $payload->getDataByPath('fields/type');

        $success = false;
        $subscriber = null;

        // Try to calculate store if missing (but I have the country)
        if (!$store && $country) {
            $storeCalculatorFromCountry = $this->storeCalculatorResolverInterface->get('fromCountry');
            $store = ($storeCalculatorFromCountry->calculate($country))->getCode();
        }

        $storeByCode = $this->storeRepository->getActiveStoreByCode($store);
        $existingCustomer = $this->customerExists($email, $storeByCode->getWebsiteId());

        // Calculate type if missing
        if (!$type) {
            $type = (($existingCustomer !== false) ? 'CUSTOMER' : 'GUEST');
        }

        // Calculate language if missing
        if (!$language) {
            $locale = $this->scopeConfig->getValue(
                'general/locale/code',
                ScopeInterface::SCOPE_STORE,
                $storeByCode->getCode()
            );
            $language = $locale;
        }

        // Calculate website name if missing
        if (!$website_name) {
            $website_name = $storeByCode->getWebsite()->getName();
        }

        // Calculate dob and birthday if missing
        if ($dob) {
            $date = trim(str_replace('00:00:00', '', $dob));
            if ($this->validateDate($date)) {
                $date_obj = DateTime::createFromFormat('Y-m-d', $date);
                $dob = $date_obj->format(self::MAILCHIMP_DOB_FORMAT);
                $birthday = $date_obj->format(self::MAILCHIMP_BIRTHDAY_FORMAT);
            }
        }

        $data = [
            'source'       => $source,
            'firstname'    => $firstname,
            'lastname'     => $lastname,
            'dob'          => $dob,
            'country'      => $country,
            'gender'       => $gender,
            'collections'  => $collections,
            'language'     => $language,
            'website_name' => $website_name,
            'phone'        => $phone,
            'type'         => $type,
            'birthday'     => $birthday
        ];

        // Dynamic attributes here!
        // Add to data all the new attributes found inside the 'fields' path
        $additional_fields = $payload->getDataByPath('fields');
        foreach ($additional_fields as $additional_field_name => $additional_field_value) {
            if (!isset($data[$additional_field_name]) && !is_array($additional_field_value)){
                $data[$additional_field_name] = $additional_field_value;
            }
        }

        try {
            $this->validateEmailFormat($email);
            $this->validateGuestSubscription();

            $subscriber = $this->subscriberFactory->create()->loadByEmail($email);
            if ($subscriber->getId() && $subscriber->getSubscriberStatus() == Subscriber::STATUS_SUBSCRIBED) {
                throw new LocalizedException(
                    __('This email address is already subscribed.')
                );
            }

            foreach ($data as $name => $value) {
                $subscriber->setData("filoblu_{$name}", $value);
            }

            $this->storeManager->setCurrentStore($storeByCode->getId());
//            $subscriber->setStoreId($storeByCode->getId());

            if ($existingCustomer !== false) {
                $resp = $subscriber->subscribeCustomerById($existingCustomer->getId());
                // fallback for \FiloBlu\Newsletter\Model\Plugin\Subscriber::afterSubscribeCustomerById
                if (is_numeric($resp)) {
                    $resp = $this->subscriberFactory->create()->loadByCustomer((int)$resp, $storeByCode->getWebsiteId());
                }
                $status = $resp->getStatus();
            } else {
                $status = $subscriber->subscribe($email);
            }

            if ($status == Subscriber::STATUS_NOT_ACTIVE) {
                $success = true;
                $message = __('The confirmation request has been sent.');
            } else {
                $success = true;
                $message = __('Thank you for your subscription.');
            }
        } catch (LocalizedException $e) {
            $message = __('There was a problem with the subscription: %1', $e->getMessage());
        } catch (Exception $e) {
            $message = __('There was a problem with the subscription: %1', $e->getMessage());
        }

        try {
            if ($email) {
                $data['subscriber_id'] = $subscriber->getId();
                $this->setSubscriberInfo($data);
            }
        } catch (Throwable $e) {
            $message = __('There was a problem with the subscription: %1', $e->getMessage());
        }

        $data = [
            'success' => $success,
            'message' => $message,
        ];

        $result = new DataObject();
        $result->setData($data);

        $this->eventManager->dispatch('filoblu_refilo_subscribe', ['subscriber' => $subscriber, 'result' => $result]);
        return $result;
    }

    /**
     * @param $email
     * @param null $websiteId
     * @return false|\Magento\Customer\Api\Data\CustomerInterface
     */
    public function customerExists($email, $websiteId = null)
    {
        try {
            $customer = $this->customerRepositoryInterface->get($email, $websiteId);
        } catch (Exception $e) {
            return false;
        }
        return $customer;
    }

    /**
     * @param $date
     * @param string $format
     * @return bool
     */
    public function validateDate($date, $format = 'Y-m-d')
    {
        $d = DateTime::createFromFormat($format, $date);
        // The Y ( 4 digits year ) returns TRUE for any integer with any number of digits so changing the comparison from == to === fixes the issue.
        return $d && $d->format($format) === $date;
    }

    /**
     * Validates the format of the email address
     * Reference: vendor/magento/module-newsletter/Controller/Subscriber/NewAction.php
     *
     * @param string $email
     * @return void
     * @throws LocalizedException|\Zend_Validate_Exception
     */
    protected function validateEmailFormat($email)
    {
        if (!$this->emailValidator->isValid($email)) {
            throw new LocalizedException(__('Please enter a valid email address.'));
        }
    }

    /**
     * Validates that if the current user is a guest, that they can subscribe to a newsletter.
     * Reference: vendor/magento/module-newsletter/Controller/Subscriber/NewAction.php
     *
     * @return void
     * @throws LocalizedException
     */
    protected function validateGuestSubscription()
    {
        if ($this->scopeConfig->getValue(
                Subscriber::XML_PATH_ALLOW_GUEST_SUBSCRIBE_FLAG,
                ScopeInterface::SCOPE_STORE
            ) != 1) {
            throw new LocalizedException(
                __(
                    'subscription denied for guests.'
                )
            );
        }
    }

    /**
     * @param $data
     * @return void
     * @throws \Exception
     */
    public function setSubscriberInfo($data)
    {
        if (!$this->moduleManager->isEnabled('FiloBlu_Newsletter')) {
            return;
        }

        $subscribe = ObjectManager::getInstance()->create(SubscriberInfo::class);
        $subscribe->addData($data);
        $subscribe->save();
    }

    /**
     * @param $email
     * @throws LocalizedException
     * @throws NoSuchEntityException
     */
    public function unsubscribe($email)
    {
        $subscriber = $this->subscriberFactory->create()->loadByEmail($email);
        if ($subscriber->getId()) {
            $subscriber->unsubscribe();
            $this->eventManager->dispatch('filoblu_refilo_unsubscribe', ['subscriber' => $subscriber]);
            return;
        }

        throw new NoSuchEntityException(new Phrase('Subscriber not found'));
    }

    /**
     * Validates that the email address isn't being used by a different account.
     * Reference: vendor/magento/module-newsletter/Controller/Subscriber/NewAction.php
     *
     * @param string $email
     * @return void
     * @throws LocalizedException
     */
    protected function validateEmailAvailable($email)
    {
        $websiteId = $this->storeManager->getStore()->getWebsiteId();

        if (!$this->customerAccountManagement->isEmailAvailable($email, $websiteId)) {
            throw new LocalizedException(
                __('This email address is already assigned to another user.')
            );
        }
    }
}
