<?php

namespace Sibs\Stargate\Cron;

use Sibs\Stargate\Api\CheckStatus;
use Sibs\Stargate\Config\SibsConfig;
use Sibs\Stargate\Constants\Logs;
use Sibs\Stargate\Constants\PaymentStatus;
use Sibs\Stargate\Constants\PaymentTypes;
use Sibs\Stargate\Constants\Plugin;
use Sibs\Stargate\Constants\WoocommerceOrderStatus;
use Sibs\Stargate\Core\SibsGeneralFunctions;
use Sibs\Stargate\Dto\WebhookDto;
use Sibs\Stargate\Exceptions\SibsException;
use Sibs\Stargate\Loggers\SibsLogger;
use Sibs\Stargate\Models\SibsGeneralModels;

defined('ABSPATH') || exit;

class ProcessWebhookCalls
{
    /**
     * Process.
     */
    public static function process()
    {
        $unprocessed_webhook_calls = SibsGeneralModels::get_unprocessed_webhook_calls();

        foreach ($unprocessed_webhook_calls as $key => $unprocessed_webhook_call) {
            SibsLogger::debug('Execute '.($key + 1).'º Unprocessed', [
                'source' => Logs::CRON,
            ]);
            self::single_process($unprocessed_webhook_call);
        }
    }

    /**
     * Single Process.
     */
    public static function single_process(array $unprocessed_webhook_call): bool
    {
        $webhook_id = $unprocessed_webhook_call['id'] ?? 0;
        $processed_qty = $unprocessed_webhook_call['processed_qty'] ?? 0;
        $processed_at = $unprocessed_webhook_call['processed_at'] ?? 'Not processed yet';

        SibsLogger::debug("WEBHOOK id: {$webhook_id}", [
            'source' => Logs::CRON,
        ]);
        SibsLogger::debug("WEBHOOK processed_qty: {$processed_qty}", [
            'source' => Logs::CRON,
        ]);
        SibsLogger::debug("WEBHOOK processed_at: {$processed_at}", [
            'source' => Logs::CRON,
        ]);

        try {
            $decoded_payload = $unprocessed_webhook_call['decoded_payload'];

            if (is_null($decoded_payload)) {
                throw new SibsException('WEBHOOK is not decrypted', [
                    'source' => Logs::CRON,
                ]);
            }

            $webhook_payload = json_decode($decoded_payload, true);

            /** @var WebhookDto $webhook_dto */
            $webhook_dto = WebhookDto::fromArray($webhook_payload);

            SibsLogger::debug('WEBHOOK PAYLOAD', [
                'source' => Logs::CRON,
                'payload' => $webhook_dto->toArray(),
            ]);

            if (Plugin::WEBHOOK_TEST === $webhook_dto->transactionId) {
                SibsLogger::debug('WEBHOOK is a test and have nothing to process', [
                    'source' => Logs::CRON,
                ]);
                SibsGeneralModels::update_webhook_calls($webhook_id, [
                    'processed_qty' => ($processed_qty + 1),
                    'processed_at' => SibsConfig::getStoreCurrentTime(),
                ]);

                return true;
            }

            $wc_order = wc_get_order($webhook_dto->merchant->transactionId);

            if (!$wc_order instanceof \WC_Order) {
                throw new SibsException(
                    "WEBHOOK Unknown Order #{$webhook_dto->merchant->transactionId} for {$webhook_dto->transactionId}",
                    [
                        'source' => Logs::CRON,
                    ]
                );
            }

            $sibs_transaction = SibsGeneralModels::get_transaction_by_transaction_id($webhook_dto->transactionId);

            if (empty($sibs_transaction)) {
                $sibs_transaction = SibsGeneralModels::get_transaction($wc_order);

                if (empty($sibs_transaction)) {
                    throw new SibsException(
                        "WEBHOOK Transaction not found for Order #{$webhook_dto->merchant->transactionId}",
                        [
                            'source' => Logs::CRON,
                        ]
                    );
                }
            }

            SibsLogger::debug('WEBHOOK order_no: '.$sibs_transaction['order_no'], [
                'source' => Logs::CRON,
            ]);

            if ($webhook_dto->paymentMethod != $sibs_transaction['payment_brand']) {
                throw new SibsException('Invalid payment method', [
                    'source' => Logs::CRON,
                ]);
            }

            if (in_array($webhook_dto->paymentStatus, PaymentStatus::pending())) {
                SibsLogger::debug("WEBHOOK have status {$webhook_dto->paymentStatus} and nothing will process", [
                    'source' => Logs::CRON,
                ]);
                SibsGeneralModels::update_webhook_calls($webhook_id, [
                    'processed_qty' => ($processed_qty + 1),
                    'processed_at' => SibsConfig::getStoreCurrentTime(),
                ]);

                return true;
            }

            switch ($webhook_dto->paymentType) {
                case PaymentTypes::AUTH:
                case PaymentTypes::PURS:
                    self::webhook_payment($wc_order, $webhook_payload, $sibs_transaction, $webhook_dto);

                    break;

                case PaymentTypes::CAPT:
                    self::webhook_capture($wc_order, $sibs_transaction, $webhook_dto);

                    break;

                case PaymentTypes::RFND:
                    self::webhook_refund($wc_order, $sibs_transaction, $webhook_dto);

                    break;

                default:
                    throw new SibsException('Invalid payment type', [
                        'source' => Logs::CRON,
                    ]);
            }

            SibsGeneralModels::update_webhook_calls($webhook_id, [
                'processed_qty' => ($processed_qty + 1),
                'processed_at' => SibsConfig::getStoreCurrentTime(),
            ]);

            return true;
        } catch (SibsException $e) {
            SibsLogger::warning('WEBHOOK problem: '.$e->getExceptionMessage(), [
                'source' => Logs::CRON,
            ]);
            SibsGeneralModels::update_webhook_calls($webhook_id, [
                'processed_qty' => ($processed_qty + 1),
                'exception' => $e->getExceptionMessage(),
            ]);

            return false;
        }
    }

