<?php

namespace Sibs\Sibs\Cron;

use Magento\Sales\Model\OrderFactory;
use Sibs\Sibs\DB\SibsDB;
use Sibs\Sibs\Helper\DataHelper;
use Sibs\Sibs\Model\SibsConstants;
use Sibs\Sibs\Model\SibsOrder;
use Sibs\Sibs\Utils\SibsException;
use Sibs\Sibs\Utils\SibsLog;

class WebhookCron
{
    /**
     * @var \Sibs\Sibs\Helper\DataHelper
     */
    protected $_sibsHelper;

    /**
     * @var \Magento\Sales\Model\OrderFactory
     */
    protected $_orderFactory;

    /**
     * Constructor
     *
     * @param \Sibs\Sibs\Helper\DataHelper $sibsHelper
     * @param \Magento\Sales\Model\OrderFactory $orderFactory
     */
    public function __construct(DataHelper $sibsHelper, OrderFactory $orderFactory)
    {
        $this->_sibsHelper = $sibsHelper;
        $this->_orderFactory = $orderFactory;
    }

    /**
     * Execute Cron Function
     */
    public function execute()
    {
        SibsLog::setNameFile(SibsConstants::SIBS_LOG_CRON);
        SibsLog::debug('CRON WEBHOOK begin');

        $dateToProcess = $this->_sibsHelper->getDateFromMagento('now', 'Y-m-d H:i:s', '-5 minutes');
        $unprocessedWebhooks = $this->getUnprocessedWebhooks($dateToProcess);

        foreach ($unprocessedWebhooks as $key => $unprocessedWebhook) {
            try {
                SibsLog::debug('CRON WEBHOOK Will execute ' . ($key + 1) . 'º Unprocessed webhook with ID ' . $unprocessedWebhook['id']);

                $decodedPayload = $unprocessedWebhook['decoded_payload'];

                if (is_null($decodedPayload)) {
                    throw new SibsException('CRON WEBHOOK is not decrypted', SibsConstants::SIBS_LOG_CRON);
                }

                $webhookPayload = json_decode($decodedPayload, true);

                list(
                    $orderIncrementId,
                    $transactionId,
                    $paymentMethod,
                    $paymentType,
                    $paymentStatus,
                    $amountValue
                ) = $this->getWebhookDecryptedPayloadData($webhookPayload);

                if ($transactionId === SibsConstants::SIBS_WEBHOOK_TEST) {
                    SibsLog::debug("CRON WEBHOOK Order #$orderIncrementId is a test webhook");

                    self::updateWebhook($unprocessedWebhook['id'], [
                        'processed_qty' => ($unprocessedWebhook['processed_qty'] + 1),
                        'processed_at'  => $this->_sibsHelper->getDateFromMagento(),
                        'exception'     => ''
                    ]);

                    continue;
                }

                $order = $this->_orderFactory->create()->loadByIncrementId($orderIncrementId);
                $sibsOrder = new SibsOrder($order->getEntityId());

                if (is_null($sibsOrder->get())) {
                    throw new SibsException("CRON WEBHOOK Unknown Order #$orderIncrementId for $transactionId", SibsConstants::SIBS_LOG_CRON);
                }

                SibsLog::debug("CRON WEBHOOK Found Order #$orderIncrementId for $transactionId");

                if ($paymentMethod != $this->_sibsHelper->sibs_get_payment_method_for_spg($sibsOrder->getPaymentMethod())) {
                    throw new SibsException("CRON WEBHOOK Invalid Payment Method", SibsConstants::SIBS_LOG_CRON);
                }

                if (!in_array($paymentStatus, SibsConstants::SIBS_KNOWN_API_STATUSES)) {
                    SibsLog::debug("CRON WEBHOOK Order #$orderIncrementId for $transactionId received an webhook with a Unknown status and will not have more processing retries");

                    self::updateWebhook($unprocessedWebhook['id'], [
                        'processed_qty' => ($unprocessedWebhook['processed_qty'] + 1),
                        'processed_at'  => $this->_sibsHelper->getDateFromMagento(),
                        'exception'     => ''
                    ]);

                    continue;
                }

                $this->process($sibsOrder, $paymentType, $paymentStatus, $amountValue, $transactionId);

                SibsLog::debug("CRON WEBHOOK Order #$orderIncrementId for $transactionId processed");

                self::updateWebhook($unprocessedWebhook['id'], [
                    'processed_qty' => ($unprocessedWebhook['processed_qty'] + 1),
                    'processed_at'  => $this->_sibsHelper->getDateFromMagento(),
                    'exception'     => ''
                ]);
            } catch (\Exception $e) {
                self::updateWebhook($unprocessedWebhook['id'], [
                    'processed_qty' => ($unprocessedWebhook['processed_qty'] + 1),
                    'exception'     => $e->getMessage()
                ]);
            }
        }

        SibsLog::debug('CRON WEBHOOK end');
    }

