<?php

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

declare(strict_types=1);

namespace Resursbank\MerchantApi\Service;

use Configuration;
use Db;
use DbQuery;
use JsonException;
use PrestaShopDatabaseException;
use ReflectionException;
use Resursbank\Ecom\Exception\AttributeCombinationException;
use Resursbank\Ecom\Exception\Validation\IllegalTypeException;
use Resursbank\Ecom\Lib\Model\PaymentHistory\DataHandler\DataHandlerInterface;
use Resursbank\Ecom\Lib\Model\PaymentHistory\Entry;
use Resursbank\Ecom\Lib\Model\PaymentHistory\EntryCollection;
use Resursbank\Ecom\Lib\Model\PaymentHistory\Event;
use Resursbank\MerchantApi\Exception\InvalidArgumentException;
use Resursbank\MerchantApi\Repository\PaymentHistory as PaymentHistoryRepository;
use Resursbank\MerchantApi\Repository\ResursbankOrderRepositoryFrontend;
use Resursbank\MerchantApi\Util\Log;
use Throwable;

/**
 * Payment history data handle integration.
 *
 * There is currently no Symfony implementation because the Ecom integration cannot be given a dynamic class based on
 * whether we are on frontend or backend since the backend implementation would require DI unavailable to us on front.
 */
class PaymentHistory implements DataHandlerInterface
{
    /**
     * Create new payment history entry.
     *
     * @param Entry $entry
     * @return void
     */
    public function write(Entry $entry): void
    {
        try {
            // Resurs Bank Order.
            $order = ResursbankOrderRepositoryFrontend::findByMapiId(mapiId: $entry->paymentId);

            // PrestaShop Order.
            $psOrder = ResursbankOrderRepositoryFrontend::getOrderByMapiId(mapiId: $entry->paymentId);

            $statusFrom = $entry->previousOrderStatus;
            $statusTo = $entry->currentOrderStatus;

            if (in_array(
                needle: $entry->event,
                haystack: [
                    Event::REACHED_ORDER_SUCCESS_PAGE,
                    Event::REACHED_ORDER_FAILURE_PAGE,
                    Event::CALLBACK_AUTHORIZATION
                ]
            )) {
                try {
                    // When invoice generating is disabled, this action tends to fail.
                    // The problem is not critical, and payment history still needs to be written.
                    // If it fails, just take not on it and log it.
                    $statusFrom = (int)$psOrder->getCurrentState();
                    $psOrder = Order::syncOrderState(
                        paymentId: $entry->paymentId,
                        order: $psOrder
                    );
                    $statusTo = (int)$psOrder->getCurrentState();
                } catch (Throwable $innerError) {
                    Log::error(message: $innerError);
                }
            }

            // Do not update the status if it is the same as current status.
            if ($statusTo === $statusFrom) {
                $statusFrom = null;
                $statusTo = null;
            } else {
                // Translate order state id to the corresponding order state name.
                $statusFrom = $this->getOrderStateNameById(stateId: $statusFrom);
                $statusTo = $this->getOrderStateNameById(stateId: $statusTo);
            }

            PaymentHistoryRepository::create(
                resursbankOrderId: $order->getId(),
                event: $entry->event->value,
                user: $entry->user->value,
                extra: $entry->extra,
                statusFrom: $statusFrom,
                statusTo: $statusTo,
                result: $entry->result->value,
                userReference: $entry->userReference
            );
        } catch (Throwable $error) {
            Log::error(message: $error);
        }
    }

    /**
     * Get a list of history entry attached to a payment.
     *
     * @param string $paymentId
     * @param Event|null $event
     * @return EntryCollection|null
     */
    public function getList(
        string $paymentId,
        ?Event $event = null
    ): ?EntryCollection {
        try {
            $order = ResursbankOrderRepositoryFrontend::findByMapiId(mapiId: $paymentId);

            return PaymentHistoryRepository::find(
                mapiId: $paymentId,
                resursbankOrderId: $order->getId(),
                event: $event
            );
        } catch (Throwable $error) {
            Log::error(message: $error);
        }

        return null;
    }

    /**
     * Check whether specific event has been executed for a payment.
     *
     * @param string $paymentId
     * @param Event $event
     * @return bool
     * @throws JsonException
     * @throws PrestaShopDatabaseException
     * @throws ReflectionException
     * @throws AttributeCombinationException
     * @throws IllegalTypeException
     * @throws InvalidArgumentException
     */
    public function hasExecuted(string $paymentId, Event $event): bool
    {
        $order = ResursbankOrderRepositoryFrontend::findByMapiId(mapiId: $paymentId);
        $collection = PaymentHistoryRepository::find(
            mapiId: $paymentId,
            resursbankOrderId: $order->getId(),
            event: $event
        );

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

    /**
     * Resolve order state name through id.
     *
     * @param int $stateId
     * @return string|null
     */
    private function getOrderStateNameById(int $stateId): ?string
    {
        $sql = new DbQuery();
        $sql->select(fields: 'osl.name');
        $sql->from(table: 'order_state', alias: 'os');
        $sql->leftJoin(table: 'order_state_lang', alias: 'osl', on: 'os.id_order_state = osl.id_order_state');
        $sql->where(restriction: 'os.id_order_state = ' . $stateId);
        $sql->where(restriction: 'osl.id_lang = ' . (int)Configuration::get(key: 'PS_LANG_DEFAULT'));

        return Db::getInstance()->getValue(sql: $sql);
    }
}
