<?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\Data;
use FiloBlu\ActiveCampaign\Model\Config\Source\JobStatus;
use FiloBlu\ActiveCampaign\Model\Resource\ActiveCampaign;
use Magento\Framework\App\ResourceConnection;
use Throwable;

/**
 * Class AbstractEntityExport
 * @package FiloBlu\ActiveCampaign\Model\Entity\Export
 */
abstract class AbstractEntityExport implements EntityExportInterface
{

    /**
     * @var string
     */
    protected $entity;
    /**
     * @var string
     */
    protected $direction;
    /**
     * @var JobsInterfaceFactory
     */
    protected $jobsFactory;
    /**
     * @var string
     */
    protected $type;
    /**
     * @var ActiveCampaign
     */
    protected $activeCampaign;
    /**
     * @var string
     */
    protected $statusToProcess = JobStatus::JOB_STATUS_WAITING;
    /**
     * @var string
     */
    protected $statusToPrepare = JobStatus::JOB_STATUS_WAITING;

    /**
     * @var array
     */
    protected $options = [];

    /**
     * AbstractEntityExport constructor.
     * @param ResourceConnection $resourceConnection
     * @param Data $helperData
     * @param JobsRepositoryInterface $jobsRepository
     * @param JobsInterfaceFactory $jobsFactory
     * @param ActiveCampaign $activeCampaign
     */
    public function __construct(
        ResourceConnection $resourceConnection,
        Data $helperData,
        JobsRepositoryInterface $jobsRepository,
        JobsInterfaceFactory $jobsFactory,
        ActiveCampaign $activeCampaign
    ) {
        $this->resourceConnection = $resourceConnection;
        $this->helperData = $helperData;
        $this->jobsRepository = $jobsRepository;
        $this->jobsFactory = $jobsFactory;
        $this->activeCampaign = $activeCampaign;
    }

    /**
     * @return mixed
     */
    public function prepare()
    {
        $ids = $this->getAllIncrementalIds();
        foreach ($ids as $id) {
            $job = $this->jobsFactory->create();
            $job->setEntity($this->entity)->setEntityId($id)->setDirection($this->direction)->setType(
                    $this->type
                )->setStatus($this->statusToPrepare);
            $this->jobsRepository->save($job);
        }
        return $ids;
    }

    /**
     * @return mixed
     */
    abstract public function getAllIncrementalIds();

    /**
     * @param $type
     * @return $this
     */
    public function setType($type)
    {
        $this->type = $type;
        return $this;
    }

    /**
     * @return mixed|void
     */
    public function process()
    {
        $this->options = [
            JobsInterface::TYPE      => $this->type,
            JobsInterface::DIRECTION => $this->direction,
            JobsInterface::ENTITY    => $this->entity
        ];
        $success = 0;
        $error = 0;
        $jobs = $this->jobsRepository->getAll($this->options, $this->statusToProcess);
        foreach ($jobs as $job) {
            try {
                //$this->jobsRepository->setStatus($options,$job['entity_id'],JobStatus::JOB_STATUS_PROCESSING);
                $resp = $this->executeProcess($job);
                if ($resp) {
                    $this->jobsRepository->setStatus(
                        $this->options,
                        $job['entity_id'],
                        JobStatus::JOB_STATUS_PROCESSED
                    );
                }
                $success++;
            } catch (Exception $exception) {
                $this->jobsRepository->setStatus(
                    $this->options,
                    $job['entity_id'],
                    JobStatus::JOB_STATUS_ERROR,
                    $exception->getMessage()
                );
                $error++;
            } catch (Throwable $throwable) {
                $this->jobsRepository->setStatus(
                    $this->options,
                    $job['entity_id'],
                    JobStatus::JOB_STATUS_ERROR,
                    $throwable->getMessage()
                );
                $error++;
            }
        }
        return [$success, $error];
    }

    /**
     * @param array $job
     * @return bool
     */
    abstract public function executeProcess(array $job): bool;

    /**
     * @param $entityData
     * @param $job
     * @throws Exception
     */
    public function executeContactRestProcess($entityData, $job)
    {
        //convert in json
        $jsonData = $this->helperData->serializer->serialize($entityData);
        $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);
        $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());
        }
        $this->jobsRepository->updateJobs(
            [JobsInterface::RESPONSE => $listResponse->getBody(), JobsInterface::LOG => ''],
            'id = "' . $job['id'] . '"'
        );
    }

    /**
     * @param array $updateArray
     * @param string $where
     * @return mixed|void
     */
    public function updateJobs(array $updateArray, $where = '')
    {
        $w = 'entity="' . $this->entity . '" AND direction="' . $this->direction . '"';
        if (!empty($where)) {
            $w .= ' AND ' . $where;
        }
        $this->jobsRepository->updateJobs($updateArray, $w);
    }

    /**
     * @param $entityData
     * @param $job
     * @throws Exception
     */
    public function executeSalesRestProcess($entityData, $job)
    {
        //convert in json
        $jsonData = $this->helperData->serializer->serialize($entityData);
        $this->jobsRepository->updateJobs([JobsInterface::REQUEST => $jsonData], 'id = "' . $job['id'] . '"');
        $job[JobsInterface::REQUEST] = $jsonData;

        $customerId = $this->activeCampaign->createCustomer($job);
        $entityData['ecomOrder']['customerid'] = $customerId;
        $jsonData = $this->helperData->serializer->serialize($entityData);
        $this->jobsRepository->updateJobs([JobsInterface::REQUEST => $jsonData, JobsInterface::LOG => ''],
            'id = "' . $job['id'] . '"');
        $job[JobsInterface::REQUEST] = $jsonData;

        $orderResponse = $this->activeCampaign->createOrder($job);
        if (!in_array($orderResponse->getStatusCode(), [200, 201])) {
            throw new Exception($orderResponse->getReasonPhrase());
        }
        $this->jobsRepository->updateJobs(
            [JobsInterface::RESPONSE => $orderResponse->getBody(), JobsInterface::LOG => ''],
            'id = "' . $job['id'] . '"'
        );
    }

    /**
     * @param $statusToProcess
     * @return $this
     */
    public function setStatusToProcess($statusToProcess)
    {
        $this->statusToProcess = $statusToProcess;
        return $this;
    }

    /**
     * @param $statusToPrepare
     * @return $this
     */
    public function setStatusToPrepare($statusToPrepare)
    {
        $this->statusToPrepare = $statusToPrepare;
        return $this;
    }

}