<?php

/**
 * Copyright © Resurs Bank AB. All rights reserved.
 * See LICENSE for license details.
 */

namespace Resursbank\MerchantApi\Hook;

use InvalidArgumentException;
use JsonException;
use Order;
use PrestaShop\PrestaShop\Adapter\Validate;
use PrestaShopDatabaseException;
use PrestaShopException;
use ReflectionException;
use Resursbank\Ecom\Exception\ApiException;
use Resursbank\Ecom\Exception\AttributeCombinationException;
use Resursbank\Ecom\Exception\AuthException;
use Resursbank\Ecom\Exception\ConfigException;
use Resursbank\Ecom\Exception\CurlException;
use Resursbank\Ecom\Exception\Validation\EmptyValueException;
use Resursbank\Ecom\Exception\Validation\IllegalTypeException;
use Resursbank\Ecom\Exception\Validation\IllegalValueException;
use Resursbank\Ecom\Exception\Validation\NotJsonEncodedException;
use Resursbank\Ecom\Exception\ValidationException;
use Resursbank\Ecom\Lib\Model\Payment;
use Resursbank\Ecom\Lib\Model\PaymentHistory\Entry;
use Resursbank\Ecom\Lib\Model\PaymentHistory\Event;
use Resursbank\Ecom\Lib\Model\PaymentHistory\User;
use Resursbank\Ecom\Module\Payment\Repository as PaymentRepository;
use Resursbank\Ecom\Module\PaymentHistory\Repository;
use Resursbank\MerchantApi\Exception\HookException;
use Resursbank\MerchantApi\Repository\ResursbankOrderRepositoryFrontend;
use Resursbank\MerchantApi\Util\Log;
use Throwable;

/**
 *  Track customer reaching order success page and sync order status (through
 * payment history integration).
 */
class DisplayOrderConfirmation
{
    /**
     * Track order success page in payment history.
     *
     * @param mixed $parameters
     * @return void
     */
    public function exec(mixed $parameters): void
    {
        try {
            $this->validateParameters(parameters: $parameters);

            if (!$this->isEnabled(parameters: $parameters)) {
                return;
            }

            $payment = $this->getMapiPayment(
                orderId: $this->getOrderId(parameters: $parameters)
            );

            if ($payment === null) {
                throw new InvalidArgumentException(message: 'Payment not found.');
            }

            if (!$this->hasHistoryEntries(payment: $payment)) {
                Repository::write(entry: new Entry(
                    paymentId: $payment->id,
                    event: Event::REACHED_ORDER_SUCCESS_PAGE,
                    user: User::CUSTOMER
                ));

                // Verify that the entry was actually created.
                if (!$this->hasHistoryEntries(payment: $payment)) {
                    Log::error(message: sprintf(
                        'Failed to verify REACHED_ORDER_SUCCESS_PAGE for payment id %s',
                        $payment->id
                    ));
                }
            }
        } catch (Throwable $error) {
            Log::error(message: $error);
        }
    }

    /**
     * Check if payment has relevant history entries for customer landing page writing.
     *
     * @param Payment $payment
     * @return bool
     * @throws ConfigException
     */
    private function hasHistoryEntries(Payment $payment): bool
    {
        $historyEntries = Repository::getList(
            paymentId: $payment->id,
            event: Event::REACHED_ORDER_SUCCESS_PAGE
        );

        return $historyEntries->count() > 0;
    }

    /**
     * Confirm a parameter list can be used in the context of frontend order action.
     *
     * @param mixed $parameters
     * @return void
     * @throws HookException
     */
    public function validateParameters(mixed $parameters): void
    {
        // Confirm that the parameters are an array.
        if (!is_array(value: $parameters)) {
            throw new HookException(message: 'Invalid parameters');
        }

        // Confirm the presence of order data.
        if (!isset($parameters['order'])) {
            throw new HookException(message: 'Order not found in parameters');
        }

        if (!$parameters['order'] instanceof Order) {
            throw new HookException(message: 'Invalid order parameters');
        }
    }

    /**
     * Frontend order hooks should only execute for Resurs Bank orders.
     *
     * @param array $parameters
     * @return bool
     */
    public function isEnabled(array $parameters): bool
    {
        return isset($parameters['order']->module) &&
            $parameters['order']->module === 'resursbank';
    }

    /**
     * Resolve order id from anonymous parameter list.
     *
     * @param array $parameters
     * @return int
     * @throws HookException
     */
    public function getOrderId(array $parameters): int
    {
        $id = (int)$parameters['order']->id;

        if ($id === 0) {
            throw new HookException(message: 'Invalid order id');
        }

        return $id;
    }

    /**
     * Resolve Order currently being viewed.
     *
     * @param int $orderId
     * @return Order
     * @throws HookException
     * @throws PrestaShopDatabaseException
     * @throws PrestaShopException
     */
    public function getOrder(
        int $orderId
    ): Order {
        $order = new Order(id: $orderId);

        if (!Validate::isLoadedObject(object: $order)) {
            throw new HookException(message: "Order not found on id $orderId");
        }

        return $order;
    }

    /**
     * Fetch MAPI payment.
     *
     * @param int $orderId
     * @return Payment
     * @throws ApiException
     * @throws AttributeCombinationException
     * @throws AuthException
     * @throws ConfigException
     * @throws CurlException
     * @throws EmptyValueException
     * @throws HookException
     * @throws IllegalTypeException
     * @throws IllegalValueException
     * @throws JsonException
     * @throws NotJsonEncodedException
     * @throws PrestaShopDatabaseException
     * @throws PrestaShopException
     * @throws ReflectionException
     * @throws ValidationException
     */
    public function getMapiPayment(
        int $orderId
    ): Payment {
        $resursbankOrder = ResursbankOrderRepositoryFrontend::findByOrderId(orderId: $orderId);

        if ($resursbankOrder === null) {
            throw new HookException(message: 'Resurs Bank order not found.');
        }

        return PaymentRepository::get(paymentId: $resursbankOrder->getMapiId());
    }
}
