<?php

namespace Autogedal\TbiBank\Model;

use Magento\Framework\App\ResourceConnection;
use Magento\Framework\Exception\LocalizedException;
use Magento\Sales\Model\OrderFactory;
use Magento\Sales\Model\ResourceModel\Order as OrderResource;

/** Persists non-approval TBI callbacks without allowing them to release an order. */
class CallbackStatusUpdate
{
    public function __construct(
        private ResourceConnection $resource,
        private OrderFactory $orderFactory,
        private OrderResource $orderResource
    ) {
    }

    public function update(
        string $incrementId,
        string $tbiStatus,
        ?string $contractId = null,
        string $command = CallbackAudit::COMMAND_UPDATE_STATUS,
        ?string $statusCode = null,
        ?string $message = null,
        ?string $motive = null
    ): void {
        $connection = $this->resource->getConnection();
        $orderTable = $this->resource->getTableName('sales_order');
        $paymentTable = $this->resource->getTableName('sales_order_payment');
        $connection->beginTransaction();
        try {
            // Lock the order before deciding whether a callback may preserve the waiting state.
            // ApprovalAccounting uses the same row lock, so a racing non-approval callback sees
            // the committed approval instead of applying a stale waiting-state decision.
            $rows = $connection->fetchAll(
                $connection->select()
                    ->from(['o' => $orderTable], ['entity_id', 'state', 'status'])
                    ->joinLeft(
                        ['p' => $paymentTable],
                        'p.parent_id = o.entity_id',
                        ['payment_method' => 'method', 'additional_information']
                    )
                    ->where('o.increment_id = ?', $incrementId)
                    ->forUpdate(true)
            );

            if (count($rows) !== 1) {
                throw new LocalizedException(__('The TBI order number is unknown or ambiguous.'));
            }
            if (($rows[0]['payment_method'] ?? '') !== PaymentMethod::CODE) {
                throw new LocalizedException(__('The order does not use TBI financing.'));
            }

            $data = ['tbi_status' => $tbiStatus];
            $additionalInformation = json_decode((string) ($rows[0]['additional_information'] ?? ''), true);
            $isAlreadyApproved = is_array($additionalInformation)
                && !empty($additionalInformation[ApprovalAccounting::MARKER]);
            $isWaitingState = !$isAlreadyApproved && in_array(
                $rows[0]['state'] ?? '',
                [ApprovalConfig::ORDER_STATE, 'new', 'pending_payment'],
                true
            );
            $isUnreleasedStatus = in_array(
                $rows[0]['status'] ?? '',
                ['new', 'pending', 'pending_payment', ApprovalConfig::WAITING_STATUS],
                true
            );
            if ($isWaitingState && $isUnreleasedStatus) {
                $data['state'] = ApprovalConfig::ORDER_STATE;
                $data['status'] = ApprovalConfig::WAITING_STATUS;
            }
            if ($contractId !== null && $contractId !== '') {
                $data['tbi_contract_id'] = $contractId;
            }

            $order = $this->orderFactory->create();
            $this->orderResource->load($order, (int) $rows[0]['entity_id']);
            $order->setData('tbi_status', $tbiStatus);
            if ($contractId !== null && $contractId !== '') {
                $order->setData('tbi_contract_id', $contractId);
            }
            if (isset($data['state'])) {
                $order->setState($data['state'])->setStatus($data['status']);
            }
            $callbackStatusCode = $statusCode ?? CallbackAudit::statusCodeFor($tbiStatus);
            if ($command === CallbackAudit::COMMAND_FAILURE_REDIRECT
                || in_array($callbackStatusCode, ['0', '3'], true)
            ) {
                $order->addStatusHistoryComment(
                    CallbackAudit::comment(
                        $command,
                        $tbiStatus,
                        $callbackStatusCode,
                        $contractId,
                        $message,
                        $motive
                    ),
                    $order->getStatus()
                )->setIsCustomerNotified(false);
            }
            $this->orderResource->save($order);
            $gridData = ['tbi_status' => $tbiStatus];
            if (isset($data['status'])) {
                $gridData['status'] = $data['status'];
            }
            if ($contractId !== null && $contractId !== '') {
                $gridData['tbi_contract_id'] = $contractId;
            }
            $connection->update(
                $this->resource->getTableName('sales_order_grid'),
                $gridData,
                ['entity_id = ?' => (int) $order->getId()]
            );
            $connection->commit();
        } catch (\Throwable $exception) {
            $connection->rollBack();
            throw $exception;
        }
    }
}