    /**
     * Webhook Payment.
     *
     * @return bool
     */
    protected static function webhook_payment(
        \WC_Order $wc_order,
        array $webhook_payload,
        array $sibs_transaction,
        WebhookDto $webhook_dto
    ) {
        SibsLogger::debug('WEBHOOK will process Payment on order #'.$wc_order->get_id(), [
            'source' => Logs::CRON,
        ]);

        $order_status = SibsGeneralFunctions::convertTransactionStatus($webhook_dto->paymentStatus, $webhook_dto->paymentType);

        SibsGeneralModels::update_transaction_by_reference_id($sibs_transaction['reference_id'], [
            'payment_status' => $order_status,
        ]);

        SibsLogger::debug("WEBHOOK order_status: {$order_status}", [
            'source' => Logs::CRON,
        ]);

        if (SibsGeneralFunctions::isAlreadyPaidOrAuthorized($wc_order)) {
            SibsLogger::debug('WEBHOOK Order is Already Paid or Authorized and nothing will process', [
                'source' => Logs::CRON,
            ]);

            return true;
        }

        switch ($order_status) {
            case WoocommerceOrderStatus::PROCESSING:
            case WoocommerceOrderStatus::AUTHORIZED:
                try {
                    $wc_order->update_status($order_status);

                    CheckStatus::save_or_update_token_info($webhook_payload);
                } catch (\Throwable $t) {
                    SibsLogger::error($t->getMessage(), [
                        'source' => Logs::CRON,
                    ]);
                }

                SibsLogger::debug('Reduce Stock webhook', [
                    'source' => Logs::CRON,
                ]);
                wc_reduce_stock_levels($wc_order->get_id());

                break;

            case WoocommerceOrderStatus::ON_HOLD:
                try {
                    $wc_order->update_status($order_status);
                } catch (\Throwable $t) {
                    SibsLogger::error($t->getMessage(), [
                        'source' => Logs::CRON,
                    ]);
                }

                wc_increase_stock_levels($wc_order->get_id());

                break;

            default:
                $errorMessage = 'WEBHOOK operation failed.';
                $wc_order->add_order_note($errorMessage);
        }

        SibsLogger::debug('WEBHOOK Payment operation with success', [
            'source' => Logs::CRON,
        ]);

        return true;
    }

