<?php

namespace Autogedal\TbiBank\Plugin;

use Autogedal\TbiBank\Model\ApprovalAccounting;
use Autogedal\TbiBank\Model\ApprovalConfig;
use Autogedal\TbiBank\Model\CallbackAudit;
use Autogedal\TbiBank\Model\CallbackStatusUpdate;
use Magento\Framework\Controller\Result\JsonFactory;
use Psr\Log\LoggerInterface;
use TbiBank\FusionPay\Controller\Api\Gateway;
use TbiBank\FusionPay\Helper\Data as FusionPayHelper;
use TbiBank\FusionPay\Model\Cryptor;

/** Handles the documented TBI callback shape without modifying the vendor gateway. */
class GatewayCallback
{
    public function __construct(
        private FusionPayHelper $fusionPayHelper,
        private ApprovalConfig $approvalConfig,
        private ApprovalAccounting $approvalAccounting,
        private CallbackStatusUpdate $callbackStatusUpdate,
        private JsonFactory $resultJsonFactory,
        private LoggerInterface $logger
    ) {
    }

    public function aroundExecute(Gateway $subject, callable $proceed)
    {
        $request = $subject->getRequest();
        if (!$request->isPost()) {
            return $proceed();
        }

        $requestData = json_decode((string) $request->getContent(), true);
        if (!is_array($requestData) || !isset($requestData['data'])) {
            return $proceed();
        }

        try {
            $encryptionKey = (string) $this->fusionPayHelper->getConfigValue(
                'tbi_settings/general_settings/encryption_key'
            );
            $decodedData = json_decode(
                Cryptor::Decrypt((string) $requestData['data'], $encryptionKey),
                true,
                512,
                JSON_THROW_ON_ERROR
            );
        } catch (\Throwable) {
            return $proceed();
        }

        if (!$this->isDocumentedStatusCallback($decodedData)) {
            return $proceed();
        }

        $orderId = (string) $decodedData['OrderId'];
        $statusCode = (string) $decodedData['Status'];
        $status = $this->getStatus($statusCode);
        $contractId = $this->getOptionalString($decodedData, ['ContractNumber', 'Contract_id', 'contract_id']);
        $message = $this->getOptionalString($decodedData, ['Message', 'message']);
        $motive = $this->getOptionalString($decodedData, ['Motive', 'motive']);

        try {
            // The documented callback carries the numeric provider status. The configurable
            // source-text setting applies to the legacy vendor path, while status code 1 is the
            // authoritative approval signal for this documented callback shape.
            if ($status === 'Approved') {
                $this->approvalAccounting->approve($orderId, (string) __('Approved'), $contractId, $statusCode, $message, $motive);
            } else {
                $this->callbackStatusUpdate->update(
                    $orderId,
                    $status,
                    $contractId,
                    CallbackAudit::COMMAND_UPDATE_STATUS,
                    $statusCode,
                    $message,
                    $motive
                );
            }
        } catch (\Throwable $exception) {
            $this->logger->warning('TBI official callback could not update the order.', [
                'increment_id' => $orderId,
                'status_code' => $statusCode,
                'exception' => $exception,
            ]);
            return $this->resultJsonFactory->create()->setData([
                'error' => 1,
                'message' => 'The TBI callback could not be processed.',
            ]);
        }

        return $this->resultJsonFactory->create()->setData([
            'success' => true,
            'order_id' => $orderId,
        ]);
    }

    private function isDocumentedStatusCallback(array $data): bool
    {
        return ($data['command'] ?? null) === 'update_status'
            && array_key_exists('OrderId', $data)
            && array_key_exists('Status', $data);
    }

    private function getStatus(string $statusCode): string
    {
        return match ($statusCode) {
            '0' => 'Canceled',
            '1' => 'Approved',
            '2' => 'In Progress',
            '3' => 'Rejected',
            default => 'Draft',
        };
    }

    private function getOptionalString(array $data, array $keys): ?string
    {
        foreach ($keys as $key) {
            if (array_key_exists($key, $data) && $data[$key] !== null && $data[$key] !== '') {
                return (string) $data[$key];
            }
        }
        return null;
    }
}
