<?php

/**
 * Copyright 2024-2025 Simply.IN Sp. z o.o.
 *
 * NOTICE OF LICENSE
 *
 * Licensed under the EUPL-1.2 or later.
 * You may not use this work except in compliance with the Licence.
 *
 * Copy of the Licence is available at:
 * https://joinup.ec.europa.eu/software/page/eupl
 * It is bundled with this package in the file LICENSE.txt
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the Licence is distributed on an as is basis,
 * without warranties or conditions of any kind, either express or implied.
 * Check the Licence for the specific language governing permissions
 * and limitations under the License.
 *
 * @author   Simply.IN Sp. z o.o.
 * @copyright 2024-2025 Simply.IN Sp. z o.o.
 * @license   https://joinup.ec.europa.eu/software/page/eupl
 */

declare(strict_types=1);

namespace SimplyIn\Ajax\Handler;

use Customer;
use Order;
use Simplyin;
use SimplyIn\Ajax\AjaxActionInterface;
use SimplyIn\Ajax\AjaxRequest;
use SimplyIn\Ajax\Service\CheckoutSessionService;
use SimplyIn\Api\SimplyInApiClient;
use SimplyIn\Session\SimplyInSessionInterface;
use SimplyInEmailCache;
use SimplyInOrder;
use SimplyInOrderSync;

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

final class PlaceOrderHandler implements AjaxActionInterface
{
    public function __construct(
        private readonly SimplyInApiClient $api,
        private readonly Simplyin $module,
        private readonly SimplyInSessionInterface $session,
        private readonly CheckoutSessionService $checkoutSession,
    ) {
    }

    public function name(): string
    {
        return 'placeOrder';
    }

    public function execute(AjaxRequest $request): array
    {
        $idCart = $request->getInt('id_cart');
        $orderToken = $request->getString('order_token');

        if ($orderToken !== $this->module->getOrderToken($idCart)) {
            return ['success' => false, 'error' => $this->module->l('Invalid order token', 'ajax')];
        }

        $order = Order::getByCartId($idCart);
        if (!$order instanceof Order) {
            return ['success' => false, 'error' => $this->module->l('Invalid order', 'ajax')];
        }

        $authToken = $this->checkoutSession->getAuthToken();
        $newAccountPhone = (string) $this->session->get('new_account_phone', '');

        $result = [];
        if ($authToken) {
            $result = $this->createOrderWithoutAccount($order, $authToken);
            $this->session->set('simplyin_auth_token', $authToken);
        } elseif ($newAccountPhone !== '' && $newAccountPhone !== '0') {
            $result = $this->createOrderAndAccount($order, $newAccountPhone);
        } else {
            $this->session->clear();

            return ['success' => false, 'error' => $this->module->l('Invalid order', 'ajax')];
        }

        $this->checkoutSession->clearPsAddressIds();

        $simplyinId = isset($result['createdOrder']['_id']) ? (string) $result['createdOrder']['_id'] : '';
        // The retry pays THIS order, so the authorization it was placed with is what a later
        // pay-again attempt needs - and this is the one place that still has it before
        // clearCheckout() below drops the session down to its post-checkout shape. Only the
        // already-authorized branch is stored here: the brand-new-account branch has no token of
        // its own yet at this point, and mints one only once the backend confirms the account,
        // which OrderCreationHandler persists the same way when that happens.
        //
        // Gated on the backend actually having confirmed this order, not merely on $authToken
        // being truthy: createOrderWithoutAccount() collapses a failed/unreachable API call to []
        // ($simplyinId === ''), and $authToken says nothing about whether THIS call succeeded - it
        // is the shopper's pre-existing authorization, valid or not regardless of this request. An
        // order the backend never confirmed is not one worth holding a stored authorization for.
        if ($this->isOrderConfirmed($result)) {
            $object = SimplyInOrder::getByIdOrder((int) $order->id);

            if ($object instanceof SimplyInOrder) {
                // Self-healing: a shop that has not yet run the auth_token upgrade must not have
                // this write fail outright on the payment path. Same guard SimplyInPayments takes
                // before its own writes.
                SimplyInOrder::ensureSchema();
                if ($simplyinId !== '') {
                    $object->simplyin_id = $simplyinId;
                }
                if ($authToken) {
                    $object->auth_token = (string) $authToken;
                }
                $object->save();
            }
        }

        // Placing the order ends the CHECKOUT, not the shopper's Simply.IN authorization. The full
        // wipe used to run here and took the auth token with it, so the order-confirmation page -
        // where the pay-again surface lives - asked the backend for payment methods with no token
        // and got back only the anonymous ones: add-a-card and BLIK. A shopper with cards on their
        // Simply.IN profile saw none of them, which is the opposite of what a 1-click product is
        // for. PaymentCreationService already draws this same line.
        $this->session->clearCheckout();

        return ['success' => true, 'result' => $result];
    }

    /**
     * Whether the backend actually confirmed this order - created now, or already there from an
     * earlier attempt. Mirrors OrderCreationHandler's own success check for the same endpoint.
     *
     * @param array<string, mixed> $result
     */
    private function isOrderConfirmed(array $result): bool
    {
        if (isset($result['createdOrder']['_id'])) {
            return true;
        }

        return isset($result['message']) && $result['message'] === 'Order already exists';
    }

    /**
     * @return array<string, mixed>
     */
    private function createOrderWithoutAccount(Order $order, string $authToken): array
    {
        $orderData = SimplyInOrderSync::getOrderData((int) $order->id);
        $orderData['newOrderData']['placedDuringAccountCreation'] = false;
        unset($orderData['newAccountData']);

        $result = $this->api->apiCall($orderData, 'POST', 'checkout/createOrderWithoutAccount', $authToken);

        return is_array($result) ? $result : [];
    }

    /**
     * @return array<string, mixed>
     */
    private function createOrderAndAccount(Order $order, string $phoneNumber): array
    {
        $customer = new Customer((int) $order->id_customer);
        SimplyInEmailCache::clearCacheByEmail((string) $customer->email);

        $orderData = SimplyInOrderSync::getOrderData((int) $order->id);
        $orderData['newOrderData']['placedDuringAccountCreation'] = true;
        $orderData['newAccountData'] ??= [];
        $orderData['newAccountData']['email'] = (string) $customer->email;
        $orderData['newAccountData']['phoneNumber'] = $phoneNumber;

        $result = $this->api->apiCall($orderData, 'POST', 'checkout/createOrderAndAccount');

        return is_array($result) ? $result : [];
    }
}