    /**
     * Webhook Capture.
     *
     * @return bool
     */
    protected static function webhook_capture(
        \WC_Order $wc_order,
        array $sibs_transaction,
        WebhookDto $webhook_dto
    ) {
        SibsLogger::debug('WEBHOOK will process Capture on order #'.$wc_order->get_id(), [
            'source' => Logs::CRON,
        ]);

        if (SibsGeneralFunctions::isAlreadyCaptured($wc_order)) {
            SibsLogger::debug('WEBHOOK Order is Already Captured and nothing will process', [
                'source' => Logs::CRON,
            ]);

            return true;
        }

        $note_message = (WoocommerceOrderStatus::FAILED === $webhook_dto->paymentStatus)
            ? __('WEBHOOK Capture operation failed', 'sibs-woo-stg')
            : sprintf(
                __('Order was Captured for %s', 'sibs-woo-stg'),
                $webhook_dto->amount->value.' '.$wc_order->get_currency()
            );

        $orderRemainingCapture = $wc_order->get_total() - $sibs_transaction['captured_amount'];
        SibsLogger::debug('WEBHOOK order remaining Capture: '.$orderRemainingCapture, [
            'source' => Logs::CRON,
        ]);

        $order_status = ($webhook_dto->amount->value >= $orderRemainingCapture)
            ? WoocommerceOrderStatus::PROCESSING
            : WoocommerceOrderStatus::PARTIALLY_CAPTURED;

        try {
            $wc_order->update_status($order_status);
        } catch (\Throwable $t) {
            SibsLogger::error($t->getMessage(), [
                'source' => Logs::CRON,
            ]);
        }

        $wc_order->add_order_note($note_message);

        SibsGeneralModels::update_transaction($wc_order, [
            'payment_status' => $order_status,
            'capture_status' => SibsGeneralFunctions::convertTransactionStatusCaptRfnd($webhook_dto->paymentStatus),
            'captured_amount' => $webhook_dto->amount->value + $sibs_transaction['captured_amount'],
            'capture_information' => $note_message
                .(!empty($sibs_transaction['capture_information']) ? ', '.$sibs_transaction['capture_information'] : ''),
        ]);

        SibsLogger::debug('WEBHOOK Refund operation with success', [
            'source' => Logs::CRON,
        ]);

        return true;
    }

    /**
     * Webhook Refund.
     *
     * @return bool
     */
    protected static function webhook_refund(
        \WC_Order $wc_order,
        array $sibs_transaction,
        WebhookDto $webhook_dto
    ) {
        SibsLogger::debug('WEBHOOK will process Refund on order #'.$wc_order->get_id(), [
            'source' => Logs::CRON,
        ]);

        if (SibsGeneralFunctions::isAlreadyRefunded($wc_order)) {
            SibsLogger::debug('WEBHOOK Order is Refunded and nothing will process', [
                'source' => Logs::CRON,
            ]);

            return true;
        }

        $note_message = (WoocommerceOrderStatus::FAILED === $webhook_dto->paymentStatus)
            ? __('WEBHOOK Refund operation failed', 'sibs-woo-stg')
            : sprintf(
                __('Order was Refunded for %s', 'sibs-woo-stg'),
                $webhook_dto->amount->value.' '.$wc_order->get_currency()
            );

        $orderRemainingRefund = $wc_order->get_total() - $wc_order->get_total_refunded();
        SibsLogger::debug('WEBHOOK order remaining Refund: '.$orderRemainingRefund, [
            'source' => Logs::CRON,
        ]);

        $order_status = ($webhook_dto->amount->value >= $orderRemainingRefund)
            ? WoocommerceOrderStatus::REFUNDED
            : WoocommerceOrderStatus::PARTIALLY_REFUNDED;

        try {
            $wc_order->update_status($order_status);
        } catch (\Throwable $t) {
            SibsLogger::error($t->getMessage(), [
                'source' => Logs::CRON,
            ]);
        }

        $wc_order->add_order_note($note_message);

        SibsGeneralModels::update_transaction($wc_order, [
            'payment_status' => $order_status,
            'refund_status' => SibsGeneralFunctions::convertTransactionStatusCaptRfnd($webhook_dto->paymentStatus),
            'refunded_amount' => $webhook_dto->amount->value + $sibs_transaction['refunded_amount'],
            'refund_information' => $note_message
                .(!empty($sibs_transaction['refund_information']) ? ', '.$sibs_transaction['refund_information'] : ''),
        ]);

        SibsLogger::debug('WEBHOOK Refund operation with success', [
            'source' => Logs::CRON,
        ]);

        return true;
    }
}
