<?php // phpcs:disable PSR1.Files.SideEffects.FoundWithSymbols

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

declare(strict_types=1);

use Address as PrestaAddress;
use Customer as PrestaCustomer;
use Order as PrestaOrder;
use Resursbank\Ecom\Exception\ApiException;
use Resursbank\Ecom\Exception\AttributeCombinationException;
use Resursbank\Ecom\Exception\AuthException;
use Resursbank\Ecom\Exception\CacheException;
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\ValidationException;
use Resursbank\Ecom\Lib\Api\Environment;
use Resursbank\Ecom\Lib\Model\Address;
use Resursbank\Ecom\Lib\Model\Payment\CreatePaymentRequest\Options;
use Resursbank\Ecom\Lib\Model\Payment\CreatePaymentRequest\Options\Callback;
use Resursbank\Ecom\Lib\Model\Payment\CreatePaymentRequest\Options\Callbacks;
use Resursbank\Ecom\Lib\Model\Payment\CreatePaymentRequest\Options\ParticipantRedirectionUrls;
use Resursbank\Ecom\Lib\Model\Payment\CreatePaymentRequest\Options\RedirectionUrls;
use Resursbank\Ecom\Lib\Model\Payment\Customer;
use Resursbank\Ecom\Lib\Model\PaymentHistory\Entry;
use Resursbank\Ecom\Lib\Model\PaymentHistory\Event;
use Resursbank\Ecom\Lib\Model\PaymentHistory\Result;
use Resursbank\Ecom\Lib\Model\PaymentHistory\User;
use Resursbank\Ecom\Lib\Model\PaymentMethod;
use Resursbank\Ecom\Lib\Order\CountryCode;
use Resursbank\Ecom\Lib\Order\CustomerType;
use Resursbank\Ecom\Module\Payment\Repository as PaymentRepository;
use Resursbank\Ecom\Module\PaymentMethod\Repository;
use Resursbank\MerchantApi\Config\Config;
use Resursbank\MerchantApi\Exception\FakeContainerException;
use Resursbank\MerchantApi\Exception\InvalidDataException;
use Resursbank\MerchantApi\Repository\ResursbankOrderRepositoryFrontend;
use Resursbank\MerchantApi\Service\Cart as CartService;
use Resursbank\MerchantApi\Service\FakeContainer;
use Resursbank\MerchantApi\Service\Order;
use Resursbank\MerchantApi\Service\PaymentHistory;
use Resursbank\MerchantApi\Util\Log;
use Resursbank\MerchantApi\Util\Translator;

/**
 * Order creation class. The naming is based on the principle "Validate cart to order".
 *
 * @since 1.0.0
 */
