<?php

namespace Autogedal\TbiBank\Model;

use Magento\Framework\App\ResourceConnection;
use Magento\Framework\Exception\LocalizedException;
use Magento\Sales\Api\InvoiceRepositoryInterface;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Sales\Model\Order;
use Magento\Sales\Model\OrderFactory;
use Magento\Sales\Model\Order\Invoice;
use Magento\Sales\Model\ResourceModel\Order as OrderResource;
use Magento\Sales\Model\Service\InvoiceService;

/** Records financed orders locally; this is not a capture or settlement request to TBI. */
class ApprovalAccounting
{
    public const MARKER = 'autogedal_tbi_approval_accounted';

    public function __construct(
        private ResourceConnection $resource,
        private OrderFactory $orderFactory,
        private OrderResource $orderResource,
        private OrderRepositoryInterface $orderRepository,
        private InvoiceService $invoiceService,
        private InvoiceRepositoryInterface $invoiceRepository,
        private ApprovalConfig $approvalConfig,
        private ApprovedOrderEmail $approvedEmail
    ) {
    }

    public function approve(
        string $incrementId,
        string $displayStatus,
        ?string $contractId = null,
        ?string $statusCode = null,
        ?string $message = null,
        ?string $motive = null
    ): void
    {
        $connection = $this->resource->getConnection();
        $committed = false;
        $order = null;
        $connection->beginTransaction();
        try {
            // Serialize duplicate callbacks and preserve nonnumeric merchant order numbers.
            $table = $connection->quoteIdentifier($this->resource->getTableName('sales_order'));
            $ids = $connection->fetchCol(
                'SELECT entity_id FROM ' . $table . ' WHERE increment_id = ? FOR UPDATE',
                [$incrementId]
            );
            if (count($ids) !== 1) {
                throw new LocalizedException(__('The TBI order number is unknown or ambiguous.'));
            }
            $order = $this->orderFactory->create();
            $this->orderResource->load($order, (int) $ids[0]);
            $payment = $order->getPayment();
            if (!$order->getId() || !$payment || $payment->getMethod() !== PaymentMethod::CODE) {
                throw new LocalizedException(__('The order does not use TBI financing.'));
            }
            if ($payment->getAdditionalInformation(self::MARKER)) {
                // A duplicate must never reopen an order already advanced, canceled, or refunded.
                if ($contractId !== null && $contractId !== '') {
                    foreach (['sales_order', 'sales_order_grid'] as $targetTable) {
                        $connection->update(
                            $this->resource->getTableName($targetTable),
                            ['tbi_contract_id' => $contractId],
                            ['entity_id = ?' => (int) $order->getId()]
                        );
                    }
                }
                $connection->commit();
                $committed = true;
                return;
            }
            if (!in_array($order->getState(), [ApprovalConfig::ORDER_STATE, Order::STATE_NEW, Order::STATE_PENDING_PAYMENT], true)
                || !in_array($order->getStatus(), ['new', 'pending', 'pending_payment', ApprovalConfig::WAITING_STATUS], true)
            ) {
                throw new LocalizedException(__('The order is no longer waiting for TBI approval.'));
            }
            $status = $this->approvalConfig->getApprovedStatus($order->getStoreId());
            if (!array_key_exists($status, $order->getConfig()->getStateStatuses(ApprovalConfig::ORDER_STATE))
                || $status === ApprovalConfig::WAITING_STATUS
            ) {
                throw new LocalizedException(__('Choose an approved TBI order status assigned to the Pending state.'));
            }
            $payment->setIsTransactionPending(false);
            $payment->setIsTransactionApproved(true);
            if ((float) $order->getBaseTotalDue() > 0.0001) {
                if (!$order->canInvoice()) {
                    throw new LocalizedException(__('The TBI order has an unpaid invoice and requires reconciliation.'));
                }
                $invoice = $this->invoiceService->prepareInvoice($order);
                if (!(float) $invoice->getTotalQty()
                    || abs((float) $invoice->getBaseGrandTotal() - (float) $order->getBaseTotalDue()) > 0.0001
                ) {
                    throw new LocalizedException(__('The TBI invoice does not cover the outstanding order amount.'));
                }
                $invoice->setRequestedCaptureCase(Invoice::CAPTURE_OFFLINE);
                $invoice->register()->pay();
                if ((float) $order->getBaseTotalDue() > 0.0001) {
                    throw new LocalizedException(__('The TBI invoice did not clear the outstanding order amount.'));
                }
                $this->invoiceRepository->save($invoice);
            }
            $payment->setAdditionalInformation(self::MARKER, true);
            $gridData = ['tbi_status' => $displayStatus, 'status' => $status];
            if ($contractId !== null && $contractId !== '') {
                $order->setData('tbi_contract_id', $contractId);
                $gridData['tbi_contract_id'] = $contractId;
            }
            $order->setData('tbi_status', $displayStatus);
            $order->setState(ApprovalConfig::ORDER_STATE)->setStatus($status)->setIsInProcess(false);
            $order->addStatusHistoryComment(
                CallbackAudit::comment(
                    CallbackAudit::COMMAND_UPDATE_STATUS,
                    $displayStatus,
                    $statusCode ?? CallbackAudit::statusCodeFor($displayStatus),
                    $contractId,
                    $message,
                    $motive
                ),
                $status
            )
                ->setIsCustomerNotified(false);
            $this->orderRepository->save($order);
            // Match by entity ID, avoiding the vendor updater's integer cast of increment_id.
            $connection->update(
                $this->resource->getTableName('sales_order_grid'),
                $gridData,
                ['entity_id = ?' => (int) $order->getId()]
            );
            $connection->commit();
            $committed = true;
        } catch (\Throwable $exception) {
            $connection->rollBack();
            if ($exception instanceof LocalizedException) {
                throw $exception;
            }
            throw new LocalizedException(__('TBI approval could not be recorded. Retry the notification.'), $exception);
        } finally {
            if ($committed && $order instanceof Order) {
                $this->approvedEmail->send($order);
            }
        }
    }
}