    /**
     * Get Webhook Decrypted Payload Data
     *
     * @param array $webhookPayload
     *
     * @return array
     */
    private function getWebhookDecryptedPayloadData($webhookPayload)
    {
        if (empty($webhookPayload['merchant']['transactonId'])) {
            throw new SibsException('CRON WEBHOOK Missing [merchant][transactonId]');
        }

        SibsLog::debug("CRON WEBHOOK [merchant][transactonId]: " . $webhookPayload['merchant']['transactonId']);

        // TODO: Put back when webhook fix transactonId to transactionId
        /*
        if (empty($webhookPayload['merchant']['transactionId'])) {
            throw new SibsException('CRON WEBHOOK Missing [merchant][transactionId]');
        }

        SibsLog::debug("CRON WEBHOOK [merchant][transactionId]: " . $webhookPayload['merchant']['transactionId']);
        */

        if (empty($webhookPayload['transactionID'])) {
            throw new SibsException('CRON WEBHOOK Missing [transactionID]');
        }

        SibsLog::debug("CRON WEBHOOK [transactionID]: " . $webhookPayload['transactionID']);

        if (empty($webhookPayload['paymentMethod'])) {
            throw new SibsException('CRON WEBHOOK Missing [paymentMethod]');
        }

        SibsLog::debug("CRON WEBHOOK [paymentMethod]: " . $webhookPayload['paymentMethod']);

        if (empty($webhookPayload['paymentType'])) {
            throw new SibsException('CRON WEBHOOK Missing [paymentType]');
        }

        SibsLog::debug("CRON WEBHOOK [paymentType]: " . $webhookPayload['paymentType']);

        if (empty($webhookPayload['paymentStatus'])) {
            throw new SibsException('CRON WEBHOOK Missing [paymentStatus]');
        }

        SibsLog::debug("CRON WEBHOOK [paymentStatus]: " . $webhookPayload['paymentStatus']);

        if (empty($webhookPayload['amount']['value'])) {
            throw new SibsException('CRON WEBHOOK Missing [amount][value]');
        }

        SibsLog::debug("CRON WEBHOOK [amount][value]: " . $webhookPayload['amount']['value']);

        return [
            // TODO: Put back when webhook fix transactonId to transactionId
            // $webhookPayload['merchant']['transactionId'],
            $webhookPayload['merchant']['transactonId'],
            $webhookPayload['transactionID'],
            $webhookPayload['paymentMethod'],
            $webhookPayload['paymentType'],
            $webhookPayload['paymentStatus'],
            $webhookPayload['amount']['value']
        ];
    }

    /**
     * Get Unprocessed Webhooks
     *
     * @param string $dateToProcess
     * 
     * @return array
     */
    public function getUnprocessedWebhooks($dateToProcess)
    {
        $retryQty = SibsConstants::SIBS_WEBHOOK_RETRY_QTY;

        $table_name          = SibsDB::getTableName('sibs_webhook_calls');
        $unprocessedWebhooks = SibsDB::fetchAll("SELECT *
            FROM `$table_name`
            WHERE `processed_qty` < $retryQty
                AND `processed_at` IS NULL
                AND `created_at` < '$dateToProcess';
        ");

        SibsLog::debug('CRON WEBHOOK found ' . count($unprocessedWebhooks) . ' unprocessed Webhooks before ' . $dateToProcess);

        return $unprocessedWebhooks ?? [];
    }

    /**
     * Get Unprocessed Webhooks
     *
     * @param int $webhookID
     * @param array $updateData
     */
    public function updateWebhook($webhookID, $updateData)
    {
        $table_name = SibsDB::getTableName('sibs_webhook_calls');

        if (!isset($updateData['updated_at'])) {
            $updateData['updated_at'] = $this->_sibsHelper->getDateFromMagento();
        }

        SibsDB::update($table_name, $updateData, "id = $webhookID");
    }

    /**
     * process Webhook Calls
     *
     * @param SibsOrder $sibsOrder
     * @param string $paymentType
     * @param string $paymentStatus
     * @param string $amountValue
     * @param string $transactionId
     */
    public function process($sibsOrder, $paymentType, $paymentStatus, $amountValue, $transactionId)
    {
        list($orderState, $orderStatus) = $this->_sibsHelper->sibs_get_order_state_and_status($paymentStatus, $paymentType);
        SibsLog::info("CRON WEBHOOK - (Order state: $orderState, Order status: $orderStatus)");

        switch ($paymentType) {
            case SibsConstants::SIBS_PAYMENT_TYPE_AUTH:
                $sibsOrder->authorize($orderState, $orderStatus);
                break;
            case SibsConstants::SIBS_PAYMENT_TYPE_PURS:
                $sibsOrder->purchase($orderState, $orderStatus, $amountValue);
                break;
            case SibsConstants::SIBS_PAYMENT_TYPE_CAPT:
                $captureStatus = $this->_sibsHelper->sibs_get_capture_refund_status($paymentStatus);
                SibsLog::info("CRON WEBHOOK - Capture status: $captureStatus");
                $sibsOrder->capture($orderState, $orderStatus, $captureStatus, $amountValue, $transactionId);
                break;
            case SibsConstants::SIBS_PAYMENT_TYPE_RFND:
                $refundStatus = $this->_sibsHelper->sibs_get_capture_refund_status($paymentStatus);
                SibsLog::info("CRON WEBHOOK - Refund status: $refundStatus");
                $sibsOrder->refund($orderStatus, $orderStatus, $refundStatus, $amountValue, $transactionId);
                break;
            default:
                throw new SibsException('CRON WEBHOOK Invalid Payment Type', SibsConstants::SIBS_LOG_CRON);
        }
    }
}