// phpcs:disable
class ResursbankValidationModuleFrontController extends ModuleFrontController
// phpcs:enable
{
    /**
     * @throws Exception
     *
     * @see FrontController::postProcess()
     * @since 1.0.0
     */
    public function postProcess(): void
    {
        try {
            $cart = $this->getCart();
            $customer = $this->getCustomer(cart: $cart);
            $paymentMethod = $this->getPaymentMethod();
            $order = $this->placeOrder(
                cart: $cart,
                customer: $customer,
                currency: $this->getCurrency(),
                paymentMethod: $paymentMethod
            );
        } catch (Throwable $error) {
            Log::error(message: $error);
            $this->goBack(
                details: $error->getMessage()
            );

            return;
        }

        try {
            $config = FakeContainer::getConfig();
            $collection = Order::convert(order: $order);
            $options = $this->getOptions(order: $order, cart: $cart, customer: $customer, config: $config);
            $customerData = $this->getCustomerData(order: $order, customer: $customer, paymentMethod: $paymentMethod);

            $session = PaymentRepository::create(
                paymentMethodId: $paymentMethod->id,
                orderLines: $collection,
                orderReference: $order->reference,
                customer: $customerData,
                options: $options
            );

            // @todo Not sure if we need to wait for callbacks.
            ResursbankOrderRepositoryFrontend::createMapiRecord(
                order: $order,
                payment: $session,
                test: $config->getEnvironment() === Environment::TEST,
                readyForCallback: true
            );

            if ($session->order->capturedAmount > 0.0) {
                Order::createInvoice(
                    payment: $session,
                    order: $order,
                    capturedAmount: $session->order->capturedAmount
                );
            }

            // Clear fetchAddress cookies when not needed anymore.
            unset($this->context->cookie->govId);
            unset($this->context->cookie->customerType);
            $this->context->cookie->write();

            $redirectUrl = $session->taskRedirectionUrls?->customerUrl ??
                $this->getSuccessUrl(order: $order, cart: $cart, customer: $customer);

            $paymentHistory = new PaymentHistory();
            $paymentHistory->write(
                entry: new Entry(
                    paymentId: $session->id,
                    event: Event::REDIRECTED_TO_GATEWAY,
                    user: User::RESURSBANK,
                    extra: $redirectUrl,
                    previousOrderStatus: null,
                    currentOrderStatus: null,
                    result: Result::SUCCESS,
                    userReference: null
                )
            );

            Tools::redirect(
                url: $redirectUrl
            );
        } catch (Throwable $error) {
            Log::error(message: $error);
            CartService::reinstate(order: $order);
            Order::cancel(order: $order);
            $this->goBack(
                details: $error instanceof CurlException ? $error->getDetails() : $error->getMessage()
            );
        }
    }

    /**
     * Resolve customer data for payment creation.
     *
     * @throws AttributeCombinationException
     * @throws IllegalValueException
     * @throws InvalidDataException
     * @throws JsonException
     * @throws PrestaShopDatabaseException
     * @throws PrestaShopException
     * @throws ReflectionException
     */
    public function getCustomerData(
        PrestaOrder $order,
        PrestaCustomer $customer,
        PaymentMethod $paymentMethod
    ): Customer {
        $deliveryAddress = new PrestaAddress(id_address: $order->id_address_delivery);
        $invoiceAddress = new PrestaAddress(
            id_address: $order->id_address_invoice
        );

        if (!Validate::isLoadedObject(object: $deliveryAddress)) {
            throw new InvalidDataException(
                message: "Address $order->id_address_delivery not found or invalid."
            );
        }

        if (!Validate::isLoadedObject(object: $invoiceAddress)) {
            throw new InvalidDataException(
                message: "Address $order->id_address_delivery not found or invalid."
            );
        }

        $country = new Country(id: $deliveryAddress->id_country);

        if (!Validate::isLoadedObject(object: $country)) {
            throw new InvalidDataException(
                message: "Country $deliveryAddress->id_country not found or invalid."
            );
        }

        $fullName = $deliveryAddress->company !== '' ?
            $deliveryAddress->company :
            $deliveryAddress->firstname . ' ' . $deliveryAddress->lastname;

        $contactPerson = $deliveryAddress->company !== '' ?
            $deliveryAddress->firstname . ' ' . $deliveryAddress->lastname :
            '';

        if ((int)$order->id_address_delivery !== (int)$order->id_address_invoice) {
            // Prioritize invoice address phone/mobile when delivery address differs
            $customerPhone = $invoiceAddress->phone_mobile
                ?: $invoiceAddress->phone
                    ?: $deliveryAddress->phone_mobile
                        ?: $deliveryAddress->phone;
        } else {
            // If no separate delivery address, keep delivery first
            $customerPhone = $deliveryAddress->phone_mobile
                ?: $invoiceAddress->phone_mobile
                    ?: $deliveryAddress->phone
                        ?: $invoiceAddress->phone;
        }

        // $deliveryAddress->company may wrongfully contain a name that is not really a company
        // name but a person's name.
        $governmentId = $this->context->cookie->govId ?? null;
        $customerType = $this->getCustomerType(address: $deliveryAddress, paymentMethod: $paymentMethod);

        return new Customer(
            deliveryAddress: new Address(
                addressRow1: $deliveryAddress->address1,
                postalArea: $deliveryAddress->city,
                postalCode: $deliveryAddress->postcode,
                countryCode: CountryCode::from(
                    value: $country->iso_code
                ),
                fullName: $fullName,
                firstName: $deliveryAddress->firstname,
                lastName: $deliveryAddress->lastname,
                addressRow2: $deliveryAddress->address2,
            ),
            customerType: $customerType,
            contactPerson: $contactPerson,
            email: $customer->email,
            governmentId: $governmentId,
            mobilePhone: $customerPhone
        );
    }

    /**
     * Get the customer type based on the address, payment method and eventually via the fetchAddress cookie.
     *
     * @param PrestaAddress $address
     * @param PaymentMethod $paymentMethod
     * @return CustomerType
     */
    private function getCustomerType(PrestaAddress $address, PaymentMethod $paymentMethod): CustomerType
    {
        // Default to NATURAL customer type
        $customerType = CustomerType::NATURAL;

        // Attempt to retrieve the customer type from the cookie
        $cookieCustomerType = $this->context->cookie->customerType ?? null;

        if (is_string(value: $cookieCustomerType)) {
            $parsedCustomerType = CustomerType::tryFrom(value: $cookieCustomerType);
            if ($parsedCustomerType !== null) {
                $customerType = $parsedCustomerType;
            }
        }

        // Validate that the chosen type is allowed according to the PaymentMethod
        if ($customerType === CustomerType::LEGAL && !$paymentMethod->enabledForLegalCustomer) {
            $customerType = CustomerType::NATURAL;
        } elseif ($customerType === CustomerType::NATURAL && !$paymentMethod->enabledForNaturalCustomer) {
            $customerType = CustomerType::LEGAL;
        }

        // If the address contains a company, prioritize LEGAL if it is allowed
        if (!empty($address->company) && $paymentMethod->enabledForLegalCustomer) {
            $customerType = CustomerType::LEGAL;
        }

        return $customerType;
    }


    /**
     * @throws IllegalValueException
     * @throws JsonException
     * @throws ReflectionException
     * @throws AttributeCombinationException
     */
    public function getOptions(
        PrestaOrder $order,
        Cart $cart,
        PrestaCustomer $customer,
        Config $config,
    ): Options {
        return new Options(
            initiatedOnCustomersDevice: true,
            handleManualInspection: false,
            handleFrozenPayments: true,
            automaticCapture: false,
            redirectionUrls: new RedirectionUrls(
                customer: new ParticipantRedirectionUrls(
                    failUrl: $this->getFailUrl(order: $order),
                    successUrl: $this->getSuccessUrl(
                        order: $order,
                        cart: $cart,
                        customer: $customer
                    )
                ),
                coApplicant: null,
                merchant: null
            ),
            callbacks: new Callbacks(
                authorization: new Callback(
                    url: $this->getCallbackUrl()
                ),
                management: null,
                creditApplication: null
            ),
            timeToLiveInMinutes: $config->getCallbackTtl(),
        );
    }

    /**
     * @return string
     */
    public function getCallbackUrl(): string
    {
        try {
            $config = FakeContainer::getConfig();
        } catch (FakeContainerException $e) {
            Log::error(message: $e);
        }

        $xdebugSession = $config?->getXdebugSession() ?? '';

        $url = $this->context->link->getModuleLink(
            module: 'resursbank',
            controller: 'authorization'
        );

        if (!empty($xdebugSession)) {
            $separator = str_contains(haystack: $url, needle: '?') ? '&' : '?';
            $url .= $separator . 'XDEBUG_SESSION_START=' . urlencode($xdebugSession);
        }

        return $url;
    }

    public function getFailUrl(
        PrestaOrder $order
    ): string {
        return $this->context->link->getModuleLink(
            module: 'resursbank',
            controller: 'fail',
            params: ['order_id' => $order->id]
        );
    }

    public function getSuccessUrl(
        PrestaOrder $order,
        Cart $cart,
        PrestaCustomer $customer
    ): string {
        $cartId = $cart->id;
        $moduleId = $this->module->id;
        $orderId = $order->id;
        $secureKey = $customer->secure_key;

        return Context::getContext()->link->getPageLink(
            controller: 'order-confirmation',
            ssl: true,
            idLang: null,
            request: [
                'id_cart' => $cartId,
                'id_module' => $moduleId,
                'id_order' => $orderId,
                'key' => $secureKey,
            ]
        );
    }

    /**
     * @throws InvalidDataException
     * @throws PrestaShopDatabaseException
     * @throws PrestaShopException
     * @throws Exception
     */
    public function placeOrder(
        Cart $cart,
        PrestaCustomer $customer,
        Currency $currency,
        PaymentMethod $paymentMethod
    ): PrestaOrder {

        // Checks that are also made in the validateOrder method, but non-catchable.
        // Not doing this from here will generate an error (Error: parameter "idShop" is corrupted).
        // Instead, we should redirect customers if there is no cart to create an order from.
        if (!Validate::isLoadedObject(object: $this->context->cart) || $this->context->cart->OrderExists()) {
            $error = $this->trans(
                id: 'Cart cannot be loaded or an order has already been placed using this cart',
                parameters: [],
                domain: 'Admin.Payment.Notification'
            );
            throw new Exception(message: $error);
        }

        $this->module->validateOrder(
            id_cart: $cart->id,
            id_order_state: Configuration::get(key: Order::ORDER_STATE_REDIRECTED),
            amount_paid: $cart->getOrderTotal(),
            payment_method: $paymentMethod->name,
            message: null,
            extra_vars: [],
            currency_special: (int)$currency->id,
            dont_touch_amount: false,
            secure_key: $customer->secure_key
        );

        $orderId = (int)$this->module->currentOrder;
        $order = new PrestaOrder(id: $orderId);

        if (!Validate::isLoadedObject(object: $order)) {
            throw new InvalidDataException(message: 'Order not found.');
        }

        return $order;
    }

    public function getCurrency(): Currency
    {
        $currency = new Currency(id: $this->context->cart->id_currency);

        if (!Validate::isLoadedObject(object: $currency)) {
            throw new InvalidDataException(message: 'Currency not found.');
        }

        return $currency;
    }

    public function getCart(): Cart
    {
        $cart = $this->context->cart;

        if (!$this->module->active) {
            throw new InvalidDataException(message: 'Module not active.');
        }

        if ((int)$cart->id_customer === 0) {
            throw new InvalidDataException(message: 'No customer attached to cart.');
        }

        if ((int)$cart->id_address_delivery === 0) {
            throw new InvalidDataException(message: 'No delivery address attached to cart.');
        }

        if ((int)$cart->id_address_invoice === 0) {
            throw new InvalidDataException(message: 'No invoice address attached to cart.');
        }

        return $cart;
    }

    public function getCustomer(Cart $cart): PrestaCustomer
    {
        $customer = new PrestaCustomer(id: $cart->id_customer);

        if (!Validate::isLoadedObject(object: $customer)) {
            throw new InvalidDataException(
                message: "Customer $cart->id_customer not found or invalid."
            );
        }

        return $customer;
    }

    /**
     * Prestashop does not natively pass us this. We append it as a parameter
     * when registering our payment methods.
     *
     * @return PaymentMethod
     *
     * @throws IllegalValueException
     * @throws InvalidDataException
     * @throws JsonException
     * @throws ReflectionException
     * @throws Throwable
     * @throws ApiException
     * @throws AuthException
     * @throws CacheException
     * @throws ConfigException
     * @throws CurlException
     * @throws ValidationException
     * @throws EmptyValueException
     * @throws IllegalTypeException
     */
    public function getPaymentMethod(): PaymentMethod
    {
        $paymentMethodId = (string)Tools::getValue(key: 'payment_method_id');

        if ($paymentMethodId === '') {
            throw new InvalidDataException(message: 'No payment method ID provided.');
        }

        $paymentMethod = Repository::getById(paymentMethodId: $paymentMethodId);

        if (!$paymentMethod) {
            throw new InvalidDataException(message: "Payment method $paymentMethodId not found.");
        }

        return $paymentMethod;
    }

    /**
     * @param $details
     * @return void
     * @throws JsonException
     */
    public function goBack($details = null): void
    {
        $this->errors[] = Translator::translate(phraseId: 'failed-placing-order');

        if (is_array(value: $details)) {
            foreach ($details as $detail) {
                $this->errors[] = $detail;
            }
        } elseif (is_string($details)) {
            $this->errors[] = ucfirst(string: $details);
        }
        $this->context->cookie->__set(
            key: 'resursbank_fail_error',
            value: json_encode(value: $this->errors, flags: JSON_THROW_ON_ERROR)
        );
        $this->context->cookie->write();

        // Redirect back to check out step 1.
        Tools::redirect(url: 'index.php?controller=order&step=1');
    }

    /*private function createPayment(Order $order, PaymentMethod $paymentMethod, Options $options): void
    {
        // Implement the logic to create a payment at the gateway
        // Throw an exception if the payment creation fails
    }

    private function reinstateCart(Order $order): void
    {

    }

    private function deleteOrder(int $orderId): void
    {
        $order = new Order($orderId);
        if (Validate::isLoadedObject($order)) {
            $order->delete();
        }
    }

    */

    /*private function getOptions(OrderInterface $order): Options
    {
        $ttl = $this->config->getMagentoCronCleanupTtl(
            scopeCode: $order->getStore()->getCode()
        );

        return new Options(
            initiatedOnCustomersDevice: true,
            handleManualInspection: false,
            handleFrozenPayments: true,
            automaticCapture: false,
            redirectionUrls: new RedirectionUrls(
                customer: new ParticipantRedirectionUrls(
                    failUrl: $this->url->getFailureUrl(
                        quoteId: (int) $order->getQuoteId()
                    ),
                    successUrl: $this->url->getSuccessUrl(
                        quoteId: (int) $order->getQuoteId()
                    )
                ),
                coApplicant: null,
                merchant: null
            ),
            callbacks: new Callbacks(
                authorization: new Callback(
                    url: $this->ecomCallbackHelper->getUrl(type: 'authorization')
                ),
                management: null
            ),
            timeToLiveInMinutes: (!empty($ttl) && $ttl > 0) ? (int)$ttl : null,
        );
    }*/
}
