<?php

namespace FiloBlu\Esb\Model;

use Exception;
use FiloBlu\Esb\Api\Data\ProducerConfigurationInterface;
use FiloBlu\Esb\Api\Data\ProducerConfigurationInterfaceFactory;
use FiloBlu\Esb\Api\Data\ProducerInterface;
use FiloBlu\Esb\Api\ProducerConfigurationRepositoryInterface;
use FiloBlu\Esb\Api\ProducerResolverInterface;
use FiloBlu\Esb\Api\RetryStrategyResolverInterface;
use Magento\Framework\Api\Search\SearchCriteriaInterfaceFactory;
use Magento\Framework\Api\SearchResultsInterfaceFactory;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DataObjectFactory;
use Magento\Framework\Exception\NoSuchEntityException;
use RuntimeException;
use Zend_Db;

/**
 * Class ProducerConfigurationRepository
 * @package FiloBlu\Esb\Model
 */
class ProducerConfigurationRepository extends AbstractSqlRepository implements ProducerConfigurationRepositoryInterface
{
    /**
     * @var string
     */
    const TABLE = 'esb_producer_configuration';

    /**
     * @var DataObjectFactory
     */
    protected $dataObjectFactory;

    /**
     * @var ProducerConfigurationInterfaceFactory
     */
    protected $producerConfigurationFactory;

    /**
     * @var RetryStrategyResolverInterface
     */
    protected $strategyResolver;
    /**
     * @var ProducerResolverInterface
     */
    private $producerResolver;

    /**
     * ConsumerCofigurationRepository constructor.
     * @param ResourceConnection $resourceConnection
     * @param ProducerConfigurationInterfaceFactory $producerConfigurationFactory
     * @param SearchCriteriaInterfaceFactory $searchCriteriaFactory
     * @param SearchResultsInterfaceFactory $searchResultsFactory
     * @param RetryStrategyResolverInterface $strategyResolver
     * @param DataObjectFactory $dataObjectFactory
     * @param ProducerResolverInterface $producerResolver
     */
    public function __construct(
        ResourceConnection $resourceConnection,
        ProducerConfigurationInterfaceFactory $producerConfigurationFactory,
        SearchCriteriaInterfaceFactory $searchCriteriaFactory,
        SearchResultsInterfaceFactory $searchResultsFactory,
        RetryStrategyResolverInterface $strategyResolver,
        DataObjectFactory $dataObjectFactory,
    ProducerResolverInterface $producerResolver
    ) {
        parent::__construct($resourceConnection, $searchCriteriaFactory, $searchResultsFactory);
        $this->dataObjectFactory = $dataObjectFactory;
        $this->producerConfigurationFactory = $producerConfigurationFactory;
        $this->strategyResolver = $strategyResolver;
        $this->producerResolver = $producerResolver;
    }

    /**
     * @inheritDoc
     */
    public function save(ProducerConfigurationInterface $configuration)
    {
        $connection = $this->resourceConnection->getConnection();
        $table = $connection->getTableName(self::TABLE);

        $bindings = [
            ProducerConfigurationInterface::ID => $configuration->getId(),
            ProducerConfigurationInterface::PRODUCER => $configuration->getProducer()->getUniqueName(),
            ProducerConfigurationInterface::OUTPUT_EVENT => $configuration->getOutputEvent(),
            ProducerConfigurationInterface::PARAMETERS => $configuration->getParameters()->convertToJson(),
            ProducerConfigurationInterface::LABEL => $configuration->getLabel(),
            ProducerConfigurationInterface::ENABLE => $configuration->isEnabled(),
            ProducerConfigurationInterface::RETRY_STRATEGY => get_class($configuration->getRetryStrategy()),
            ProducerConfigurationInterface::UUID => $configuration->getUuid(),
            ProducerConfigurationInterface::PRIORITY => $configuration->getPriority()
        ];

        if ($configuration->getId()) {
            $connection->update($table, $bindings, $connection->quoteInto(
                sprintf('%s = ?', ProducerConfigurationInterface::ID),
                $configuration->getId()
            ));
        } else {
            $connection->insert($table, $bindings);
            $configuration->setId($connection->lastInsertId($table));
        }

        return $configuration;
    }

    /**
     * @inheritDoc
     */
    public function getByProducer($producer): ProducerConfigurationInterface
    {
        if (is_string($producer)) {
            $producerClass = $producer;
        } elseif ($producer instanceof ProducerInterface) {
            $producerClass = get_class($producer);
        } else {
            throw new NoSuchEntityException(__('Unable to find configuration for producer'));
        }

        $producerClass = trim($producerClass, '\\');

        $connection = $this->resourceConnection->getConnection();
        $table = $connection->getTableName(self::TABLE);

        $select = $connection->select()->from($table)->where(sprintf('%s = :producer', ProducerConfigurationInterface::PRODUCER));

        $item = $connection->fetchRow($select, ['producer' => $producerClass], Zend_Db::FETCH_ASSOC);

        if (($item === null) || empty($item)) {
            throw new NoSuchEntityException(__("Could not find producer item with id = {$producerClass}"));
        }

        return $this->fromRow($item);
    }

    /**
     * @param array $row
     * @return ProducerConfigurationInterface
     */
    public function fromRow(array $row)
    {
        /** @var ProducerConfigurationInterface $configuration */
        $configuration = $this->producerConfigurationFactory->create();

        $parameters = $this->dataObjectFactory->create();

        if ($row[ProducerConfigurationInterface::PARAMETERS]) {
            $parameters->setData(json_decode($row[ProducerConfigurationInterface::PARAMETERS], true));
        }

        $configuration
            ->setPriority($row[ProducerConfigurationInterface::PRIORITY])
            ->setOutputEvent($row[ProducerConfigurationInterface::OUTPUT_EVENT])
            ->setProducer($this->producerResolver->get($row[ProducerConfigurationInterface::PRODUCER]))
            ->setParameters($parameters)
            ->setId($row[ProducerConfigurationInterface::ID])
            ->setUuid($row[ProducerConfigurationInterface::UUID])
            ->setLabel($row[ProducerConfigurationInterface::LABEL]);

        try {
            $retryStrategy = $this->strategyResolver->get($row[ProducerConfigurationInterface::RETRY_STRATEGY]);
        } catch (Exception $exception) {
            $retryStrategy = $this->strategyResolver->getDefault();
        }

        $configuration->setRetryStrategy($retryStrategy);

        if ($row[ProducerConfigurationInterface::ENABLE]) {
            $configuration->enable();
        } else {
            $configuration->disable();
        }

        return $configuration;
    }

    /**
     * @inheritDoc
     */
    public function delete(ProducerConfigurationInterface $configuration)
    {
        if (!($id = $configuration->getId())) {
            return;
        }

        $this->deleteById($id);
    }

    /**
     * @inheritDoc
     */
    public function getTable(): string
    {
        return self::TABLE;
    }

    /**
     * @inheritDoc
     */
    public function getIdFieldName(): string
    {
        return ProducerConfigurationInterface::ID;
    }
}
