<?php
/**
 * 2026 Paysera
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Academic Free License (AFL 3.0)
 * that is bundled with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://opensource.org/licenses/afl-3.0.php
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@prestashop.com so we can send you a copy immediately.
 *
 * @author Paysera <plugins@paysera.com>
 * @copyright 2026 Paysera
 * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
 * International Registered Trademark & Property of Paysera
 */

declare(strict_types=1);

namespace Paysera\PrestashopPayments\Service;

use Paysera\CheckoutSdk\Entity\PaymentCallback;
use Paysera\PrestashopPayments\Dto\PayseraPayment;
use Paysera\PrestashopPayments\Exception\AmountMismatchException;
use Paysera\PrestashopPayments\Exception\CallbackValidationException;
use Psr\Log\LoggerInterface;

if (!defined('_PS_VERSION_')) {
    exit;
}

class CallbackValidator
{
    public function __construct(
        private MoneyConverter $moneyConverter,
        private LoggerInterface $logger
    ) {
    }

    public function validatePaymentCompletion(PaymentCallback $callback): void
    {
        $orderInfo = $callback->getOrder()->getOrderInfo();

        if (!$orderInfo->isPaid()) {
            $this->logger->warning('Callback received for non-paid order', [
                'paysera_order_id' => $callback->getOrder()->getId(),
                'is_paid' => false,
            ]);

            throw new CallbackValidationException('Payment not completed - order is not marked as paid');
        }

        $amountPaid = $orderInfo->getAmountPaid();
        if ($amountPaid <= 0) {
            $this->logger->warning('Callback received with zero amount paid', [
                'paysera_order_id' => $callback->getOrder()->getId(),
                'amount_paid' => $amountPaid,
            ]);

            throw new CallbackValidationException(sprintf('Payment not completed - amountPaid is %d', $amountPaid));
        }

        $this->logger->debug('Payment completion validated', [
            'paysera_order_id' => $callback->getOrder()->getId(),
            'amount_paid' => $amountPaid,
        ]);
    }

    public function validateAmountAndCurrency(PaymentCallback $callback, \Order $order): void
    {
        $orderInfo = $callback->getOrder()->getOrderInfo();

        $expectedAmount = $this->moneyConverter->getExpectedAmountFromOrder($order);
        $expectedCurrency = $this->moneyConverter->getCurrencyCodeFromOrder($order);

        $callbackAmount = $orderInfo->getAmount();
        $callbackAmountPaid = $orderInfo->getAmountPaid();
        $callbackCurrency = $orderInfo->getCurrency();

        if ($callbackAmount !== $expectedAmount) {
            $this->logger->error('Amount mismatch detected', [
                'order_id' => $order->id,
                'expected_amount' => $expectedAmount,
                'callback_amount' => $callbackAmount,
                'difference' => $expectedAmount - $callbackAmount,
            ]);

            throw new AmountMismatchException(sprintf('Amount mismatch: expected %d cents, got %d cents (difference: %d)', $expectedAmount, $callbackAmount, $expectedAmount - $callbackAmount));
        }

        if ($callbackAmountPaid !== $callbackAmount) {
            $this->logger->error('Partial payment detected', [
                'order_id' => $order->id,
                'callback_amount' => $callbackAmount,
                'callback_amount_paid' => $callbackAmountPaid,
                'unpaid' => $callbackAmount - $callbackAmountPaid,
            ]);

            throw new AmountMismatchException(sprintf('Partial payment: expected %d cents paid, got %d cents (unpaid: %d)', $callbackAmount, $callbackAmountPaid, $callbackAmount - $callbackAmountPaid));
        }

        if ($callbackCurrency !== $expectedCurrency) {
            $this->logger->error('Currency mismatch detected', [
                'order_id' => $order->id,
                'expected_currency' => $expectedCurrency,
                'callback_currency' => $callbackCurrency,
            ]);

            throw new AmountMismatchException(sprintf('Currency mismatch: expected %s, got %s', $expectedCurrency, $callbackCurrency));
        }

        $this->logger->debug('Amount and currency validated', [
            'order_id' => $order->id,
            'amount' => $expectedAmount,
            'currency' => $expectedCurrency,
        ]);
    }

    public function validateAgainstInitiationData(PaymentCallback $callback, ?PayseraPayment $initiationData): void
    {
        $callbackPayseraOrderId = $callback->getOrder()->getId();

        if ($initiationData === null) {
            $this->logger->warning('No payment initiation record found for callback, skipping initiation validation', [
                'callback_paysera_order_id' => $callbackPayseraOrderId,
            ]);

            return;
        }

        $storedPayseraOrderId = $initiationData->getPayseraOrderId();

        if ($storedPayseraOrderId === null) {
            $this->logger->error('Stored payment initiation has no Paysera order ID', [
                'order_id' => $initiationData->getOrderId(),
                'callback_paysera_order_id' => $callbackPayseraOrderId,
            ]);

            throw new CallbackValidationException(sprintf('Stored payment initiation has no Paysera order ID for order %d', $initiationData->getOrderId()));
        }

        if ($callbackPayseraOrderId !== $storedPayseraOrderId) {
            $this->logger->error('Paysera order ID mismatch', [
                'order_id' => $initiationData->getOrderId(),
                'expected_paysera_order_id' => $storedPayseraOrderId,
                'callback_paysera_order_id' => $callbackPayseraOrderId,
            ]);

            throw new CallbackValidationException(sprintf('Paysera order ID mismatch: expected %s, got %s', $storedPayseraOrderId, $callbackPayseraOrderId));
        }

        $orderInfo = $callback->getOrder()->getOrderInfo();
        $callbackAmount = $orderInfo->getAmount();
        $callbackCurrency = $orderInfo->getCurrency();

        $storedAmount = $initiationData->getOriginalAmount();
        $storedCurrency = $initiationData->getOriginalCurrency();

        if ($storedAmount !== null && $callbackAmount !== $storedAmount) {
            $this->logger->error('Initiated amount mismatch', [
                'order_id' => $initiationData->getOrderId(),
                'expected_amount' => $storedAmount,
                'callback_amount' => $callbackAmount,
            ]);

            throw new AmountMismatchException(sprintf('Initiated amount mismatch: expected %d cents, got %d cents', $storedAmount, $callbackAmount));
        }

        if ($storedCurrency !== null && $callbackCurrency !== $storedCurrency) {
            $this->logger->error('Initiated currency mismatch', [
                'order_id' => $initiationData->getOrderId(),
                'expected_currency' => $storedCurrency,
                'callback_currency' => $callbackCurrency,
            ]);

            throw new AmountMismatchException(sprintf('Initiated currency mismatch: expected %s, got %s', $storedCurrency, $callbackCurrency));
        }

        $this->logger->debug('Initiation data validated', [
            'order_id' => $initiationData->getOrderId(),
            'paysera_order_id' => $storedPayseraOrderId,
        ]);
    }
}
